def jn_zeros(n, k, method="diofant", dps=15): """ Zeros of the spherical Bessel function of the first kind. This returns an array of zeros of jn up to the k-th zero. * method = "diofant": uses mpmath's function ``besseljzero`` * method = "scipy": uses the `SciPy's sph_jn <http://docs.scipy.org/doc/scipy/reference/generated/scipy.special.jn_zeros.html>`_ and `newton <http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.newton.html>`_ to find all roots, which is faster than computing the zeros using a general numerical solver, but it requires SciPy and only works with low precision floating point numbers. [The function used with method="diofant" is a recent addition to mpmath, before that a general solver was used.] Examples ======== >>> from diofant import jn_zeros >>> jn_zeros(2, 4, dps=5) [5.7635, 9.095, 12.323, 15.515] See Also ======== jn, yn, besselj, besselk, bessely """ from math import pi if method == "diofant": prec = dps_to_prec(dps) return [ Expr._from_mpmath( besseljzero(sympify(n + 0.5)._to_mpmath(prec), int(l)), prec) for l in range(1, k + 1) ] elif method == "scipy": from scipy.optimize import newton try: from scipy.special import spherical_jn except ImportError: # pragma: no cover from scipy.special import sph_jn def spherical_jn(n, x): return sph_jn(n, x)[0][-1] def f(x): return spherical_jn(n, x) else: raise NotImplementedError("Unknown method.") def solver(f, x): if method == "scipy": root = newton(f, x) else: raise NotImplementedError("Unknown method.") return root # we need to approximate the position of the first root: root = n + pi # determine the first root exactly: root = solver(f, root) roots = [root] for i in range(k - 1): # estimate the position of the next root using the last root + pi: root = solver(f, root + pi) roots.append(root) return roots
def eval_approx(self, n): """Evaluate this complex root to the given precision. This uses secant method and root bounds are used to both generate an initial guess and to check that the root returned is valid. If ever the method converges outside the root bounds, the bounds will be made smaller and updated. """ prec = dps_to_prec(n) with workprec(prec): g = self.poly.gen if not g.is_Symbol: d = Dummy('x') if self.is_imaginary: d *= I func = lambdify(d, self.expr.subs(g, d)) else: expr = self.expr if self.is_imaginary: expr = self.expr.subs(g, I*g) func = lambdify(g, expr) interval = self._get_interval() while True: if self.is_real: a = mpf(str(interval.a)) b = mpf(str(interval.b)) if a == b: root = a break x0 = mpf(str(interval.center)) x1 = x0 + mpf(str(interval.dx))/4 elif self.is_imaginary: a = mpf(str(interval.ay)) b = mpf(str(interval.by)) if a == b: root = mpc(mpf('0'), a) break x0 = mpf(str(interval.center[1])) x1 = x0 + mpf(str(interval.dy))/4 else: ax = mpf(str(interval.ax)) bx = mpf(str(interval.bx)) ay = mpf(str(interval.ay)) by = mpf(str(interval.by)) if ax == bx and ay == by: root = mpc(ax, ay) break x0 = mpc(*map(str, interval.center)) x1 = x0 + mpc(*map(str, (interval.dx, interval.dy)))/4 try: # without a tolerance, this will return when (to within # the given precision) x_i == x_{i-1} root = findroot(func, (x0, x1)) # If the (real or complex) root is not in the 'interval', # then keep refining the interval. This happens if findroot # accidentally finds a different root outside of this # interval because our initial estimate 'x0' was not close # enough. It is also possible that the secant method will # get trapped by a max/min in the interval; the root # verification by findroot will raise a ValueError in this # case and the interval will then be tightened -- and # eventually the root will be found. # # It is also possible that findroot will not have any # successful iterations to process (in which case it # will fail to initialize a variable that is tested # after the iterations and raise an UnboundLocalError). if self.is_real or self.is_imaginary: if not bool(root.imag) == self.is_real and ( a <= root <= b): if self.is_imaginary: root = mpc(mpf('0'), root.real) break elif (ax <= root.real <= bx and ay <= root.imag <= by): break except (UnboundLocalError, ValueError): pass interval = interval.refine() # update the interval so we at least (for this precision or # less) don't have much work to do to recompute the root self._set_interval(interval) return (Float._new(root.real._mpf_, prec) + I*Float._new(root.imag._mpf_, prec))
def eval_approx(self, n): """Evaluate this complex root to the given precision. This uses secant method and root bounds are used to both generate an initial guess and to check that the root returned is valid. If ever the method converges outside the root bounds, the bounds will be made smaller and updated. """ prec = dps_to_prec(n) with workprec(prec): g = self.poly.gen if not g.is_Symbol: d = Dummy('x') if self.is_imaginary: d *= I func = lambdify(d, self.expr.subs(g, d)) else: expr = self.expr if self.is_imaginary: expr = self.expr.subs(g, I * g) func = lambdify(g, expr) interval = self._get_interval() while True: if self.is_real: a = mpf(str(interval.a)) b = mpf(str(interval.b)) if a == b: root = a break x0 = mpf(str(interval.center)) x1 = x0 + mpf(str(interval.dx)) / 4 elif self.is_imaginary: a = mpf(str(interval.ay)) b = mpf(str(interval.by)) if a == b: root = mpc(mpf('0'), a) break x0 = mpf(str(interval.center[1])) x1 = x0 + mpf(str(interval.dy)) / 4 else: ax = mpf(str(interval.ax)) bx = mpf(str(interval.bx)) ay = mpf(str(interval.ay)) by = mpf(str(interval.by)) if ax == bx and ay == by: root = mpc(ax, ay) break x0 = mpc(*map(str, interval.center)) x1 = x0 + mpc(*map(str, (interval.dx, interval.dy))) / 4 try: # without a tolerance, this will return when (to within # the given precision) x_i == x_{i-1} root = findroot(func, (x0, x1)) # If the (real or complex) root is not in the 'interval', # then keep refining the interval. This happens if findroot # accidentally finds a different root outside of this # interval because our initial estimate 'x0' was not close # enough. It is also possible that the secant method will # get trapped by a max/min in the interval; the root # verification by findroot will raise a ValueError in this # case and the interval will then be tightened -- and # eventually the root will be found. # # It is also possible that findroot will not have any # successful iterations to process (in which case it # will fail to initialize a variable that is tested # after the iterations and raise an UnboundLocalError). if self.is_real or self.is_imaginary: if not bool(root.imag) == self.is_real and (a <= root <= b): if self.is_imaginary: root = mpc(mpf('0'), root.real) break elif (ax <= root.real <= bx and ay <= root.imag <= by): break except (UnboundLocalError, ValueError): pass interval = interval.refine() # update the interval so we at least (for this precision or # less) don't have much work to do to recompute the root self._set_interval(interval) return (Float._new(root.real._mpf_, prec) + I * Float._new(root.imag._mpf_, prec))
def evalf(self, n=15, subs=None, maxn=100, chop=False, strict=False, quad=None, verbose=False): """ Evaluate the given formula to an accuracy of n digits. Optional keyword arguments: subs=<dict> Substitute numerical values for symbols, e.g. subs={x:3, y:1+pi}. The substitutions must be given as a dictionary. maxn=<integer> Allow a maximum temporary working precision of maxn digits (default=100) chop=<bool> Replace tiny real or imaginary parts in subresults by exact zeros (default=False) strict=<bool> Raise PrecisionExhausted if any subresult fails to evaluate to full accuracy, given the available maxprec (default=False) quad=<str> Choose algorithm for numerical quadrature. By default, tanh-sinh quadrature is used. For oscillatory integrals on an infinite interval, try quad='osc'. verbose=<bool> Print debug information (default=False) """ from sympy import Float, Number n = n if n is not None else 15 if subs and is_sequence(subs): raise TypeError('subs must be given as a dictionary') # for sake of sage that doesn't like evalf(1) if n == 1 and isinstance(self, Number): from sympy.core.expr import _mag rv = self.evalf(2, subs, maxn, chop, strict, quad, verbose) m = _mag(rv) rv = rv.round(1 - m) return rv if not evalf_table: _create_evalf_table() prec = dps_to_prec(n) options = { 'maxprec': max(prec, int(maxn * LG10)), 'chop': chop, 'strict': strict, 'verbose': verbose } if subs is not None: options['subs'] = subs if quad is not None: options['quad'] = quad try: result = evalf(self, prec + 4, options) except NotImplementedError: # Fall back to the ordinary evalf v = self._eval_evalf(prec) if v is None: return self try: # If the result is numerical, normalize it result = evalf(v, prec, options) except NotImplementedError: # Probably contains symbols or unknown functions return v re, im, re_acc, im_acc = result if re: p = max(min(prec, re_acc), 1) re = Float._new(re, p) else: re = S.Zero if im: p = max(min(prec, im_acc), 1) im = Float._new(im, p) return re + im * S.ImaginaryUnit else: return re
def jn_zeros(n, k, method="sympy", dps=15): """ Zeros of the spherical Bessel function of the first kind. This returns an array of zeros of jn up to the k-th zero. * method = "sympy": uses :func:`mpmath.besseljzero` * method = "scipy": uses the `SciPy's sph_jn <http://docs.scipy.org/doc/scipy/reference/generated/scipy.special.jn_zeros.html>`_ and `newton <http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.newton.html>`_ to find all roots, which is faster than computing the zeros using a general numerical solver, but it requires SciPy and only works with low precision floating point numbers. [The function used with method="sympy" is a recent addition to mpmath, before that a general solver was used.] Examples ======== >>> from sympy import jn_zeros >>> jn_zeros(2, 4, dps=5) [5.7635, 9.095, 12.323, 15.515] See Also ======== jn, yn, besselj, besselk, bessely """ from math import pi if method == "sympy": from mpmath import besseljzero from mpmath.libmp.libmpf import dps_to_prec from sympy import Expr prec = dps_to_prec(dps) return [Expr._from_mpmath(besseljzero(S(n + 0.5)._to_mpmath(prec), int(l)), prec) for l in range(1, k + 1)] elif method == "scipy": from scipy.special import sph_jn from scipy.optimize import newton f = lambda x: sph_jn(n, x)[0][-1] else: raise NotImplementedError("Unknown method.") def solver(f, x): if method == "scipy": root = newton(f, x) else: raise NotImplementedError("Unknown method.") return root # we need to approximate the position of the first root: root = n + pi # determine the first root exactly: root = solver(f, root) roots = [root] for i in range(k - 1): # estimate the position of the next root using the last root + pi: root = solver(f, root + pi) roots.append(root) return roots
def evalf(self, dps=15, subs=None, maxn=110, chop=False, strict=True, quad=None): """ Evaluate the given formula to an accuracy of dps decimal digits. Optional keyword arguments: subs=<dict> Substitute numerical values for symbols, e.g. subs={x:3, y:1+pi}. The substitutions must be given as a dictionary. maxn=<integer> Allow a maximum temporary working precision of maxn digits (default=110) chop=<bool> Replace tiny real or imaginary parts in subresults by exact zeros (default=False) strict=<bool> Raise PrecisionExhausted if any subresult fails to evaluate to full accuracy, given the available maxprec (default=True) quad=<str> Choose algorithm for numerical quadrature. By default, tanh-sinh quadrature is used. For oscillatory integrals on an infinite interval, try quad='osc'. """ from .numbers import Float, I, Integer if subs and is_sequence(subs): raise TypeError('subs must be given as a dictionary') if not evalf_table: _create_evalf_table() prec = dps_to_prec(dps) options = { 'maxprec': max(prec, int(maxn * LG10)), 'chop': chop, 'strict': strict } if subs is not None: options['subs'] = subs if quad is not None: options['quad'] = quad try: result = evalf(self, prec + 4, options) except PrecisionExhausted: if self.is_Float and self._prec >= prec: return Float._new(self._mpf_, prec) else: raise except NotImplementedError: # Fall back to the ordinary evalf v = self._eval_evalf(prec) # pylint: disable=assignment-from-none if v is None: return self else: # Normalize result return v.subs( {_: _.evalf(dps, strict=strict) for _ in v.atoms(Float)}) re, im, re_acc, im_acc = result if re: p = max(min(prec, re_acc), 1) re = Float._new(re, p) else: re = Integer(0) if im: p = max(min(prec, im_acc), 1) im = Float._new(im, p) return re + im * I else: return re
def evalf(self, n=15, subs=None, maxn=100, chop=False, strict=False, quad=None, verbose=False): """ Evaluate the given formula to an accuracy of n digits. Optional keyword arguments: subs=<dict> Substitute numerical values for symbols, e.g. subs={x:3, y:1+pi}. The substitutions must be given as a dictionary. maxn=<integer> Allow a maximum temporary working precision of maxn digits (default=100) chop=<bool> Replace tiny real or imaginary parts in subresults by exact zeros (default=False) strict=<bool> Raise PrecisionExhausted if any subresult fails to evaluate to full accuracy, given the available maxprec (default=False) quad=<str> Choose algorithm for numerical quadrature. By default, tanh-sinh quadrature is used. For oscillatory integrals on an infinite interval, try quad='osc'. verbose=<bool> Print debug information (default=False) """ from sympy import Float, Number n = n if n is not None else 15 if subs and is_sequence(subs): raise TypeError('subs must be given as a dictionary') # for sake of sage that doesn't like evalf(1) if n == 1 and isinstance(self, Number): from sympy.core.expr import _mag rv = self.evalf(2, subs, maxn, chop, strict, quad, verbose) m = _mag(rv) rv = rv.round(1 - m) return rv if not evalf_table: _create_evalf_table() prec = dps_to_prec(n) options = {'maxprec': max(prec, int(maxn*LG10)), 'chop': chop, 'strict': strict, 'verbose': verbose} if subs is not None: options['subs'] = subs if quad is not None: options['quad'] = quad try: result = evalf(self, prec + 4, options) except NotImplementedError: # Fall back to the ordinary evalf v = self._eval_evalf(prec) if v is None: return self try: # If the result is numerical, normalize it result = evalf(v, prec, options) except NotImplementedError: # Probably contains symbols or unknown functions return v re, im, re_acc, im_acc = result if re: p = max(min(prec, re_acc), 1) re = Float._new(re, p) else: re = S.Zero if im: p = max(min(prec, im_acc), 1) im = Float._new(im, p) return re + im*S.ImaginaryUnit else: return re
def evalf(self, dps=15, subs=None, maxn=110, chop=False, strict=True, quad=None): """ Evaluate the given formula to an accuracy of dps decimal digits. Optional keyword arguments: subs=<dict> Substitute numerical values for symbols, e.g. subs={x:3, y:1+pi}. The substitutions must be given as a dictionary. maxn=<integer> Allow a maximum temporary working precision of maxn digits (default=110) chop=<bool> Replace tiny real or imaginary parts in subresults by exact zeros (default=False) strict=<bool> Raise PrecisionExhausted if any subresult fails to evaluate to full accuracy, given the available maxprec (default=True) quad=<str> Choose algorithm for numerical quadrature. By default, tanh-sinh quadrature is used. For oscillatory integrals on an infinite interval, try quad='osc'. """ from .numbers import Float, I if subs and is_sequence(subs): raise TypeError('subs must be given as a dictionary') if not evalf_table: _create_evalf_table() prec = dps_to_prec(dps) options = {'maxprec': max(prec, int(maxn*LG10)), 'chop': chop, 'strict': strict} if subs is not None: options['subs'] = subs if quad is not None: options['quad'] = quad try: result = evalf(self, prec + 4, options) except PrecisionExhausted: if self.is_Float and self._prec >= prec: return Float._new(self._mpf_, prec) else: raise except NotImplementedError: # Fall back to the ordinary evalf v = self._eval_evalf(prec) if v is None: return self else: # Normalize result return v.subs({_: _.evalf(dps, strict=strict) for _ in v.atoms(Float)}) re, im, re_acc, im_acc = result if re: p = max(min(prec, re_acc), 1) re = Float._new(re, p) else: re = S.Zero if im: p = max(min(prec, im_acc), 1) im = Float._new(im, p) return re + im*I else: return re
def jn_zeros(n, k, method="diofant", dps=15): """ Zeros of the spherical Bessel function of the first kind. This returns an array of zeros of jn up to the k-th zero. * method = "diofant": uses mpmath's function ``besseljzero`` * method = "scipy": uses :func:`scipy.special.jn_zeros`. and :func:`scipy.optimize.newton` to find all roots, which is faster than computing the zeros using a general numerical solver, but it requires SciPy and only works with low precision floating point numbers. [The function used with method="diofant" is a recent addition to mpmath, before that a general solver was used.] Examples ======== >>> jn_zeros(2, 4, dps=5) [5.7635, 9.095, 12.323, 15.515] See Also ======== jn, yn, besselj, besselk, bessely """ from math import pi if method == "diofant": prec = dps_to_prec(dps) return [Expr._from_mpmath(besseljzero(sympify(n + 0.5)._to_mpmath(prec), int(l)), prec) for l in range(1, k + 1)] elif method == "scipy": from scipy.optimize import newton try: from scipy.special import spherical_jn except ImportError: # pragma: no cover from scipy.special import sph_jn def spherical_jn(n, x): return sph_jn(n, x)[0][-1] def f(x): return spherical_jn(n, x) else: raise NotImplementedError("Unknown method.") def solver(f, x): if method == "scipy": root = newton(f, x) else: raise NotImplementedError("Unknown method.") return root # we need to approximate the position of the first root: root = n + pi # determine the first root exactly: root = solver(f, root) roots = [root] for i in range(k - 1): # estimate the position of the next root using the last root + pi: root = solver(f, root + pi) roots.append(root) return roots
class Printer9000(PrettyPrinter): _float_cutoff = dps_to_prec(refs.ExtraPrecision) - 4 def _print_Float(self, e): # TODO: fix float printing in List e = s.Float(e, precision=max(e._prec - self._float_cutoff, 1)) full_prec = self._settings["full_prec"] if full_prec == "auto": full_prec = self._print_level == 1 return prettyForm(sstr(e, full_prec=full_prec)) def _print_Range(self, e): if isinstance(e, s.Range): return super()._print_Range(e) return super()._print_Function(e) def _print_Dot(self, e): if isinstance(e, Dot): return self._print_seq( e.args, None, None, ".", parenthesize=lambda x: precedence_traditional(x) <= PRECEDENCE["Mul"], ) return super()._print_Dot(e) def _print_Cross(self, e): if isinstance(e, Cross): return self._print_seq( e.args, None, None, "×", parenthesize=lambda x: precedence_traditional(x) <= PRECEDENCE["Mul"], ) return super()._print_Cross(e) # def _print_Rule(self, e): # TODO: Proper rule printing # return self._print_Implies(List( # self._print_seq(e.lhs), # self._print_seq(e.rhs) # ), altchar='->') def _print_Limit(self, lim): if isinstance(lim, Limit): return super()._print_Function(lim) return super()._print_Limit(lim) @staticmethod def pretty_int(x): x = str(x) i = len(x) % 3 for digit in x: if i > 0: yield digit i -= 1 else: i = 2 yield " " yield digit # def _print_Integer(self, x): # pretty_int = self.pretty_int(x) # final_str = ''.join(*pretty_int).strip() # prettyForm(final_str) def _print_List(self, e): # for better performance if len(e) > 10: avg_len = ( sum([len(pretty_print(e[x])) for x in range(0, len(e), len(e) // 10)]) / 10 ) avg_total_len = (avg_len + 2) * len(e) + 2 if avg_total_len > 1000: m = int(75 / avg_len) return self._print_seq( e.value[:m] + [ListSkip(len(e.value) - 2 * m)] + e.value[-m:], "{", "}", ) return self._print_seq(e.value, "{", "}") def _print_Mod(self, expr): if len(expr.args) > 2: return self._print_Function(expr) return super()._print_Mod(expr) def _print_Plus(self, expr): return super()._print_Add(s.Add(*expr.args, evaluate=False)) def _print_Power(self, expr): return super()._print_Pow(s.Pow(*expr.args, evaluate=False)) def _print_Times(self, expr): return super()._print_Mul(s.Mul(*expr.args, evaluate=False)) def _print_Max(self, expr): return self._print_Function(expr) def _print_Min(self, expr): return self._print_Function(expr) @staticmethod def _print_ComplexInfinity(*args): return prettyForm("ComplexInfinity") def _helper_print_function( self, func, args, sort=False, func_name=None, delimiter=", ", elementwise=False, ): if sort: args = sorted(args, key=s.utilities.default_sort_key) if not func_name and hasattr(func, "__name__"): func_name = func.__name__ if func_name: if func_name in FunctionWrappersReverse and not issubclass( func, DefinedFunction ): func_name = FunctionWrappersReverse[func_name] prettyFunc = self._print(s.Symbol(func_name)) else: prettyFunc = prettyForm(*self._print(func).parens(left="[", right="]")) if elementwise: if self._use_unicode: circ = pretty_atom("Modifier Letter Low Ring") else: circ = "." circ = self._print(circ) prettyFunc = prettyForm( binding=prettyForm.LINE, *stringPict.next(prettyFunc, circ) ) prettyArgs = prettyForm( *self._print_seq(args, delimiter=delimiter).parens(left="[", right="]") ) pform = prettyForm( binding=prettyForm.FUNC, *stringPict.next(prettyFunc, prettyArgs) ) # store pform parts so it can be reassembled e.g. when powered pform.prettyFunc = prettyFunc pform.prettyArgs = prettyArgs return pform