import random from operator import gt, lt from . import libmp from .calculus.calculus import CalculusMethods from .calculus.inverselaplace import LaplaceTransformInversionMethods from .calculus.odes import ODEMethods from .calculus.optimization import OptimizationMethods from .calculus.quadrature import QuadratureMethods from .functions.functions import SpecialFunctions from .functions.rszeta import RSCache from .identification import IdentificationMethods from .matrices.calculus import MatrixCalculusMethods from .matrices.eigen import Eigen from .matrices.linalg import LinearAlgebraMethods from .matrices.matrices import MatrixMethods from .visualization import VisualizationMethods class Context: pass class StandardBaseContext(Context, SpecialFunctions, RSCache, QuadratureMethods, LaplaceTransformInversionMethods, CalculusMethods, MatrixMethods, MatrixCalculusMethods, LinearAlgebraMethods, Eigen, IdentificationMethods, OptimizationMethods, ODEMethods, VisualizationMethods): NoConvergence = libmp.NoConvergence ComplexResult = libmp.ComplexResult def __init__(ctx): ctx._aliases = {} # Call those that need preinitialization (e.g. for wrappers) SpecialFunctions.__init__(ctx) RSCache.__init__(ctx) QuadratureMethods.__init__(ctx) LaplaceTransformInversionMethods.__init__(ctx) CalculusMethods.__init__(ctx) MatrixMethods.__init__(ctx) def _init_aliases(ctx): for alias, value in ctx._aliases.items(): try: setattr(ctx, alias, getattr(ctx, value)) except AttributeError: pass _fixed_precision = False # XXX verbose = False def warn(ctx, msg): print("Warning:", msg) def bad_domain(ctx, msg): raise ValueError(msg) def _re(ctx, x): if hasattr(x, "real"): return x.real return x def _im(ctx, x): if hasattr(x, "imag"): return x.imag return ctx.zero def _as_points(ctx, x): return x def fneg(ctx, x, **kwargs): return -ctx.convert(x) def fadd(ctx, x, y, **kwargs): return ctx.convert(x)+ctx.convert(y) def fsub(ctx, x, y, **kwargs): return ctx.convert(x)-ctx.convert(y) def fmul(ctx, x, y, **kwargs): return ctx.convert(x)*ctx.convert(y) def fdiv(ctx, x, y, **kwargs): return ctx.convert(x)/ctx.convert(y) def fsum(ctx, args, absolute=False, squared=False): if absolute: if squared: return sum((abs(x)**2 for x in args), ctx.zero) return sum((abs(x) for x in args), ctx.zero) if squared: return sum((x**2 for x in args), ctx.zero) return sum(args, ctx.zero) def fdot(ctx, xs, ys=None, conjugate=False): if ys is not None: xs = zip(xs, ys) if conjugate: cf = ctx.conj return sum((x*cf(y) for (x,y) in xs), ctx.zero) else: return sum((x*y for (x,y) in xs), ctx.zero) def fprod(ctx, args): prod = ctx.one for arg in args: prod *= arg return prod def nprint(ctx, x, n=6, **kwargs): """ Equivalent to ``print(nstr(x, n))``. """ print(ctx.nstr(x, n, **kwargs)) def chop(ctx, x, tol=None): """ Chops off small real or imaginary parts, or converts numbers close to zero to exact zeros. The input can be a single number or an iterable:: >>> from mpmath import chop, nprint >>> chop(5+1e-10j, tol=1e-9) mpf('5.0') >>> nprint(chop([1.0, 1e-20, 3+1e-18j, -4, 2])) [1.0, 0.0, 3.0, -4.0, 2.0] The tolerance defaults to ``100*eps``. """ if tol is None: tol = 100*ctx.eps try: x = ctx.convert(x) absx = abs(x) if abs(x) < tol: return ctx.zero if ctx._is_complex_type(x): #part_tol = min(tol, absx*tol) part_tol = max(tol, absx*tol) if abs(x.imag) < part_tol: return x.real if abs(x.real) < part_tol: return ctx.mpc(0, x.imag) except TypeError: if isinstance(x, ctx.matrix): return x.apply(lambda a: ctx.chop(a, tol)) if hasattr(x, "__iter__"): return [ctx.chop(a, tol) for a in x] return x def almosteq(ctx, s, t, rel_eps=None, abs_eps=None): r""" Determine whether the difference between `s` and `t` is smaller than a given epsilon, either relatively or absolutely. Both a maximum relative difference and a maximum difference ('epsilons') may be specified. The absolute difference is defined as `|s-t|` and the relative difference is defined as `|s-t|/\max(|s|, |t|)`. If only one epsilon is given, both are set to the same value. If none is given, both epsilons are set to `2^{-p+m}` where `p` is the current working precision and `m` is a small integer. The default setting typically allows :func:`~mpmath.almosteq` to be used to check for mathematical equality in the presence of small rounding errors. **Examples** >>> from mpmath import almosteq >>> almosteq(3.141592653589793, 3.141592653589790) True >>> almosteq(3.141592653589793, 3.141592653589700) False >>> almosteq(3.141592653589793, 3.141592653589700, 1e-10) True >>> almosteq(1e-20, 2e-20) True >>> almosteq(1e-20, 2e-20, rel_eps=0, abs_eps=0) False """ s = ctx.convert(s) t = ctx.convert(t) if any(ctx.isinf(_) or ctx.isnan(_) for _ in [s, t]): return s == t if abs_eps is None and rel_eps is None: rel_eps = abs_eps = ctx.ldexp(1, -ctx.prec+4) if abs_eps is None: abs_eps = rel_eps elif rel_eps is None: rel_eps = abs_eps diff = abs(s-t) if diff <= abs_eps: return True abss = abs(s) abst = abs(t) if abss < abst: err = diff/abst else: err = diff/abss return err <= rel_eps def arange(ctx, *args): r""" This is a generalized version of Python's :class:`range` function that accepts fractional endpoints and step sizes and returns a list of ``mpf`` instances. Like :class:`range`, :func:`~mpmath.arange` can be called with 1, 2 or 3 arguments: ``arange(b)`` `[0, 1, 2, \ldots, x]` ``arange(a, b)`` `[a, a+1, a+2, \ldots, x]` ``arange(a, b, h)`` `[a, a+h, a+h, \ldots, x]` where `b-1 \le x < b` (in the third case, `b-h \le x < b`). Like Python's :class:`range`, the endpoint is not included. To produce ranges where the endpoint is included, :func:`~mpmath.linspace` is more convenient. **Examples** >>> from mpmath import arange >>> arange(4) [mpf('0.0'), mpf('1.0'), mpf('2.0'), mpf('3.0')] >>> arange(1, 2, 0.25) [mpf('1.0'), mpf('1.25'), mpf('1.5'), mpf('1.75')] >>> arange(1, -1, -0.75) [mpf('1.0'), mpf('0.25'), mpf('-0.5')] """ if not len(args) <= 3: raise TypeError('arange expected at most 3 arguments, got %i' % len(args)) if not len(args) >= 1: raise TypeError('arange expected at least 1 argument, got %i' % len(args)) # set default a = 0 dt = 1 # interpret arguments if len(args) == 1: b = args[0] elif len(args) >= 2: a = args[0] b = args[1] if len(args) == 3: dt = args[2] a, b, dt = ctx.mpf(a), ctx.mpf(b), ctx.mpf(dt) assert a + dt != a, 'dt is too small and would cause an infinite loop' # adapt code for sign of dt if a > b: if dt > 0: return [] op = gt else: if dt < 0: return [] op = lt # create list result = [] i = 0 t = a while 1: t = a + dt*i i += 1 if op(t, b): result.append(t) else: break return result def linspace(ctx, *args, **kwargs): """ ``linspace(a, b, n)`` returns a list of `n` evenly spaced samples from `a` to `b`. The syntax ``linspace(mpi(a,b), n)`` is also valid. This function is often more convenient than :func:`~mpmath.arange` for partitioning an interval into subintervals, since the endpoint is included:: >>> from mpmath import linspace >>> linspace(1, 4, 4) [mpf('1.0'), mpf('2.0'), mpf('3.0'), mpf('4.0')] You may also provide the keyword argument ``endpoint=False``:: >>> linspace(1, 4, 4, endpoint=False) [mpf('1.0'), mpf('1.75'), mpf('2.5'), mpf('3.25')] """ if len(args) == 3: a = ctx.mpf(args[0]) b = ctx.mpf(args[1]) n = int(args[2]) elif len(args) == 2: assert hasattr(args[0], '_mpi_') a = args[0].a b = args[0].b n = int(args[1]) else: raise TypeError('linspace expected 2 or 3 arguments, got %i' \ % len(args)) if n < 1: raise ValueError('n must be greater than 0') if 'endpoint' not in kwargs or kwargs['endpoint']: if n == 1: return [ctx.mpf(a)] step = (b - a) / ctx.mpf(n - 1) y = [i*step + a for i in range(n)] y[-1] = b else: step = (b - a) / ctx.mpf(n) y = [i*step + a for i in range(n)] return y def cos_sin(ctx, z, **kwargs): return ctx.cos(z, **kwargs), ctx.sin(z, **kwargs) def cospi_sinpi(ctx, z, **kwargs): return ctx.cospi(z, **kwargs), ctx.sinpi(z, **kwargs) def _default_hyper_maxprec(ctx, p): return int(1000 * p**0.25 + 4*p) _gcd = staticmethod(libmp.libintmath.gcd) list_primes = staticmethod(libmp.libintmath.list_primes) isprime = staticmethod(libmp.libintmath.isprime) bernfrac = staticmethod(libmp.gammazeta.bernfrac) moebius = staticmethod(libmp.libintmath.moebius) _ifac = staticmethod(libmp.ifac) _eulernum = staticmethod(libmp.libintmath.eulernum) _stirling1 = staticmethod(libmp.libintmath.stirling1) _stirling2 = staticmethod(libmp.libintmath.stirling2) def sum_accurately(ctx, terms, check_step=1): prec = ctx.prec try: extraprec = 10 while 1: ctx.prec = prec + extraprec + 5 max_mag = ctx.ninf sum_mag = ctx.zero s = ctx.zero k = 0 for term in terms(): s += term if (not k % check_step) and term: term_mag = ctx.mag(term) max_mag = max(max_mag, term_mag) sum_mag = ctx.mag(s) if sum_mag - term_mag > ctx.prec: break k += 1 cancellation = max_mag - sum_mag if cancellation != cancellation: break if cancellation < extraprec or ctx._fixed_precision: break extraprec += min(ctx.prec, cancellation) return s finally: ctx.prec = prec def mul_accurately(ctx, factors, check_step=1): prec = ctx.prec try: extraprec = 10 while 1: ctx.prec = prec + extraprec + 5 max_mag = ctx.ninf one = ctx.one s = one k = 0 for factor in factors(): s *= factor term = factor - one if (not k % check_step): term_mag = ctx.mag(term) max_mag = max(max_mag, term_mag) sum_mag = ctx.mag(s-one) #if sum_mag - term_mag > ctx.prec: # break if -term_mag > ctx.prec: break k += 1 cancellation = max_mag - sum_mag if cancellation != cancellation: break if cancellation < extraprec or ctx._fixed_precision: break extraprec += min(ctx.prec, cancellation) return s finally: ctx.prec = prec def power(ctx, x, y): r"""Converts `x` and `y` to mpmath numbers and evaluates the principal value of `\exp(y \log(x))`:: >>> from mpmath import mp, power >>> mp.dps = 30 >>> mp.pretty = True >>> power(2, 0.5) 1.41421356237309504880168872421 This shows the leading few digits of a large Mersenne prime (performing the exact calculation ``2**43112609-1`` and displaying the result in Python would be very slow):: >>> power(2, 43112609)-1 3.16470269330255923143453723949e+12978188 **See Also** :func:`~mpmath.root` """ return ctx.convert(x) ** ctx.convert(y) def _zeta_int(ctx, n): return ctx.zeta(n) def maxcalls(ctx, f, N): """ Return a wrapped copy of *f* that raises ``NoConvergence`` when *f* has been called more than *N* times:: >>> from mpmath import maxcalls, sin >>> f = maxcalls(sin, 10) >>> print(sum(f(n) for n in range(10))) 1.95520948210738 >>> f(10) Traceback (most recent call last): ... NoConvergence: maxcalls: function evaluated 10 times """ counter = [0] def f_maxcalls_wrapped(*args, **kwargs): counter[0] += 1 if counter[0] > N: raise ctx.NoConvergence("maxcalls: function evaluated %i times" % N) return f(*args, **kwargs) return f_maxcalls_wrapped def rand(ctx): """ Get a random number in the range ``[0.0, 1.0)`` with (almost) uniform distribution. This method is a replacement for ``random.random()``. It is roughly equal to ``random.randint(0, 2 ** prec - 1) / (2 ** prec)``, where ``prec`` is the current resolution in bits. Just like ``random.random()`` and most other floating-point random number generators, the distribution is not perfect: Some float values within ``[0,1)`` will never be returned, for example, ``2 ** -(prec + 1)`` is impossible. See http://mumble.net/~campbell/2014/04/28/uniform-random-float for a lengthy discussion. """ # slow default implementation of rand() that works for arbitrary precision. return ctx.convert(random.getrandbits(ctx.prec)) * (ctx.convert(2) ** (-ctx.prec)) def memoize(ctx, f): """ Return a wrapped copy of *f* that caches computed values, i.e. a memoized copy of *f*. Values are only reused if the cached precision is equal to or higher than the working precision:: >>> from mpmath import memoize, maxcalls, mp, sin >>> mp.pretty = True >>> f = memoize(maxcalls(sin, 1)) >>> f(2) 0.909297426825682 >>> f(2) 0.909297426825682 >>> mp.dps = 25 >>> f(2) Traceback (most recent call last): ... NoConvergence: maxcalls: function evaluated 1 times """ f_cache = {} def f_cached(*args, **kwargs): if kwargs: key = args, tuple(kwargs.items()) else: key = args prec = ctx.prec if key in f_cache: cprec, cvalue = f_cache[key] if cprec >= prec: return +cvalue value = f(*args, **kwargs) f_cache[key] = (prec, value) return value f_cached.__name__ = f.__name__ f_cached.__doc__ = f.__doc__ return f_cached