Initial import: grid-bot — grid trading bot for BTC-USDT on Cifra Markets
This commit is contained in:
@@ -0,0 +1,468 @@
|
||||
__version__ = '1.3.0'
|
||||
|
||||
from .usertools import monitor, timing
|
||||
|
||||
from .ctx_fp import FPContext
|
||||
from .ctx_mp import MPContext
|
||||
from .ctx_iv import MPIntervalContext
|
||||
|
||||
fp = FPContext()
|
||||
mp = MPContext()
|
||||
iv = MPIntervalContext()
|
||||
|
||||
fp._mp = mp
|
||||
mp._mp = mp
|
||||
iv._mp = mp
|
||||
mp._fp = fp
|
||||
fp._fp = fp
|
||||
mp._iv = iv
|
||||
fp._iv = iv
|
||||
iv._iv = iv
|
||||
|
||||
# XXX: extremely bad pickle hack
|
||||
from . import ctx_mp as _ctx_mp
|
||||
_ctx_mp._mpf_module.mpf = mp.mpf
|
||||
_ctx_mp._mpf_module.mpc = mp.mpc
|
||||
|
||||
make_mpf = mp.make_mpf
|
||||
make_mpc = mp.make_mpc
|
||||
|
||||
extraprec = mp.extraprec
|
||||
extradps = mp.extradps
|
||||
workprec = mp.workprec
|
||||
workdps = mp.workdps
|
||||
autoprec = mp.autoprec
|
||||
maxcalls = mp.maxcalls
|
||||
memoize = mp.memoize
|
||||
|
||||
mag = mp.mag
|
||||
|
||||
bernfrac = mp.bernfrac
|
||||
|
||||
qfrom = mp.qfrom
|
||||
mfrom = mp.mfrom
|
||||
kfrom = mp.kfrom
|
||||
taufrom = mp.taufrom
|
||||
qbarfrom = mp.qbarfrom
|
||||
ellipfun = mp.ellipfun
|
||||
jtheta = mp.jtheta
|
||||
kleinj = mp.kleinj
|
||||
eta = mp.eta
|
||||
|
||||
qp = mp.qp
|
||||
qhyper = mp.qhyper
|
||||
qgamma = mp.qgamma
|
||||
qfac = mp.qfac
|
||||
|
||||
nint_distance = mp.nint_distance
|
||||
|
||||
plot = mp.plot
|
||||
cplot = mp.cplot
|
||||
splot = mp.splot
|
||||
|
||||
odefun = mp.odefun
|
||||
|
||||
jacobian = mp.jacobian
|
||||
findroot = mp.findroot
|
||||
multiplicity = mp.multiplicity
|
||||
|
||||
isinf = mp.isinf
|
||||
isnan = mp.isnan
|
||||
isnormal = mp.isnormal
|
||||
isint = mp.isint
|
||||
isfinite = mp.isfinite
|
||||
almosteq = mp.almosteq
|
||||
nan = mp.nan
|
||||
rand = mp.rand
|
||||
|
||||
absmin = mp.absmin
|
||||
absmax = mp.absmax
|
||||
|
||||
fraction = mp.fraction
|
||||
|
||||
linspace = mp.linspace
|
||||
arange = mp.arange
|
||||
|
||||
mpmathify = convert = mp.convert
|
||||
mpc = mp.mpc
|
||||
|
||||
mpi = iv._mpi
|
||||
|
||||
nstr = mp.nstr
|
||||
nprint = mp.nprint
|
||||
chop = mp.chop
|
||||
|
||||
fneg = mp.fneg
|
||||
fadd = mp.fadd
|
||||
fsub = mp.fsub
|
||||
fmul = mp.fmul
|
||||
fdiv = mp.fdiv
|
||||
fprod = mp.fprod
|
||||
|
||||
quad = mp.quad
|
||||
quadgl = mp.quadgl
|
||||
quadts = mp.quadts
|
||||
quadosc = mp.quadosc
|
||||
quadsubdiv = mp.quadsubdiv
|
||||
|
||||
invertlaplace = mp.invertlaplace
|
||||
invlaptalbot = mp.invlaptalbot
|
||||
invlapstehfest = mp.invlapstehfest
|
||||
invlapdehoog = mp.invlapdehoog
|
||||
|
||||
pslq = mp.pslq
|
||||
identify = mp.identify
|
||||
findpoly = mp.findpoly
|
||||
|
||||
richardson = mp.richardson
|
||||
shanks = mp.shanks
|
||||
levin = mp.levin
|
||||
cohen_alt = mp.cohen_alt
|
||||
nsum = mp.nsum
|
||||
nprod = mp.nprod
|
||||
difference = mp.difference
|
||||
diff = mp.diff
|
||||
diffs = mp.diffs
|
||||
diffs_prod = mp.diffs_prod
|
||||
diffs_exp = mp.diffs_exp
|
||||
diffun = mp.diffun
|
||||
differint = mp.differint
|
||||
taylor = mp.taylor
|
||||
pade = mp.pade
|
||||
polyval = mp.polyval
|
||||
polyroots = mp.polyroots
|
||||
fourier = mp.fourier
|
||||
fourierval = mp.fourierval
|
||||
sumem = mp.sumem
|
||||
sumap = mp.sumap
|
||||
chebyfit = mp.chebyfit
|
||||
limit = mp.limit
|
||||
|
||||
matrix = mp.matrix
|
||||
eye = mp.eye
|
||||
diag = mp.diag
|
||||
zeros = mp.zeros
|
||||
ones = mp.ones
|
||||
hilbert = mp.hilbert
|
||||
randmatrix = mp.randmatrix
|
||||
swap_row = mp.swap_row
|
||||
extend = mp.extend
|
||||
norm = mp.norm
|
||||
mnorm = mp.mnorm
|
||||
|
||||
lu_solve = mp.lu_solve
|
||||
lu = mp.lu
|
||||
qr = mp.qr
|
||||
unitvector = mp.unitvector
|
||||
inverse = mp.inverse
|
||||
residual = mp.residual
|
||||
qr_solve = mp.qr_solve
|
||||
cholesky = mp.cholesky
|
||||
cholesky_solve = mp.cholesky_solve
|
||||
det = mp.det
|
||||
cond = mp.cond
|
||||
hessenberg = mp.hessenberg
|
||||
schur = mp.schur
|
||||
eig = mp.eig
|
||||
eig_sort = mp.eig_sort
|
||||
eigsy = mp.eigsy
|
||||
eighe = mp.eighe
|
||||
eigh = mp.eigh
|
||||
svd_r = mp.svd_r
|
||||
svd_c = mp.svd_c
|
||||
svd = mp.svd
|
||||
gauss_quadrature = mp.gauss_quadrature
|
||||
|
||||
expm = mp.expm
|
||||
sqrtm = mp.sqrtm
|
||||
powm = mp.powm
|
||||
logm = mp.logm
|
||||
sinm = mp.sinm
|
||||
cosm = mp.cosm
|
||||
|
||||
mpf = mp.mpf
|
||||
j = mp.j
|
||||
exp = mp.exp
|
||||
expj = mp.expj
|
||||
expjpi = mp.expjpi
|
||||
ln = mp.ln
|
||||
im = mp.im
|
||||
re = mp.re
|
||||
inf = mp.inf
|
||||
ninf = mp.ninf
|
||||
sign = mp.sign
|
||||
|
||||
eps = mp.eps
|
||||
pi = mp.pi
|
||||
ln2 = mp.ln2
|
||||
ln10 = mp.ln10
|
||||
phi = mp.phi
|
||||
e = mp.e
|
||||
euler = mp.euler
|
||||
catalan = mp.catalan
|
||||
khinchin = mp.khinchin
|
||||
glaisher = mp.glaisher
|
||||
apery = mp.apery
|
||||
degree = mp.degree
|
||||
twinprime = mp.twinprime
|
||||
mertens = mp.mertens
|
||||
|
||||
ldexp = mp.ldexp
|
||||
frexp = mp.frexp
|
||||
|
||||
fsum = mp.fsum
|
||||
fdot = mp.fdot
|
||||
|
||||
sqrt = mp.sqrt
|
||||
cbrt = mp.cbrt
|
||||
exp = mp.exp
|
||||
ln = mp.ln
|
||||
log = mp.log
|
||||
log10 = mp.log10
|
||||
power = mp.power
|
||||
cos = mp.cos
|
||||
sin = mp.sin
|
||||
tan = mp.tan
|
||||
cosh = mp.cosh
|
||||
sinh = mp.sinh
|
||||
tanh = mp.tanh
|
||||
acos = mp.acos
|
||||
asin = mp.asin
|
||||
atan = mp.atan
|
||||
asinh = mp.asinh
|
||||
acosh = mp.acosh
|
||||
atanh = mp.atanh
|
||||
sec = mp.sec
|
||||
csc = mp.csc
|
||||
cot = mp.cot
|
||||
sech = mp.sech
|
||||
csch = mp.csch
|
||||
coth = mp.coth
|
||||
asec = mp.asec
|
||||
acsc = mp.acsc
|
||||
acot = mp.acot
|
||||
asech = mp.asech
|
||||
acsch = mp.acsch
|
||||
acoth = mp.acoth
|
||||
cospi = mp.cospi
|
||||
sinpi = mp.sinpi
|
||||
sinc = mp.sinc
|
||||
sincpi = mp.sincpi
|
||||
cos_sin = mp.cos_sin
|
||||
cospi_sinpi = mp.cospi_sinpi
|
||||
fabs = mp.fabs
|
||||
re = mp.re
|
||||
im = mp.im
|
||||
conj = mp.conj
|
||||
floor = mp.floor
|
||||
ceil = mp.ceil
|
||||
nint = mp.nint
|
||||
frac = mp.frac
|
||||
root = mp.root
|
||||
nthroot = mp.nthroot
|
||||
hypot = mp.hypot
|
||||
fmod = mp.fmod
|
||||
ldexp = mp.ldexp
|
||||
frexp = mp.frexp
|
||||
sign = mp.sign
|
||||
arg = mp.arg
|
||||
phase = mp.phase
|
||||
polar = mp.polar
|
||||
rect = mp.rect
|
||||
degrees = mp.degrees
|
||||
radians = mp.radians
|
||||
atan2 = mp.atan2
|
||||
fib = mp.fib
|
||||
fibonacci = mp.fibonacci
|
||||
lambertw = mp.lambertw
|
||||
zeta = mp.zeta
|
||||
altzeta = mp.altzeta
|
||||
gamma = mp.gamma
|
||||
rgamma = mp.rgamma
|
||||
factorial = mp.factorial
|
||||
fac = mp.fac
|
||||
fac2 = mp.fac2
|
||||
beta = mp.beta
|
||||
betainc = mp.betainc
|
||||
psi = mp.psi
|
||||
#psi0 = mp.psi0
|
||||
#psi1 = mp.psi1
|
||||
#psi2 = mp.psi2
|
||||
#psi3 = mp.psi3
|
||||
polygamma = mp.polygamma
|
||||
digamma = mp.digamma
|
||||
#trigamma = mp.trigamma
|
||||
#tetragamma = mp.tetragamma
|
||||
#pentagamma = mp.pentagamma
|
||||
harmonic = mp.harmonic
|
||||
bernoulli = mp.bernoulli
|
||||
bernfrac = mp.bernfrac
|
||||
stieltjes = mp.stieltjes
|
||||
hurwitz = mp.hurwitz
|
||||
dirichlet = mp.dirichlet
|
||||
bernpoly = mp.bernpoly
|
||||
eulerpoly = mp.eulerpoly
|
||||
eulernum = mp.eulernum
|
||||
polylog = mp.polylog
|
||||
clsin = mp.clsin
|
||||
clcos = mp.clcos
|
||||
gammainc = mp.gammainc
|
||||
gammaprod = mp.gammaprod
|
||||
binomial = mp.binomial
|
||||
rf = mp.rf
|
||||
ff = mp.ff
|
||||
hyper = mp.hyper
|
||||
hyp0f1 = mp.hyp0f1
|
||||
hyp1f1 = mp.hyp1f1
|
||||
hyp1f2 = mp.hyp1f2
|
||||
hyp2f1 = mp.hyp2f1
|
||||
hyp2f2 = mp.hyp2f2
|
||||
hyp2f0 = mp.hyp2f0
|
||||
hyp2f3 = mp.hyp2f3
|
||||
hyp3f2 = mp.hyp3f2
|
||||
hyperu = mp.hyperu
|
||||
hypercomb = mp.hypercomb
|
||||
meijerg = mp.meijerg
|
||||
appellf1 = mp.appellf1
|
||||
appellf2 = mp.appellf2
|
||||
appellf3 = mp.appellf3
|
||||
appellf4 = mp.appellf4
|
||||
hyper2d = mp.hyper2d
|
||||
bihyper = mp.bihyper
|
||||
erf = mp.erf
|
||||
erfc = mp.erfc
|
||||
erfi = mp.erfi
|
||||
erfinv = mp.erfinv
|
||||
npdf = mp.npdf
|
||||
ncdf = mp.ncdf
|
||||
expint = mp.expint
|
||||
e1 = mp.e1
|
||||
ei = mp.ei
|
||||
li = mp.li
|
||||
ci = mp.ci
|
||||
si = mp.si
|
||||
chi = mp.chi
|
||||
shi = mp.shi
|
||||
fresnels = mp.fresnels
|
||||
fresnelc = mp.fresnelc
|
||||
airyai = mp.airyai
|
||||
airybi = mp.airybi
|
||||
airyaizero = mp.airyaizero
|
||||
airybizero = mp.airybizero
|
||||
scorergi = mp.scorergi
|
||||
scorerhi = mp.scorerhi
|
||||
ellipk = mp.ellipk
|
||||
ellipe = mp.ellipe
|
||||
ellipf = mp.ellipf
|
||||
ellippi = mp.ellippi
|
||||
elliprc = mp.elliprc
|
||||
elliprj = mp.elliprj
|
||||
elliprf = mp.elliprf
|
||||
elliprd = mp.elliprd
|
||||
elliprg = mp.elliprg
|
||||
agm = mp.agm
|
||||
jacobi = mp.jacobi
|
||||
chebyt = mp.chebyt
|
||||
chebyu = mp.chebyu
|
||||
legendre = mp.legendre
|
||||
legenp = mp.legenp
|
||||
legenq = mp.legenq
|
||||
hermite = mp.hermite
|
||||
pcfd = mp.pcfd
|
||||
pcfu = mp.pcfu
|
||||
pcfv = mp.pcfv
|
||||
pcfw = mp.pcfw
|
||||
gegenbauer = mp.gegenbauer
|
||||
laguerre = mp.laguerre
|
||||
spherharm = mp.spherharm
|
||||
besselj = mp.besselj
|
||||
j0 = mp.j0
|
||||
j1 = mp.j1
|
||||
besseli = mp.besseli
|
||||
bessely = mp.bessely
|
||||
besselk = mp.besselk
|
||||
besseljzero = mp.besseljzero
|
||||
besselyzero = mp.besselyzero
|
||||
hankel1 = mp.hankel1
|
||||
hankel2 = mp.hankel2
|
||||
struveh = mp.struveh
|
||||
struvel = mp.struvel
|
||||
angerj = mp.angerj
|
||||
webere = mp.webere
|
||||
lommels1 = mp.lommels1
|
||||
lommels2 = mp.lommels2
|
||||
whitm = mp.whitm
|
||||
whitw = mp.whitw
|
||||
ber = mp.ber
|
||||
bei = mp.bei
|
||||
ker = mp.ker
|
||||
kei = mp.kei
|
||||
coulombc = mp.coulombc
|
||||
coulombf = mp.coulombf
|
||||
coulombg = mp.coulombg
|
||||
barnesg = mp.barnesg
|
||||
superfac = mp.superfac
|
||||
hyperfac = mp.hyperfac
|
||||
loggamma = mp.loggamma
|
||||
siegeltheta = mp.siegeltheta
|
||||
siegelz = mp.siegelz
|
||||
grampoint = mp.grampoint
|
||||
zetazero = mp.zetazero
|
||||
riemannr = mp.riemannr
|
||||
primepi = mp.primepi
|
||||
primepi2 = mp.primepi2
|
||||
primezeta = mp.primezeta
|
||||
bell = mp.bell
|
||||
polyexp = mp.polyexp
|
||||
expm1 = mp.expm1
|
||||
log1p = mp.log1p
|
||||
powm1 = mp.powm1
|
||||
unitroots = mp.unitroots
|
||||
cyclotomic = mp.cyclotomic
|
||||
mangoldt = mp.mangoldt
|
||||
secondzeta = mp.secondzeta
|
||||
nzeros = mp.nzeros
|
||||
backlunds = mp.backlunds
|
||||
lerchphi = mp.lerchphi
|
||||
stirling1 = mp.stirling1
|
||||
stirling2 = mp.stirling2
|
||||
squarew = mp.squarew
|
||||
trianglew = mp.trianglew
|
||||
sawtoothw = mp.sawtoothw
|
||||
unit_triangle = mp.unit_triangle
|
||||
sigmoid = mp.sigmoid
|
||||
|
||||
# be careful when changing this name, don't use test*!
|
||||
def runtests():
|
||||
"""
|
||||
Run all mpmath tests and print output.
|
||||
"""
|
||||
import os.path
|
||||
from inspect import getsourcefile
|
||||
from .tests import runtests as tests
|
||||
testdir = os.path.dirname(os.path.abspath(getsourcefile(tests)))
|
||||
importdir = os.path.abspath(testdir + '/../..')
|
||||
tests.testit(importdir, testdir)
|
||||
|
||||
def doctests(filter=[]):
|
||||
import sys
|
||||
from timeit import default_timer as clock
|
||||
for i, arg in enumerate(sys.argv):
|
||||
if '__init__.py' in arg:
|
||||
filter = [sn for sn in sys.argv[i+1:] if not sn.startswith("-")]
|
||||
break
|
||||
import doctest
|
||||
globs = globals().copy()
|
||||
for obj in globs: #sorted(globs.keys()):
|
||||
if filter:
|
||||
if not sum([pat in obj for pat in filter]):
|
||||
continue
|
||||
sys.stdout.write(str(obj) + " ")
|
||||
sys.stdout.flush()
|
||||
t1 = clock()
|
||||
doctest.run_docstring_examples(globs[obj], {}, verbose=("-v" in sys.argv))
|
||||
t2 = clock()
|
||||
print(round(t2-t1, 3))
|
||||
|
||||
if __name__ == '__main__':
|
||||
doctests()
|
||||
@@ -0,0 +1,6 @@
|
||||
from . import calculus
|
||||
# XXX: hack to set methods
|
||||
from . import approximation
|
||||
from . import differentiation
|
||||
from . import extrapolation
|
||||
from . import polynomials
|
||||
@@ -0,0 +1,246 @@
|
||||
from ..libmp.backend import xrange
|
||||
from .calculus import defun
|
||||
|
||||
#----------------------------------------------------------------------------#
|
||||
# Approximation methods #
|
||||
#----------------------------------------------------------------------------#
|
||||
|
||||
# The Chebyshev approximation formula is given at:
|
||||
# http://mathworld.wolfram.com/ChebyshevApproximationFormula.html
|
||||
|
||||
# The only major changes in the following code is that we return the
|
||||
# expanded polynomial coefficients instead of Chebyshev coefficients,
|
||||
# and that we automatically transform [a,b] -> [-1,1] and back
|
||||
# for convenience.
|
||||
|
||||
# Coefficient in Chebyshev approximation
|
||||
def chebcoeff(ctx,f,a,b,j,N):
|
||||
s = ctx.mpf(0)
|
||||
h = ctx.mpf(0.5)
|
||||
for k in range(1, N+1):
|
||||
t = ctx.cospi((k-h)/N)
|
||||
s += f(t*(b-a)*h + (b+a)*h) * ctx.cospi(j*(k-h)/N)
|
||||
return 2*s/N
|
||||
|
||||
# Generate Chebyshev polynomials T_n(ax+b) in expanded form
|
||||
def chebT(ctx, a=1, b=0):
|
||||
Tb = [1]
|
||||
yield Tb
|
||||
Ta = [b, a]
|
||||
while 1:
|
||||
yield Ta
|
||||
# Recurrence: T[n+1](ax+b) = 2*(ax+b)*T[n](ax+b) - T[n-1](ax+b)
|
||||
Tmp = [0] + [2*a*t for t in Ta]
|
||||
for i, c in enumerate(Ta): Tmp[i] += 2*b*c
|
||||
for i, c in enumerate(Tb): Tmp[i] -= c
|
||||
Ta, Tb = Tmp, Ta
|
||||
|
||||
@defun
|
||||
def chebyfit(ctx, f, interval, N, error=False):
|
||||
r"""
|
||||
Computes a polynomial of degree `N-1` that approximates the
|
||||
given function `f` on the interval `[a, b]`. With ``error=True``,
|
||||
:func:`~mpmath.chebyfit` also returns an accurate estimate of the
|
||||
maximum absolute error; that is, the maximum value of
|
||||
`|f(x) - P(x)|` for `x \in [a, b]`.
|
||||
|
||||
:func:`~mpmath.chebyfit` uses the Chebyshev approximation formula,
|
||||
which gives a nearly optimal solution: that is, the maximum
|
||||
error of the approximating polynomial is very close to
|
||||
the smallest possible for any polynomial of the same degree.
|
||||
|
||||
Chebyshev approximation is very useful if one needs repeated
|
||||
evaluation of an expensive function, such as function defined
|
||||
implicitly by an integral or a differential equation. (For
|
||||
example, it could be used to turn a slow mpmath function
|
||||
into a fast machine-precision version of the same.)
|
||||
|
||||
**Examples**
|
||||
|
||||
Here we use :func:`~mpmath.chebyfit` to generate a low-degree approximation
|
||||
of `f(x) = \cos(x)`, valid on the interval `[1, 2]`::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 15; mp.pretty = True
|
||||
>>> poly, err = chebyfit(cos, [1, 2], 5, error=True)
|
||||
>>> nprint(poly)
|
||||
[0.00291682, 0.146166, -0.732491, 0.174141, 0.949553]
|
||||
>>> nprint(err, 12)
|
||||
1.61351758081e-5
|
||||
|
||||
The polynomial can be evaluated using ``polyval``::
|
||||
|
||||
>>> nprint(polyval(poly, 1.6), 12)
|
||||
-0.0291858904138
|
||||
>>> nprint(cos(1.6), 12)
|
||||
-0.0291995223013
|
||||
|
||||
Sampling the true error at 1000 points shows that the error
|
||||
estimate generated by ``chebyfit`` is remarkably good::
|
||||
|
||||
>>> error = lambda x: abs(cos(x) - polyval(poly, x))
|
||||
>>> nprint(max([error(1+n/1000.) for n in range(1000)]), 12)
|
||||
1.61349954245e-5
|
||||
|
||||
**Choice of degree**
|
||||
|
||||
The degree `N` can be set arbitrarily high, to obtain an
|
||||
arbitrarily good approximation. As a rule of thumb, an
|
||||
`N`-term Chebyshev approximation is good to `N/(b-a)` decimal
|
||||
places on a unit interval (although this depends on how
|
||||
well-behaved `f` is). The cost grows accordingly: ``chebyfit``
|
||||
evaluates the function `(N^2)/2` times to compute the
|
||||
coefficients and an additional `N` times to estimate the error.
|
||||
|
||||
**Possible issues**
|
||||
|
||||
One should be careful to use a sufficiently high working
|
||||
precision both when calling ``chebyfit`` and when evaluating
|
||||
the resulting polynomial, as the polynomial is sometimes
|
||||
ill-conditioned. It is for example difficult to reach
|
||||
15-digit accuracy when evaluating the polynomial using
|
||||
machine precision floats, no matter the theoretical
|
||||
accuracy of the polynomial. (The option to return the
|
||||
coefficients in Chebyshev form should be made available
|
||||
in the future.)
|
||||
|
||||
It is important to note the Chebyshev approximation works
|
||||
poorly if `f` is not smooth. A function containing singularities,
|
||||
rapid oscillation, etc can be approximated more effectively by
|
||||
multiplying it by a weight function that cancels out the
|
||||
nonsmooth features, or by dividing the interval into several
|
||||
segments.
|
||||
"""
|
||||
a, b = ctx._as_points(interval)
|
||||
orig = ctx.prec
|
||||
try:
|
||||
ctx.prec = orig + int(N**0.5) + 20
|
||||
c = [chebcoeff(ctx,f,a,b,k,N) for k in range(N)]
|
||||
d = [ctx.zero] * N
|
||||
d[0] = -c[0]/2
|
||||
h = ctx.mpf(0.5)
|
||||
T = chebT(ctx, ctx.mpf(2)/(b-a), ctx.mpf(-1)*(b+a)/(b-a))
|
||||
for (k, Tk) in zip(range(N), T):
|
||||
for i in range(len(Tk)):
|
||||
d[i] += c[k]*Tk[i]
|
||||
d = d[::-1]
|
||||
# Estimate maximum error
|
||||
err = ctx.zero
|
||||
for k in range(N):
|
||||
x = ctx.cos(ctx.pi*k/N) * (b-a)*h + (b+a)*h
|
||||
err = max(err, abs(f(x) - ctx.polyval(d, x)))
|
||||
finally:
|
||||
ctx.prec = orig
|
||||
if error:
|
||||
return d, +err
|
||||
else:
|
||||
return d
|
||||
|
||||
@defun
|
||||
def fourier(ctx, f, interval, N):
|
||||
r"""
|
||||
Computes the Fourier series of degree `N` of the given function
|
||||
on the interval `[a, b]`. More precisely, :func:`~mpmath.fourier` returns
|
||||
two lists `(c, s)` of coefficients (the cosine series and sine
|
||||
series, respectively), such that
|
||||
|
||||
.. math ::
|
||||
|
||||
f(x) \sim \sum_{k=0}^N
|
||||
c_k \cos(k m x) + s_k \sin(k m x)
|
||||
|
||||
where `m = 2 \pi / (b-a)`.
|
||||
|
||||
Note that many texts define the first coefficient as `2 c_0` instead
|
||||
of `c_0`. The easiest way to evaluate the computed series correctly
|
||||
is to pass it to :func:`~mpmath.fourierval`.
|
||||
|
||||
**Examples**
|
||||
|
||||
The function `f(x) = x` has a simple Fourier series on the standard
|
||||
interval `[-\pi, \pi]`. The cosine coefficients are all zero (because
|
||||
the function has odd symmetry), and the sine coefficients are
|
||||
rational numbers::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 15; mp.pretty = True
|
||||
>>> c, s = fourier(lambda x: x, [-pi, pi], 5)
|
||||
>>> nprint(c)
|
||||
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
||||
>>> nprint(s)
|
||||
[0.0, 2.0, -1.0, 0.666667, -0.5, 0.4]
|
||||
|
||||
This computes a Fourier series of a nonsymmetric function on
|
||||
a nonstandard interval::
|
||||
|
||||
>>> I = [-1, 1.5]
|
||||
>>> f = lambda x: x**2 - 4*x + 1
|
||||
>>> cs = fourier(f, I, 4)
|
||||
>>> nprint(cs[0])
|
||||
[0.583333, 1.12479, -1.27552, 0.904708, -0.441296]
|
||||
>>> nprint(cs[1])
|
||||
[0.0, -2.6255, 0.580905, 0.219974, -0.540057]
|
||||
|
||||
It is instructive to plot a function along with its truncated
|
||||
Fourier series::
|
||||
|
||||
>>> plot([f, lambda x: fourierval(cs, I, x)], I) #doctest: +SKIP
|
||||
|
||||
Fourier series generally converge slowly (and may not converge
|
||||
pointwise). For example, if `f(x) = \cosh(x)`, a 10-term Fourier
|
||||
series gives an `L^2` error corresponding to 2-digit accuracy::
|
||||
|
||||
>>> I = [-1, 1]
|
||||
>>> cs = fourier(cosh, I, 9)
|
||||
>>> g = lambda x: (cosh(x) - fourierval(cs, I, x))**2
|
||||
>>> nprint(sqrt(quad(g, I)))
|
||||
0.00467963
|
||||
|
||||
:func:`~mpmath.fourier` uses numerical quadrature. For nonsmooth functions,
|
||||
the accuracy (and speed) can be improved by including all singular
|
||||
points in the interval specification::
|
||||
|
||||
>>> nprint(fourier(abs, [-1, 1], 0), 10)
|
||||
([0.5000441648], [0.0])
|
||||
>>> nprint(fourier(abs, [-1, 0, 1], 0), 10)
|
||||
([0.5], [0.0])
|
||||
|
||||
"""
|
||||
interval = ctx._as_points(interval)
|
||||
a = interval[0]
|
||||
b = interval[-1]
|
||||
L = b-a
|
||||
cos_series = []
|
||||
sin_series = []
|
||||
cutoff = ctx.eps*10
|
||||
for n in xrange(N+1):
|
||||
m = 2*n*ctx.pi/L
|
||||
an = 2*ctx.quadgl(lambda t: f(t)*ctx.cos(m*t), interval)/L
|
||||
bn = 2*ctx.quadgl(lambda t: f(t)*ctx.sin(m*t), interval)/L
|
||||
if n == 0:
|
||||
an /= 2
|
||||
if abs(an) < cutoff: an = ctx.zero
|
||||
if abs(bn) < cutoff: bn = ctx.zero
|
||||
cos_series.append(an)
|
||||
sin_series.append(bn)
|
||||
return cos_series, sin_series
|
||||
|
||||
@defun
|
||||
def fourierval(ctx, series, interval, x):
|
||||
"""
|
||||
Evaluates a Fourier series (in the format computed by
|
||||
by :func:`~mpmath.fourier` for the given interval) at the point `x`.
|
||||
|
||||
The series should be a pair `(c, s)` where `c` is the
|
||||
cosine series and `s` is the sine series. The two lists
|
||||
need not have the same length.
|
||||
"""
|
||||
cs, ss = series
|
||||
ab = ctx._as_points(interval)
|
||||
a = interval[0]
|
||||
b = interval[-1]
|
||||
m = 2*ctx.pi/(ab[-1]-ab[0])
|
||||
s = ctx.zero
|
||||
s += ctx.fsum(cs[n]*ctx.cos(m*n*x) for n in xrange(len(cs)) if cs[n])
|
||||
s += ctx.fsum(ss[n]*ctx.sin(m*n*x) for n in xrange(len(ss)) if ss[n])
|
||||
return s
|
||||
@@ -0,0 +1,6 @@
|
||||
class CalculusMethods(object):
|
||||
pass
|
||||
|
||||
def defun(f):
|
||||
setattr(CalculusMethods, f.__name__, f)
|
||||
return f
|
||||
@@ -0,0 +1,647 @@
|
||||
from ..libmp.backend import xrange
|
||||
from .calculus import defun
|
||||
|
||||
try:
|
||||
iteritems = dict.iteritems
|
||||
except AttributeError:
|
||||
iteritems = dict.items
|
||||
|
||||
#----------------------------------------------------------------------------#
|
||||
# Differentiation #
|
||||
#----------------------------------------------------------------------------#
|
||||
|
||||
@defun
|
||||
def difference(ctx, s, n):
|
||||
r"""
|
||||
Given a sequence `(s_k)` containing at least `n+1` items, returns the
|
||||
`n`-th forward difference,
|
||||
|
||||
.. math ::
|
||||
|
||||
\Delta^n = \sum_{k=0}^{\infty} (-1)^{k+n} {n \choose k} s_k.
|
||||
"""
|
||||
n = int(n)
|
||||
d = ctx.zero
|
||||
b = (-1) ** (n & 1)
|
||||
for k in xrange(n+1):
|
||||
d += b * s[k]
|
||||
b = (b * (k-n)) // (k+1)
|
||||
return d
|
||||
|
||||
def hsteps(ctx, f, x, n, prec, **options):
|
||||
singular = options.get('singular')
|
||||
addprec = options.get('addprec', 10)
|
||||
direction = options.get('direction', 0)
|
||||
workprec = (prec+2*addprec) * (n+1)
|
||||
orig = ctx.prec
|
||||
try:
|
||||
ctx.prec = workprec
|
||||
h = options.get('h')
|
||||
if h is None:
|
||||
if options.get('relative'):
|
||||
hextramag = int(ctx.mag(x))
|
||||
else:
|
||||
hextramag = 0
|
||||
h = ctx.ldexp(1, -prec-addprec-hextramag)
|
||||
else:
|
||||
h = ctx.convert(h)
|
||||
# Directed: steps x, x+h, ... x+n*h
|
||||
direction = options.get('direction', 0)
|
||||
if direction:
|
||||
h *= ctx.sign(direction)
|
||||
steps = xrange(n+1)
|
||||
norm = h
|
||||
# Central: steps x-n*h, x-(n-2)*h ..., x, ..., x+(n-2)*h, x+n*h
|
||||
else:
|
||||
steps = xrange(-n, n+1, 2)
|
||||
norm = (2*h)
|
||||
# Perturb
|
||||
if singular:
|
||||
x += 0.5*h
|
||||
values = [f(x+k*h) for k in steps]
|
||||
return values, norm, workprec
|
||||
finally:
|
||||
ctx.prec = orig
|
||||
|
||||
|
||||
@defun
|
||||
def diff(ctx, f, x, n=1, **options):
|
||||
r"""
|
||||
Numerically computes the derivative of `f`, `f'(x)`, or generally for
|
||||
an integer `n \ge 0`, the `n`-th derivative `f^{(n)}(x)`.
|
||||
A few basic examples are::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 15; mp.pretty = True
|
||||
>>> diff(lambda x: x**2 + x, 1.0)
|
||||
3.0
|
||||
>>> diff(lambda x: x**2 + x, 1.0, 2)
|
||||
2.0
|
||||
>>> diff(lambda x: x**2 + x, 1.0, 3)
|
||||
0.0
|
||||
>>> nprint([diff(exp, 3, n) for n in range(5)]) # exp'(x) = exp(x)
|
||||
[20.0855, 20.0855, 20.0855, 20.0855, 20.0855]
|
||||
|
||||
Even more generally, given a tuple of arguments `(x_1, \ldots, x_k)`
|
||||
and order `(n_1, \ldots, n_k)`, the partial derivative
|
||||
`f^{(n_1,\ldots,n_k)}(x_1,\ldots,x_k)` is evaluated. For example::
|
||||
|
||||
>>> diff(lambda x,y: 3*x*y + 2*y - x, (0.25, 0.5), (0,1))
|
||||
2.75
|
||||
>>> diff(lambda x,y: 3*x*y + 2*y - x, (0.25, 0.5), (1,1))
|
||||
3.0
|
||||
|
||||
**Options**
|
||||
|
||||
The following optional keyword arguments are recognized:
|
||||
|
||||
``method``
|
||||
Supported methods are ``'step'`` or ``'quad'``: derivatives may be
|
||||
computed using either a finite difference with a small step
|
||||
size `h` (default), or numerical quadrature.
|
||||
``direction``
|
||||
Direction of finite difference: can be -1 for a left
|
||||
difference, 0 for a central difference (default), or +1
|
||||
for a right difference; more generally can be any complex number.
|
||||
``addprec``
|
||||
Extra precision for `h` used to account for the function's
|
||||
sensitivity to perturbations (default = 10).
|
||||
``relative``
|
||||
Choose `h` relative to the magnitude of `x`, rather than an
|
||||
absolute value; useful for large or tiny `x` (default = False).
|
||||
``h``
|
||||
As an alternative to ``addprec`` and ``relative``, manually
|
||||
select the step size `h`.
|
||||
``singular``
|
||||
If True, evaluation exactly at the point `x` is avoided; this is
|
||||
useful for differentiating functions with removable singularities.
|
||||
Default = False.
|
||||
``radius``
|
||||
Radius of integration contour (with ``method = 'quad'``).
|
||||
Default = 0.25. A larger radius typically is faster and more
|
||||
accurate, but it must be chosen so that `f` has no
|
||||
singularities within the radius from the evaluation point.
|
||||
|
||||
A finite difference requires `n+1` function evaluations and must be
|
||||
performed at `(n+1)` times the target precision. Accordingly, `f` must
|
||||
support fast evaluation at high precision.
|
||||
|
||||
With integration, a larger number of function evaluations is
|
||||
required, but not much extra precision is required. For high order
|
||||
derivatives, this method may thus be faster if f is very expensive to
|
||||
evaluate at high precision.
|
||||
|
||||
**Further examples**
|
||||
|
||||
The direction option is useful for computing left- or right-sided
|
||||
derivatives of nonsmooth functions::
|
||||
|
||||
>>> diff(abs, 0, direction=0)
|
||||
0.0
|
||||
>>> diff(abs, 0, direction=1)
|
||||
1.0
|
||||
>>> diff(abs, 0, direction=-1)
|
||||
-1.0
|
||||
|
||||
More generally, if the direction is nonzero, a right difference
|
||||
is computed where the step size is multiplied by sign(direction).
|
||||
For example, with direction=+j, the derivative from the positive
|
||||
imaginary direction will be computed::
|
||||
|
||||
>>> diff(abs, 0, direction=j)
|
||||
(0.0 - 1.0j)
|
||||
|
||||
With integration, the result may have a small imaginary part
|
||||
even even if the result is purely real::
|
||||
|
||||
>>> diff(sqrt, 1, method='quad') # doctest:+ELLIPSIS
|
||||
(0.5 - 4.59...e-26j)
|
||||
>>> chop(_)
|
||||
0.5
|
||||
|
||||
Adding precision to obtain an accurate value::
|
||||
|
||||
>>> diff(cos, 1e-30)
|
||||
0.0
|
||||
>>> diff(cos, 1e-30, h=0.0001)
|
||||
-9.99999998328279e-31
|
||||
>>> diff(cos, 1e-30, addprec=100)
|
||||
-1.0e-30
|
||||
|
||||
"""
|
||||
partial = False
|
||||
try:
|
||||
orders = list(n)
|
||||
x = list(x)
|
||||
partial = True
|
||||
except TypeError:
|
||||
pass
|
||||
if partial:
|
||||
x = [ctx.convert(_) for _ in x]
|
||||
return _partial_diff(ctx, f, x, orders, options)
|
||||
method = options.get('method', 'step')
|
||||
if n == 0 and method != 'quad' and not options.get('singular'):
|
||||
return f(ctx.convert(x))
|
||||
prec = ctx.prec
|
||||
try:
|
||||
if method == 'step':
|
||||
values, norm, workprec = hsteps(ctx, f, x, n, prec, **options)
|
||||
ctx.prec = workprec
|
||||
v = ctx.difference(values, n) / norm**n
|
||||
elif method == 'quad':
|
||||
ctx.prec += 10
|
||||
radius = ctx.convert(options.get('radius', 0.25))
|
||||
def g(t):
|
||||
rei = radius*ctx.expj(t)
|
||||
z = x + rei
|
||||
return f(z) / rei**n
|
||||
d = ctx.quadts(g, [0, 2*ctx.pi])
|
||||
v = d * ctx.factorial(n) / (2*ctx.pi)
|
||||
else:
|
||||
raise ValueError("unknown method: %r" % method)
|
||||
finally:
|
||||
ctx.prec = prec
|
||||
return +v
|
||||
|
||||
def _partial_diff(ctx, f, xs, orders, options):
|
||||
if not orders:
|
||||
return f()
|
||||
if not sum(orders):
|
||||
return f(*xs)
|
||||
i = 0
|
||||
for i in range(len(orders)):
|
||||
if orders[i]:
|
||||
break
|
||||
order = orders[i]
|
||||
def fdiff_inner(*f_args):
|
||||
def inner(t):
|
||||
return f(*(f_args[:i] + (t,) + f_args[i+1:]))
|
||||
return ctx.diff(inner, f_args[i], order, **options)
|
||||
orders[i] = 0
|
||||
return _partial_diff(ctx, fdiff_inner, xs, orders, options)
|
||||
|
||||
@defun
|
||||
def diffs(ctx, f, x, n=None, **options):
|
||||
r"""
|
||||
Returns a generator that yields the sequence of derivatives
|
||||
|
||||
.. math ::
|
||||
|
||||
f(x), f'(x), f''(x), \ldots, f^{(k)}(x), \ldots
|
||||
|
||||
With ``method='step'``, :func:`~mpmath.diffs` uses only `O(k)`
|
||||
function evaluations to generate the first `k` derivatives,
|
||||
rather than the roughly `O(k^2)` evaluations
|
||||
required if one calls :func:`~mpmath.diff` `k` separate times.
|
||||
|
||||
With `n < \infty`, the generator stops as soon as the
|
||||
`n`-th derivative has been generated. If the exact number of
|
||||
needed derivatives is known in advance, this is further
|
||||
slightly more efficient.
|
||||
|
||||
Options are the same as for :func:`~mpmath.diff`.
|
||||
|
||||
**Examples**
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 15
|
||||
>>> nprint(list(diffs(cos, 1, 5)))
|
||||
[0.540302, -0.841471, -0.540302, 0.841471, 0.540302, -0.841471]
|
||||
>>> for i, d in zip(range(6), diffs(cos, 1)):
|
||||
... print("%s %s" % (i, d))
|
||||
...
|
||||
0 0.54030230586814
|
||||
1 -0.841470984807897
|
||||
2 -0.54030230586814
|
||||
3 0.841470984807897
|
||||
4 0.54030230586814
|
||||
5 -0.841470984807897
|
||||
|
||||
"""
|
||||
if n is None:
|
||||
n = ctx.inf
|
||||
else:
|
||||
n = int(n)
|
||||
if options.get('method', 'step') != 'step':
|
||||
k = 0
|
||||
while k < n + 1:
|
||||
yield ctx.diff(f, x, k, **options)
|
||||
k += 1
|
||||
return
|
||||
singular = options.get('singular')
|
||||
if singular:
|
||||
yield ctx.diff(f, x, 0, singular=True)
|
||||
else:
|
||||
yield f(ctx.convert(x))
|
||||
if n < 1:
|
||||
return
|
||||
if n == ctx.inf:
|
||||
A, B = 1, 2
|
||||
else:
|
||||
A, B = 1, n+1
|
||||
while 1:
|
||||
callprec = ctx.prec
|
||||
y, norm, workprec = hsteps(ctx, f, x, B, callprec, **options)
|
||||
for k in xrange(A, B):
|
||||
try:
|
||||
ctx.prec = workprec
|
||||
d = ctx.difference(y, k) / norm**k
|
||||
finally:
|
||||
ctx.prec = callprec
|
||||
yield +d
|
||||
if k >= n:
|
||||
return
|
||||
A, B = B, int(A*1.4+1)
|
||||
B = min(B, n)
|
||||
|
||||
def iterable_to_function(gen):
|
||||
gen = iter(gen)
|
||||
data = []
|
||||
def f(k):
|
||||
for i in xrange(len(data), k+1):
|
||||
data.append(next(gen))
|
||||
return data[k]
|
||||
return f
|
||||
|
||||
@defun
|
||||
def diffs_prod(ctx, factors):
|
||||
r"""
|
||||
Given a list of `N` iterables or generators yielding
|
||||
`f_k(x), f'_k(x), f''_k(x), \ldots` for `k = 1, \ldots, N`,
|
||||
generate `g(x), g'(x), g''(x), \ldots` where
|
||||
`g(x) = f_1(x) f_2(x) \cdots f_N(x)`.
|
||||
|
||||
At high precision and for large orders, this is typically more efficient
|
||||
than numerical differentiation if the derivatives of each `f_k(x)`
|
||||
admit direct computation.
|
||||
|
||||
Note: This function does not increase the working precision internally,
|
||||
so guard digits may have to be added externally for full accuracy.
|
||||
|
||||
**Examples**
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 15; mp.pretty = True
|
||||
>>> f = lambda x: exp(x)*cos(x)*sin(x)
|
||||
>>> u = diffs(f, 1)
|
||||
>>> v = mp.diffs_prod([diffs(exp,1), diffs(cos,1), diffs(sin,1)])
|
||||
>>> next(u); next(v)
|
||||
1.23586333600241
|
||||
1.23586333600241
|
||||
>>> next(u); next(v)
|
||||
0.104658952245596
|
||||
0.104658952245596
|
||||
>>> next(u); next(v)
|
||||
-5.96999877552086
|
||||
-5.96999877552086
|
||||
>>> next(u); next(v)
|
||||
-12.4632923122697
|
||||
-12.4632923122697
|
||||
|
||||
"""
|
||||
N = len(factors)
|
||||
if N == 1:
|
||||
for c in factors[0]:
|
||||
yield c
|
||||
else:
|
||||
u = iterable_to_function(ctx.diffs_prod(factors[:N//2]))
|
||||
v = iterable_to_function(ctx.diffs_prod(factors[N//2:]))
|
||||
n = 0
|
||||
while 1:
|
||||
#yield sum(binomial(n,k)*u(n-k)*v(k) for k in xrange(n+1))
|
||||
s = u(n) * v(0)
|
||||
a = 1
|
||||
for k in xrange(1,n+1):
|
||||
a = a * (n-k+1) // k
|
||||
s += a * u(n-k) * v(k)
|
||||
yield s
|
||||
n += 1
|
||||
|
||||
def dpoly(n, _cache={}):
|
||||
"""
|
||||
nth differentiation polynomial for exp (Faa di Bruno's formula).
|
||||
|
||||
TODO: most exponents are zero, so maybe a sparse representation
|
||||
would be better.
|
||||
"""
|
||||
if n in _cache:
|
||||
return _cache[n]
|
||||
if not _cache:
|
||||
_cache[0] = {(0,):1}
|
||||
R = dpoly(n-1)
|
||||
R = dict((c+(0,),v) for (c,v) in iteritems(R))
|
||||
Ra = {}
|
||||
for powers, count in iteritems(R):
|
||||
powers1 = (powers[0]+1,) + powers[1:]
|
||||
if powers1 in Ra:
|
||||
Ra[powers1] += count
|
||||
else:
|
||||
Ra[powers1] = count
|
||||
for powers, count in iteritems(R):
|
||||
if not sum(powers):
|
||||
continue
|
||||
for k,p in enumerate(powers):
|
||||
if p:
|
||||
powers2 = powers[:k] + (p-1,powers[k+1]+1) + powers[k+2:]
|
||||
if powers2 in Ra:
|
||||
Ra[powers2] += p*count
|
||||
else:
|
||||
Ra[powers2] = p*count
|
||||
_cache[n] = Ra
|
||||
return _cache[n]
|
||||
|
||||
@defun
|
||||
def diffs_exp(ctx, fdiffs):
|
||||
r"""
|
||||
Given an iterable or generator yielding `f(x), f'(x), f''(x), \ldots`
|
||||
generate `g(x), g'(x), g''(x), \ldots` where `g(x) = \exp(f(x))`.
|
||||
|
||||
At high precision and for large orders, this is typically more efficient
|
||||
than numerical differentiation if the derivatives of `f(x)`
|
||||
admit direct computation.
|
||||
|
||||
Note: This function does not increase the working precision internally,
|
||||
so guard digits may have to be added externally for full accuracy.
|
||||
|
||||
**Examples**
|
||||
|
||||
The derivatives of the gamma function can be computed using
|
||||
logarithmic differentiation::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 15; mp.pretty = True
|
||||
>>>
|
||||
>>> def diffs_loggamma(x):
|
||||
... yield loggamma(x)
|
||||
... i = 0
|
||||
... while 1:
|
||||
... yield psi(i,x)
|
||||
... i += 1
|
||||
...
|
||||
>>> u = diffs_exp(diffs_loggamma(3))
|
||||
>>> v = diffs(gamma, 3)
|
||||
>>> next(u); next(v)
|
||||
2.0
|
||||
2.0
|
||||
>>> next(u); next(v)
|
||||
1.84556867019693
|
||||
1.84556867019693
|
||||
>>> next(u); next(v)
|
||||
2.49292999190269
|
||||
2.49292999190269
|
||||
>>> next(u); next(v)
|
||||
3.44996501352367
|
||||
3.44996501352367
|
||||
|
||||
"""
|
||||
fn = iterable_to_function(fdiffs)
|
||||
f0 = ctx.exp(fn(0))
|
||||
yield f0
|
||||
i = 1
|
||||
while 1:
|
||||
s = ctx.mpf(0)
|
||||
for powers, c in iteritems(dpoly(i)):
|
||||
s += c*ctx.fprod(fn(k+1)**p for (k,p) in enumerate(powers) if p)
|
||||
yield s * f0
|
||||
i += 1
|
||||
|
||||
@defun
|
||||
def differint(ctx, f, x, n=1, x0=0):
|
||||
r"""
|
||||
Calculates the Riemann-Liouville differintegral, or fractional
|
||||
derivative, defined by
|
||||
|
||||
.. math ::
|
||||
|
||||
\,_{x_0}{\mathbb{D}}^n_xf(x) = \frac{1}{\Gamma(m-n)} \frac{d^m}{dx^m}
|
||||
\int_{x_0}^{x}(x-t)^{m-n-1}f(t)dt
|
||||
|
||||
where `f` is a given (presumably well-behaved) function,
|
||||
`x` is the evaluation point, `n` is the order, and `x_0` is
|
||||
the reference point of integration (`m` is an arbitrary
|
||||
parameter selected automatically).
|
||||
|
||||
With `n = 1`, this is just the standard derivative `f'(x)`; with `n = 2`,
|
||||
the second derivative `f''(x)`, etc. With `n = -1`, it gives
|
||||
`\int_{x_0}^x f(t) dt`, with `n = -2`
|
||||
it gives `\int_{x_0}^x \left( \int_{x_0}^t f(u) du \right) dt`, etc.
|
||||
|
||||
As `n` is permitted to be any number, this operator generalizes
|
||||
iterated differentiation and iterated integration to a single
|
||||
operator with a continuous order parameter.
|
||||
|
||||
**Examples**
|
||||
|
||||
There is an exact formula for the fractional derivative of a
|
||||
monomial `x^p`, which may be used as a reference. For example,
|
||||
the following gives a half-derivative (order 0.5)::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 15; mp.pretty = True
|
||||
>>> x = mpf(3); p = 2; n = 0.5
|
||||
>>> differint(lambda t: t**p, x, n)
|
||||
7.81764019044672
|
||||
>>> gamma(p+1)/gamma(p-n+1) * x**(p-n)
|
||||
7.81764019044672
|
||||
|
||||
Another useful test function is the exponential function, whose
|
||||
integration / differentiation formula easy generalizes
|
||||
to arbitrary order. Here we first compute a third derivative,
|
||||
and then a triply nested integral. (The reference point `x_0`
|
||||
is set to `-\infty` to avoid nonzero endpoint terms.)::
|
||||
|
||||
>>> differint(lambda x: exp(pi*x), -1.5, 3)
|
||||
0.278538406900792
|
||||
>>> exp(pi*-1.5) * pi**3
|
||||
0.278538406900792
|
||||
>>> differint(lambda x: exp(pi*x), 3.5, -3, -inf)
|
||||
1922.50563031149
|
||||
>>> exp(pi*3.5) / pi**3
|
||||
1922.50563031149
|
||||
|
||||
However, for noninteger `n`, the differentiation formula for the
|
||||
exponential function must be modified to give the same result as the
|
||||
Riemann-Liouville differintegral::
|
||||
|
||||
>>> x = mpf(3.5)
|
||||
>>> c = pi
|
||||
>>> n = 1+2*j
|
||||
>>> differint(lambda x: exp(c*x), x, n)
|
||||
(-123295.005390743 + 140955.117867654j)
|
||||
>>> x**(-n) * exp(c)**x * (x*c)**n * gammainc(-n, 0, x*c) / gamma(-n)
|
||||
(-123295.005390743 + 140955.117867654j)
|
||||
|
||||
|
||||
"""
|
||||
m = max(int(ctx.ceil(ctx.re(n)))+1, 1)
|
||||
r = m-n-1
|
||||
g = lambda x: ctx.quad(lambda t: (x-t)**r * f(t), [x0, x])
|
||||
return ctx.diff(g, x, m) / ctx.gamma(m-n)
|
||||
|
||||
@defun
|
||||
def diffun(ctx, f, n=1, **options):
|
||||
r"""
|
||||
Given a function `f`, returns a function `g(x)` that evaluates the nth
|
||||
derivative `f^{(n)}(x)`::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 15; mp.pretty = True
|
||||
>>> cos2 = diffun(sin)
|
||||
>>> sin2 = diffun(sin, 4)
|
||||
>>> cos(1.3), cos2(1.3)
|
||||
(0.267498828624587, 0.267498828624587)
|
||||
>>> sin(1.3), sin2(1.3)
|
||||
(0.963558185417193, 0.963558185417193)
|
||||
|
||||
The function `f` must support arbitrary precision evaluation.
|
||||
See :func:`~mpmath.diff` for additional details and supported
|
||||
keyword options.
|
||||
"""
|
||||
if n == 0:
|
||||
return f
|
||||
def g(x):
|
||||
return ctx.diff(f, x, n, **options)
|
||||
return g
|
||||
|
||||
@defun
|
||||
def taylor(ctx, f, x, n, **options):
|
||||
r"""
|
||||
Produces a degree-`n` Taylor polynomial around the point `x` of the
|
||||
given function `f`. The coefficients are returned as a list.
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 15; mp.pretty = True
|
||||
>>> nprint(chop(taylor(sin, 0, 5)))
|
||||
[0.0, 1.0, 0.0, -0.166667, 0.0, 0.00833333]
|
||||
|
||||
The coefficients are computed using high-order numerical
|
||||
differentiation. The function must be possible to evaluate
|
||||
to arbitrary precision. See :func:`~mpmath.diff` for additional details
|
||||
and supported keyword options.
|
||||
|
||||
Note that to evaluate the Taylor polynomial as an approximation
|
||||
of `f`, e.g. with :func:`~mpmath.polyval`, the coefficients must be reversed,
|
||||
and the point of the Taylor expansion must be subtracted from
|
||||
the argument:
|
||||
|
||||
>>> p = taylor(exp, 2.0, 10)
|
||||
>>> polyval(p[::-1], 2.5 - 2.0)
|
||||
12.1824939606092
|
||||
>>> exp(2.5)
|
||||
12.1824939607035
|
||||
|
||||
"""
|
||||
gen = enumerate(ctx.diffs(f, x, n, **options))
|
||||
if options.get("chop", True):
|
||||
return [ctx.chop(d)/ctx.factorial(i) for i, d in gen]
|
||||
else:
|
||||
return [d/ctx.factorial(i) for i, d in gen]
|
||||
|
||||
@defun
|
||||
def pade(ctx, a, L, M):
|
||||
r"""
|
||||
Computes a Pade approximation of degree `(L, M)` to a function.
|
||||
Given at least `L+M+1` Taylor coefficients `a` approximating
|
||||
a function `A(x)`, :func:`~mpmath.pade` returns coefficients of
|
||||
polynomials `P, Q` satisfying
|
||||
|
||||
.. math ::
|
||||
|
||||
P = \sum_{k=0}^L p_k x^k
|
||||
|
||||
Q = \sum_{k=0}^M q_k x^k
|
||||
|
||||
Q_0 = 1
|
||||
|
||||
A(x) Q(x) = P(x) + O(x^{L+M+1})
|
||||
|
||||
`P(x)/Q(x)` can provide a good approximation to an analytic function
|
||||
beyond the radius of convergence of its Taylor series (example
|
||||
from G.A. Baker 'Essentials of Pade Approximants' Academic Press,
|
||||
Ch.1A)::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 15; mp.pretty = True
|
||||
>>> one = mpf(1)
|
||||
>>> def f(x):
|
||||
... return sqrt((one + 2*x)/(one + x))
|
||||
...
|
||||
>>> a = taylor(f, 0, 6)
|
||||
>>> p, q = pade(a, 3, 3)
|
||||
>>> x = 10
|
||||
>>> polyval(p[::-1], x)/polyval(q[::-1], x)
|
||||
1.38169105566806
|
||||
>>> f(x)
|
||||
1.38169855941551
|
||||
|
||||
"""
|
||||
# To determine L+1 coefficients of P and M coefficients of Q
|
||||
# L+M+1 coefficients of A must be provided
|
||||
if len(a) < L+M+1:
|
||||
raise ValueError("L+M+1 Coefficients should be provided")
|
||||
|
||||
if M == 0:
|
||||
if L == 0:
|
||||
return [ctx.one], [ctx.one]
|
||||
else:
|
||||
return a[:L+1], [ctx.one]
|
||||
|
||||
# Solve first
|
||||
# a[L]*q[1] + ... + a[L-M+1]*q[M] = -a[L+1]
|
||||
# ...
|
||||
# a[L+M-1]*q[1] + ... + a[L]*q[M] = -a[L+M]
|
||||
A = ctx.matrix(M)
|
||||
for j in range(M):
|
||||
for i in range(min(M, L+j+1)):
|
||||
A[j, i] = a[L+j-i]
|
||||
v = -ctx.matrix(a[(L+1):(L+M+1)])
|
||||
x = ctx.lu_solve(A, v)
|
||||
q = [ctx.one] + list(x)
|
||||
# compute p
|
||||
p = [0]*(L+1)
|
||||
for i in range(L+1):
|
||||
s = a[i]
|
||||
for j in range(1, min(M,i) + 1):
|
||||
s += q[j]*a[i-j]
|
||||
p[i] = s
|
||||
return p, q
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,973 @@
|
||||
# contributed to mpmath by Kristopher L. Kuhlman, February 2017
|
||||
# contributed to mpmath by Guillermo Navas-Palencia, February 2022
|
||||
|
||||
class InverseLaplaceTransform(object):
|
||||
r"""
|
||||
Inverse Laplace transform methods are implemented using this
|
||||
class, in order to simplify the code and provide a common
|
||||
infrastructure.
|
||||
|
||||
Implement a custom inverse Laplace transform algorithm by
|
||||
subclassing :class:`InverseLaplaceTransform` and implementing the
|
||||
appropriate methods. The subclass can then be used by
|
||||
:func:`~mpmath.invertlaplace` by passing it as the *method*
|
||||
argument.
|
||||
"""
|
||||
|
||||
def __init__(self, ctx):
|
||||
self.ctx = ctx
|
||||
|
||||
def calc_laplace_parameter(self, t, **kwargs):
|
||||
r"""
|
||||
Determine the vector of Laplace parameter values needed for an
|
||||
algorithm, this will depend on the choice of algorithm (de
|
||||
Hoog is default), the algorithm-specific parameters passed (or
|
||||
default ones), and desired time.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def calc_time_domain_solution(self, fp):
|
||||
r"""
|
||||
Compute the time domain solution, after computing the
|
||||
Laplace-space function evaluations at the abscissa required
|
||||
for the algorithm. Abscissa computed for one algorithm are
|
||||
typically not useful for another algorithm.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class FixedTalbot(InverseLaplaceTransform):
|
||||
|
||||
def calc_laplace_parameter(self, t, **kwargs):
|
||||
r"""The "fixed" Talbot method deforms the Bromwich contour towards
|
||||
`-\infty` in the shape of a parabola. Traditionally the Talbot
|
||||
algorithm has adjustable parameters, but the "fixed" version
|
||||
does not. The `r` parameter could be passed in as a parameter,
|
||||
if you want to override the default given by (Abate & Valko,
|
||||
2004).
|
||||
|
||||
The Laplace parameter is sampled along a parabola opening
|
||||
along the negative imaginary axis, with the base of the
|
||||
parabola along the real axis at
|
||||
`p=\frac{r}{t_\mathrm{max}}`. As the number of terms used in
|
||||
the approximation (degree) grows, the abscissa required for
|
||||
function evaluation tend towards `-\infty`, requiring high
|
||||
precision to prevent overflow. If any poles, branch cuts or
|
||||
other singularities exist such that the deformed Bromwich
|
||||
contour lies to the left of the singularity, the method will
|
||||
fail.
|
||||
|
||||
**Optional arguments**
|
||||
|
||||
:class:`~mpmath.calculus.inverselaplace.FixedTalbot.calc_laplace_parameter`
|
||||
recognizes the following keywords
|
||||
|
||||
*tmax*
|
||||
maximum time associated with vector of times
|
||||
(typically just the time requested)
|
||||
*degree*
|
||||
integer order of approximation (M = number of terms)
|
||||
*r*
|
||||
abscissa for `p_0` (otherwise computed using rule
|
||||
of thumb `2M/5`)
|
||||
|
||||
The working precision will be increased according to a rule of
|
||||
thumb. If 'degree' is not specified, the working precision and
|
||||
degree are chosen to hopefully achieve the dps of the calling
|
||||
context. If 'degree' is specified, the working precision is
|
||||
chosen to achieve maximum resulting precision for the
|
||||
specified degree.
|
||||
|
||||
.. math ::
|
||||
|
||||
p_0=\frac{r}{t}
|
||||
|
||||
.. math ::
|
||||
|
||||
p_i=\frac{i r \pi}{Mt_\mathrm{max}}\left[\cot\left(
|
||||
\frac{i\pi}{M}\right) + j \right] \qquad 1\le i <M
|
||||
|
||||
where `j=\sqrt{-1}`, `r=2M/5`, and `t_\mathrm{max}` is the
|
||||
maximum specified time.
|
||||
|
||||
"""
|
||||
|
||||
# required
|
||||
# ------------------------------
|
||||
# time of desired approximation
|
||||
self.t = self.ctx.convert(t)
|
||||
|
||||
# optional
|
||||
# ------------------------------
|
||||
# maximum time desired (used for scaling) default is requested
|
||||
# time.
|
||||
self.tmax = self.ctx.convert(kwargs.get('tmax', self.t))
|
||||
|
||||
# empirical relationships used here based on a linear fit of
|
||||
# requested and delivered dps for exponentially decaying time
|
||||
# functions for requested dps up to 512.
|
||||
|
||||
if 'degree' in kwargs:
|
||||
self.degree = kwargs['degree']
|
||||
self.dps_goal = self.degree
|
||||
else:
|
||||
self.dps_goal = int(1.72*self.ctx.dps)
|
||||
self.degree = max(12, int(1.38*self.dps_goal))
|
||||
|
||||
M = self.degree
|
||||
|
||||
# this is adjusting the dps of the calling context hopefully
|
||||
# the caller doesn't monkey around with it between calling
|
||||
# this routine and calc_time_domain_solution()
|
||||
self.dps_orig = self.ctx.dps
|
||||
self.ctx.dps = self.dps_goal
|
||||
|
||||
# Abate & Valko rule of thumb for r parameter
|
||||
self.r = kwargs.get('r', self.ctx.fraction(2, 5)*M)
|
||||
|
||||
self.theta = self.ctx.linspace(0.0, self.ctx.pi, M+1)
|
||||
|
||||
self.cot_theta = self.ctx.matrix(M, 1)
|
||||
self.cot_theta[0] = 0 # not used
|
||||
|
||||
# all but time-dependent part of p
|
||||
self.delta = self.ctx.matrix(M, 1)
|
||||
self.delta[0] = self.r
|
||||
|
||||
for i in range(1, M):
|
||||
self.cot_theta[i] = self.ctx.cot(self.theta[i])
|
||||
self.delta[i] = self.r*self.theta[i]*(self.cot_theta[i] + 1j)
|
||||
|
||||
self.p = self.ctx.matrix(M, 1)
|
||||
self.p = self.delta/self.tmax
|
||||
|
||||
# NB: p is complex (mpc)
|
||||
|
||||
def calc_time_domain_solution(self, fp, t, manual_prec=False):
|
||||
r"""The fixed Talbot time-domain solution is computed from the
|
||||
Laplace-space function evaluations using
|
||||
|
||||
.. math ::
|
||||
|
||||
f(t,M)=\frac{2}{5t}\sum_{k=0}^{M-1}\Re \left[
|
||||
\gamma_k \bar{f}(p_k)\right]
|
||||
|
||||
where
|
||||
|
||||
.. math ::
|
||||
|
||||
\gamma_0 = \frac{1}{2}e^{r}\bar{f}(p_0)
|
||||
|
||||
.. math ::
|
||||
|
||||
\gamma_k = e^{tp_k}\left\lbrace 1 + \frac{jk\pi}{M}\left[1 +
|
||||
\cot \left( \frac{k \pi}{M} \right)^2 \right] - j\cot\left(
|
||||
\frac{k \pi}{M}\right)\right \rbrace \qquad 1\le k<M.
|
||||
|
||||
Again, `j=\sqrt{-1}`.
|
||||
|
||||
Before calling this function, call
|
||||
:class:`~mpmath.calculus.inverselaplace.FixedTalbot.calc_laplace_parameter`
|
||||
to set the parameters and compute the required coefficients.
|
||||
|
||||
**References**
|
||||
|
||||
1. Abate, J., P. Valko (2004). Multi-precision Laplace
|
||||
transform inversion. *International Journal for Numerical
|
||||
Methods in Engineering* 60:979-993,
|
||||
http://dx.doi.org/10.1002/nme.995
|
||||
2. Talbot, A. (1979). The accurate numerical inversion of
|
||||
Laplace transforms. *IMA Journal of Applied Mathematics*
|
||||
23(1):97, http://dx.doi.org/10.1093/imamat/23.1.97
|
||||
"""
|
||||
|
||||
# required
|
||||
# ------------------------------
|
||||
self.t = self.ctx.convert(t)
|
||||
|
||||
# assume fp was computed from p matrix returned from
|
||||
# calc_laplace_parameter(), so is already a list or matrix of
|
||||
# mpmath 'mpc' types
|
||||
|
||||
# these were computed in previous call to
|
||||
# calc_laplace_parameter()
|
||||
theta = self.theta
|
||||
delta = self.delta
|
||||
M = self.degree
|
||||
p = self.p
|
||||
r = self.r
|
||||
|
||||
ans = self.ctx.matrix(M, 1)
|
||||
ans[0] = self.ctx.exp(delta[0])*fp[0]/2
|
||||
|
||||
for i in range(1, M):
|
||||
ans[i] = self.ctx.exp(delta[i])*fp[i]*(
|
||||
1 + 1j*theta[i]*(1 + self.cot_theta[i]**2) -
|
||||
1j*self.cot_theta[i])
|
||||
|
||||
result = self.ctx.fraction(2, 5)*self.ctx.fsum(ans)/self.t
|
||||
|
||||
# setting dps back to value when calc_laplace_parameter was
|
||||
# called, unless flag is set.
|
||||
if not manual_prec:
|
||||
self.ctx.dps = self.dps_orig
|
||||
|
||||
return result.real
|
||||
|
||||
|
||||
# ****************************************
|
||||
|
||||
class Stehfest(InverseLaplaceTransform):
|
||||
|
||||
def calc_laplace_parameter(self, t, **kwargs):
|
||||
r"""
|
||||
The Gaver-Stehfest method is a discrete approximation of the
|
||||
Widder-Post inversion algorithm, rather than a direct
|
||||
approximation of the Bromwich contour integral.
|
||||
|
||||
The method abscissa along the real axis, and therefore has
|
||||
issues inverting oscillatory functions (which have poles in
|
||||
pairs away from the real axis).
|
||||
|
||||
The working precision will be increased according to a rule of
|
||||
thumb. If 'degree' is not specified, the working precision and
|
||||
degree are chosen to hopefully achieve the dps of the calling
|
||||
context. If 'degree' is specified, the working precision is
|
||||
chosen to achieve maximum resulting precision for the
|
||||
specified degree.
|
||||
|
||||
.. math ::
|
||||
|
||||
p_k = \frac{k \log 2}{t} \qquad 1 \le k \le M
|
||||
"""
|
||||
|
||||
# required
|
||||
# ------------------------------
|
||||
# time of desired approximation
|
||||
self.t = self.ctx.convert(t)
|
||||
|
||||
# optional
|
||||
# ------------------------------
|
||||
|
||||
# empirical relationships used here based on a linear fit of
|
||||
# requested and delivered dps for exponentially decaying time
|
||||
# functions for requested dps up to 512.
|
||||
|
||||
if 'degree' in kwargs:
|
||||
self.degree = kwargs['degree']
|
||||
self.dps_goal = int(1.38*self.degree)
|
||||
else:
|
||||
self.dps_goal = int(2.93*self.ctx.dps)
|
||||
self.degree = max(16, self.dps_goal)
|
||||
|
||||
# _coeff routine requires even degree
|
||||
if self.degree % 2 > 0:
|
||||
self.degree += 1
|
||||
|
||||
M = self.degree
|
||||
|
||||
# this is adjusting the dps of the calling context
|
||||
# hopefully the caller doesn't monkey around with it
|
||||
# between calling this routine and calc_time_domain_solution()
|
||||
self.dps_orig = self.ctx.dps
|
||||
self.ctx.dps = self.dps_goal
|
||||
|
||||
self.V = self._coeff()
|
||||
self.p = self.ctx.matrix(self.ctx.arange(1, M+1))*self.ctx.ln2/self.t
|
||||
|
||||
# NB: p is real (mpf)
|
||||
|
||||
def _coeff(self):
|
||||
r"""Salzer summation weights (aka, "Stehfest coefficients")
|
||||
only depend on the approximation order (M) and the precision"""
|
||||
|
||||
M = self.degree
|
||||
M2 = int(M/2) # checked earlier that M is even
|
||||
|
||||
V = self.ctx.matrix(M, 1)
|
||||
|
||||
# Salzer summation weights
|
||||
# get very large in magnitude and oscillate in sign,
|
||||
# if the precision is not high enough, there will be
|
||||
# catastrophic cancellation
|
||||
for k in range(1, M+1):
|
||||
z = self.ctx.matrix(min(k, M2)+1, 1)
|
||||
for j in range(int((k+1)/2), min(k, M2)+1):
|
||||
z[j] = (self.ctx.power(j, M2)*self.ctx.fac(2*j)/
|
||||
(self.ctx.fac(M2-j)*self.ctx.fac(j)*
|
||||
self.ctx.fac(j-1)*self.ctx.fac(k-j)*
|
||||
self.ctx.fac(2*j-k)))
|
||||
V[k-1] = self.ctx.power(-1, k+M2)*self.ctx.fsum(z)
|
||||
|
||||
return V
|
||||
|
||||
def calc_time_domain_solution(self, fp, t, manual_prec=False):
|
||||
r"""Compute time-domain Stehfest algorithm solution.
|
||||
|
||||
.. math ::
|
||||
|
||||
f(t,M) = \frac{\log 2}{t} \sum_{k=1}^{M} V_k \bar{f}\left(
|
||||
p_k \right)
|
||||
|
||||
where
|
||||
|
||||
.. math ::
|
||||
|
||||
V_k = (-1)^{k + N/2} \sum^{\min(k,N/2)}_{i=\lfloor(k+1)/2 \rfloor}
|
||||
\frac{i^{\frac{N}{2}}(2i)!}{\left(\frac{N}{2}-i \right)! \, i! \,
|
||||
\left(i-1 \right)! \, \left(k-i\right)! \, \left(2i-k \right)!}
|
||||
|
||||
As the degree increases, the abscissa (`p_k`) only increase
|
||||
linearly towards `\infty`, but the Stehfest coefficients
|
||||
(`V_k`) alternate in sign and increase rapidly in sign,
|
||||
requiring high precision to prevent overflow or loss of
|
||||
significance when evaluating the sum.
|
||||
|
||||
**References**
|
||||
|
||||
1. Widder, D. (1941). *The Laplace Transform*. Princeton.
|
||||
2. Stehfest, H. (1970). Algorithm 368: numerical inversion of
|
||||
Laplace transforms. *Communications of the ACM* 13(1):47-49,
|
||||
http://dx.doi.org/10.1145/361953.361969
|
||||
|
||||
"""
|
||||
|
||||
# required
|
||||
self.t = self.ctx.convert(t)
|
||||
|
||||
# assume fp was computed from p matrix returned from
|
||||
# calc_laplace_parameter(), so is already
|
||||
# a list or matrix of mpmath 'mpf' types
|
||||
|
||||
result = self.ctx.fdot(self.V, fp)*self.ctx.ln2/self.t
|
||||
|
||||
# setting dps back to value when calc_laplace_parameter was called
|
||||
if not manual_prec:
|
||||
self.ctx.dps = self.dps_orig
|
||||
|
||||
# ignore any small imaginary part
|
||||
return result.real
|
||||
|
||||
|
||||
# ****************************************
|
||||
|
||||
class deHoog(InverseLaplaceTransform):
|
||||
|
||||
def calc_laplace_parameter(self, t, **kwargs):
|
||||
r"""the de Hoog, Knight & Stokes algorithm is an
|
||||
accelerated form of the Fourier series numerical
|
||||
inverse Laplace transform algorithms.
|
||||
|
||||
.. math ::
|
||||
|
||||
p_k = \gamma + \frac{jk}{T} \qquad 0 \le k < 2M+1
|
||||
|
||||
where
|
||||
|
||||
.. math ::
|
||||
|
||||
\gamma = \alpha - \frac{\log \mathrm{tol}}{2T},
|
||||
|
||||
`j=\sqrt{-1}`, `T = 2t_\mathrm{max}` is a scaled time,
|
||||
`\alpha=10^{-\mathrm{dps\_goal}}` is the real part of the
|
||||
rightmost pole or singularity, which is chosen based on the
|
||||
desired accuracy (assuming the rightmost singularity is 0),
|
||||
and `\mathrm{tol}=10\alpha` is the desired tolerance, which is
|
||||
chosen in relation to `\alpha`.`
|
||||
|
||||
When increasing the degree, the abscissa increase towards
|
||||
`j\infty`, but more slowly than the fixed Talbot
|
||||
algorithm. The de Hoog et al. algorithm typically does better
|
||||
with oscillatory functions of time, and less well-behaved
|
||||
functions. The method tends to be slower than the Talbot and
|
||||
Stehfest algorithsm, especially so at very high precision
|
||||
(e.g., `>500` digits precision).
|
||||
|
||||
"""
|
||||
|
||||
# required
|
||||
# ------------------------------
|
||||
self.t = self.ctx.convert(t)
|
||||
|
||||
# optional
|
||||
# ------------------------------
|
||||
self.tmax = kwargs.get('tmax', self.t)
|
||||
|
||||
# empirical relationships used here based on a linear fit of
|
||||
# requested and delivered dps for exponentially decaying time
|
||||
# functions for requested dps up to 512.
|
||||
|
||||
if 'degree' in kwargs:
|
||||
self.degree = kwargs['degree']
|
||||
self.dps_goal = int(1.38*self.degree)
|
||||
else:
|
||||
self.dps_goal = int(self.ctx.dps*1.36)
|
||||
self.degree = max(10, self.dps_goal)
|
||||
|
||||
# 2*M+1 terms in approximation
|
||||
M = self.degree
|
||||
|
||||
# adjust alpha component of abscissa of convergence for higher
|
||||
# precision
|
||||
tmp = self.ctx.power(10.0, -self.dps_goal)
|
||||
self.alpha = self.ctx.convert(kwargs.get('alpha', tmp))
|
||||
|
||||
# desired tolerance (here simply related to alpha)
|
||||
self.tol = self.ctx.convert(kwargs.get('tol', self.alpha*10.0))
|
||||
self.np = 2*self.degree+1 # number of terms in approximation
|
||||
|
||||
# this is adjusting the dps of the calling context
|
||||
# hopefully the caller doesn't monkey around with it
|
||||
# between calling this routine and calc_time_domain_solution()
|
||||
self.dps_orig = self.ctx.dps
|
||||
self.ctx.dps = self.dps_goal
|
||||
|
||||
# scaling factor (likely tun-able, but 2 is typical)
|
||||
self.scale = kwargs.get('scale', 2)
|
||||
self.T = self.ctx.convert(kwargs.get('T', self.scale*self.tmax))
|
||||
|
||||
self.p = self.ctx.matrix(2*M+1, 1)
|
||||
self.gamma = self.alpha - self.ctx.log(self.tol)/(self.scale*self.T)
|
||||
self.p = (self.gamma + self.ctx.pi*
|
||||
self.ctx.matrix(self.ctx.arange(self.np))/self.T*1j)
|
||||
|
||||
# NB: p is complex (mpc)
|
||||
|
||||
def calc_time_domain_solution(self, fp, t, manual_prec=False):
|
||||
r"""Calculate time-domain solution for
|
||||
de Hoog, Knight & Stokes algorithm.
|
||||
|
||||
The un-accelerated Fourier series approach is:
|
||||
|
||||
.. math ::
|
||||
|
||||
f(t,2M+1) = \frac{e^{\gamma t}}{T} \sum_{k=0}^{2M}{}^{'}
|
||||
\Re\left[\bar{f}\left( p_k \right)
|
||||
e^{i\pi t/T} \right],
|
||||
|
||||
where the prime on the summation indicates the first term is halved.
|
||||
|
||||
This simplistic approach requires so many function evaluations
|
||||
that it is not practical. Non-linear acceleration is
|
||||
accomplished via Pade-approximation and an analytic expression
|
||||
for the remainder of the continued fraction. See the original
|
||||
paper (reference 2 below) a detailed description of the
|
||||
numerical approach.
|
||||
|
||||
**References**
|
||||
|
||||
1. Davies, B. (2005). *Integral Transforms and their
|
||||
Applications*, Third Edition. Springer.
|
||||
2. de Hoog, F., J. Knight, A. Stokes (1982). An improved
|
||||
method for numerical inversion of Laplace transforms. *SIAM
|
||||
Journal of Scientific and Statistical Computing* 3:357-366,
|
||||
http://dx.doi.org/10.1137/0903022
|
||||
|
||||
"""
|
||||
|
||||
M = self.degree
|
||||
np = self.np
|
||||
T = self.T
|
||||
|
||||
self.t = self.ctx.convert(t)
|
||||
|
||||
# would it be useful to try re-using
|
||||
# space between e&q and A&B?
|
||||
e = self.ctx.zeros(np, M+1)
|
||||
q = self.ctx.matrix(2*M, M)
|
||||
d = self.ctx.matrix(np, 1)
|
||||
A = self.ctx.zeros(np+1, 1)
|
||||
B = self.ctx.ones(np+1, 1)
|
||||
|
||||
# initialize Q-D table
|
||||
e[:, 0] = 0.0 + 0j
|
||||
q[0, 0] = fp[1]/(fp[0]/2)
|
||||
for i in range(1, 2*M):
|
||||
q[i, 0] = fp[i+1]/fp[i]
|
||||
|
||||
# rhombus rule for filling triangular Q-D table (e & q)
|
||||
for r in range(1, M+1):
|
||||
# start with e, column 1, 0:2*M-2
|
||||
mr = 2*(M-r) + 1
|
||||
e[0:mr, r] = q[1:mr+1, r-1] - q[0:mr, r-1] + e[1:mr+1, r-1]
|
||||
if not r == M:
|
||||
rq = r+1
|
||||
mr = 2*(M-rq)+1 + 2
|
||||
for i in range(mr):
|
||||
q[i, rq-1] = q[i+1, rq-2]*e[i+1, rq-1]/e[i, rq-1]
|
||||
|
||||
# build up continued fraction coefficients (d)
|
||||
d[0] = fp[0]/2
|
||||
for r in range(1, M+1):
|
||||
d[2*r-1] = -q[0, r-1] # even terms
|
||||
d[2*r] = -e[0, r] # odd terms
|
||||
|
||||
# seed A and B for recurrence
|
||||
A[0] = 0.0 + 0.0j
|
||||
A[1] = d[0]
|
||||
B[0:2] = 1.0 + 0.0j
|
||||
|
||||
# base of the power series
|
||||
z = self.ctx.expjpi(self.t/T) # i*pi is already in fcn
|
||||
|
||||
# coefficients of Pade approximation (A & B)
|
||||
# using recurrence for all but last term
|
||||
for i in range(1, 2*M):
|
||||
A[i+1] = A[i] + d[i]*A[i-1]*z
|
||||
B[i+1] = B[i] + d[i]*B[i-1]*z
|
||||
|
||||
# "improved remainder" to continued fraction
|
||||
brem = (1 + (d[2*M-1] - d[2*M])*z)/2
|
||||
# powm1(x,y) computes x^y - 1 more accurately near zero
|
||||
rem = brem*self.ctx.powm1(1 + d[2*M]*z/brem,
|
||||
self.ctx.fraction(1, 2))
|
||||
|
||||
# last term of recurrence using new remainder
|
||||
A[np] = A[2*M] + rem*A[2*M-1]
|
||||
B[np] = B[2*M] + rem*B[2*M-1]
|
||||
|
||||
# diagonal Pade approximation
|
||||
# F=A/B represents accelerated trapezoid rule
|
||||
result = self.ctx.exp(self.gamma*self.t)/T*(A[np]/B[np]).real
|
||||
|
||||
# setting dps back to value when calc_laplace_parameter was called
|
||||
if not manual_prec:
|
||||
self.ctx.dps = self.dps_orig
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# ****************************************
|
||||
|
||||
class Cohen(InverseLaplaceTransform):
|
||||
|
||||
def calc_laplace_parameter(self, t, **kwargs):
|
||||
r"""The Cohen algorithm accelerates the convergence of the nearly
|
||||
alternating series resulting from the application of the trapezoidal
|
||||
rule to the Bromwich contour inversion integral.
|
||||
|
||||
.. math ::
|
||||
|
||||
p_k = \frac{\gamma}{2 t} + \frac{\pi i k}{t} \qquad 0 \le k < M
|
||||
|
||||
where
|
||||
|
||||
.. math ::
|
||||
|
||||
\gamma = \frac{2}{3} (d + \log(10) + \log(2 t)),
|
||||
|
||||
`d = \mathrm{dps\_goal}`, which is chosen based on the desired
|
||||
accuracy using the method developed in [1] to improve numerical
|
||||
stability. The Cohen algorithm shows robustness similar to the de Hoog
|
||||
et al. algorithm, but it is faster than the fixed Talbot algorithm.
|
||||
|
||||
**Optional arguments**
|
||||
|
||||
*degree*
|
||||
integer order of the approximation (M = number of terms)
|
||||
*alpha*
|
||||
abscissa for `p_0` (controls the discretization error)
|
||||
|
||||
The working precision will be increased according to a rule of
|
||||
thumb. If 'degree' is not specified, the working precision and
|
||||
degree are chosen to hopefully achieve the dps of the calling
|
||||
context. If 'degree' is specified, the working precision is
|
||||
chosen to achieve maximum resulting precision for the
|
||||
specified degree.
|
||||
|
||||
**References**
|
||||
|
||||
1. P. Glasserman, J. Ruiz-Mata (2006). Computing the credit loss
|
||||
distribution in the Gaussian copula model: a comparison of methods.
|
||||
*Journal of Credit Risk* 2(4):33-66, 10.21314/JCR.2006.057
|
||||
|
||||
"""
|
||||
self.t = self.ctx.convert(t)
|
||||
|
||||
if 'degree' in kwargs:
|
||||
self.degree = kwargs['degree']
|
||||
self.dps_goal = int(1.5 * self.degree)
|
||||
else:
|
||||
self.dps_goal = int(self.ctx.dps * 1.74)
|
||||
self.degree = max(22, int(1.31 * self.dps_goal))
|
||||
|
||||
M = self.degree + 1
|
||||
|
||||
# this is adjusting the dps of the calling context hopefully
|
||||
# the caller doesn't monkey around with it between calling
|
||||
# this routine and calc_time_domain_solution()
|
||||
self.dps_orig = self.ctx.dps
|
||||
self.ctx.dps = self.dps_goal
|
||||
|
||||
ttwo = 2 * self.t
|
||||
tmp = self.ctx.dps * self.ctx.log(10) + self.ctx.log(ttwo)
|
||||
tmp = self.ctx.fraction(2, 3) * tmp
|
||||
self.alpha = self.ctx.convert(kwargs.get('alpha', tmp))
|
||||
|
||||
# all but time-dependent part of p
|
||||
a_t = self.alpha / ttwo
|
||||
p_t = self.ctx.pi * 1j / self.t
|
||||
|
||||
self.p = self.ctx.matrix(M, 1)
|
||||
self.p[0] = a_t
|
||||
|
||||
for i in range(1, M):
|
||||
self.p[i] = a_t + i * p_t
|
||||
|
||||
def calc_time_domain_solution(self, fp, t, manual_prec=False):
|
||||
r"""Calculate time-domain solution for Cohen algorithm.
|
||||
|
||||
The accelerated nearly alternating series is:
|
||||
|
||||
.. math ::
|
||||
|
||||
f(t, M) = \frac{e^{\gamma / 2}}{t} \left[\frac{1}{2}
|
||||
\Re\left(\bar{f}\left(\frac{\gamma}{2t}\right) \right) -
|
||||
\sum_{k=0}^{M-1}\frac{c_{M,k}}{d_M}\Re\left(\bar{f}
|
||||
\left(\frac{\gamma + 2(k+1) \pi i}{2t}\right)\right)\right],
|
||||
|
||||
where coefficients `\frac{c_{M, k}}{d_M}` are described in [1].
|
||||
|
||||
1. H. Cohen, F. Rodriguez Villegas, D. Zagier (2000). Convergence
|
||||
acceleration of alternating series. *Experiment. Math* 9(1):3-12
|
||||
|
||||
"""
|
||||
self.t = self.ctx.convert(t)
|
||||
|
||||
n = self.degree
|
||||
M = n + 1
|
||||
|
||||
A = self.ctx.matrix(M, 1)
|
||||
for i in range(M):
|
||||
A[i] = fp[i].real
|
||||
|
||||
d = (3 + self.ctx.sqrt(8)) ** n
|
||||
d = (d + 1 / d) / 2
|
||||
b = -self.ctx.one
|
||||
c = -d
|
||||
s = 0
|
||||
|
||||
for k in range(n):
|
||||
c = b - c
|
||||
s = s + c * A[k + 1]
|
||||
b = 2 * (k + n) * (k - n) * b / ((2 * k + 1) * (k + self.ctx.one))
|
||||
|
||||
result = self.ctx.exp(self.alpha / 2) / self.t * (A[0] / 2 - s / d)
|
||||
|
||||
# setting dps back to value when calc_laplace_parameter was
|
||||
# called, unless flag is set.
|
||||
if not manual_prec:
|
||||
self.ctx.dps = self.dps_orig
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# ****************************************
|
||||
|
||||
class LaplaceTransformInversionMethods(object):
|
||||
def __init__(ctx, *args, **kwargs):
|
||||
ctx._fixed_talbot = FixedTalbot(ctx)
|
||||
ctx._stehfest = Stehfest(ctx)
|
||||
ctx._de_hoog = deHoog(ctx)
|
||||
ctx._cohen = Cohen(ctx)
|
||||
|
||||
def invertlaplace(ctx, f, t, **kwargs):
|
||||
r"""Computes the numerical inverse Laplace transform for a
|
||||
Laplace-space function at a given time. The function being
|
||||
evaluated is assumed to be a real-valued function of time.
|
||||
|
||||
The user must supply a Laplace-space function `\bar{f}(p)`,
|
||||
and a desired time at which to estimate the time-domain
|
||||
solution `f(t)`.
|
||||
|
||||
A few basic examples of Laplace-space functions with known
|
||||
inverses (see references [1,2]) :
|
||||
|
||||
.. math ::
|
||||
|
||||
\mathcal{L}\left\lbrace f(t) \right\rbrace=\bar{f}(p)
|
||||
|
||||
.. math ::
|
||||
|
||||
\mathcal{L}^{-1}\left\lbrace \bar{f}(p) \right\rbrace = f(t)
|
||||
|
||||
.. math ::
|
||||
|
||||
\bar{f}(p) = \frac{1}{(p+1)^2}
|
||||
|
||||
.. math ::
|
||||
|
||||
f(t) = t e^{-t}
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 15; mp.pretty = True
|
||||
>>> tt = [0.001, 0.01, 0.1, 1, 10]
|
||||
>>> fp = lambda p: 1/(p+1)**2
|
||||
>>> ft = lambda t: t*exp(-t)
|
||||
>>> ft(tt[0]),ft(tt[0])-invertlaplace(fp,tt[0],method='talbot')
|
||||
(0.000999000499833375, 8.57923043561212e-20)
|
||||
>>> ft(tt[1]),ft(tt[1])-invertlaplace(fp,tt[1],method='talbot')
|
||||
(0.00990049833749168, 3.27007646698047e-19)
|
||||
>>> ft(tt[2]),ft(tt[2])-invertlaplace(fp,tt[2],method='talbot')
|
||||
(0.090483741803596, -1.75215800052168e-18)
|
||||
>>> ft(tt[3]),ft(tt[3])-invertlaplace(fp,tt[3],method='talbot')
|
||||
(0.367879441171442, 1.2428864009344e-17)
|
||||
>>> ft(tt[4]),ft(tt[4])-invertlaplace(fp,tt[4],method='talbot')
|
||||
(0.000453999297624849, 4.04513489306658e-20)
|
||||
|
||||
The methods also work for higher precision:
|
||||
|
||||
>>> mp.dps = 100; mp.pretty = True
|
||||
>>> nstr(ft(tt[0]),15),nstr(ft(tt[0])-invertlaplace(fp,tt[0],method='talbot'),15)
|
||||
('0.000999000499833375', '-4.96868310693356e-105')
|
||||
>>> nstr(ft(tt[1]),15),nstr(ft(tt[1])-invertlaplace(fp,tt[1],method='talbot'),15)
|
||||
('0.00990049833749168', '1.23032291513122e-104')
|
||||
|
||||
.. math ::
|
||||
|
||||
\bar{f}(p) = \frac{1}{p^2+1}
|
||||
|
||||
.. math ::
|
||||
|
||||
f(t) = \mathrm{J}_0(t)
|
||||
|
||||
>>> mp.dps = 15; mp.pretty = True
|
||||
>>> fp = lambda p: 1/sqrt(p*p + 1)
|
||||
>>> ft = lambda t: besselj(0,t)
|
||||
>>> ft(tt[0]),ft(tt[0])-invertlaplace(fp,tt[0],method='dehoog')
|
||||
(0.999999750000016, -6.09717765032273e-18)
|
||||
>>> ft(tt[1]),ft(tt[1])-invertlaplace(fp,tt[1],method='dehoog')
|
||||
(0.99997500015625, -5.61756281076169e-17)
|
||||
|
||||
.. math ::
|
||||
|
||||
\bar{f}(p) = \frac{\log p}{p}
|
||||
|
||||
.. math ::
|
||||
|
||||
f(t) = -\gamma -\log t
|
||||
|
||||
>>> mp.dps = 15; mp.pretty = True
|
||||
>>> fp = lambda p: log(p)/p
|
||||
>>> ft = lambda t: -euler-log(t)
|
||||
>>> ft(tt[0]),ft(tt[0])-invertlaplace(fp,tt[0],method='stehfest')
|
||||
(6.3305396140806, -1.92126634837863e-16)
|
||||
>>> ft(tt[1]),ft(tt[1])-invertlaplace(fp,tt[1],method='stehfest')
|
||||
(4.02795452108656, -4.81486093200704e-16)
|
||||
|
||||
**Options**
|
||||
|
||||
:func:`~mpmath.invertlaplace` recognizes the following optional
|
||||
keywords valid for all methods:
|
||||
|
||||
*method*
|
||||
Chooses numerical inverse Laplace transform algorithm
|
||||
(described below).
|
||||
*degree*
|
||||
Number of terms used in the approximation
|
||||
|
||||
**Algorithms**
|
||||
|
||||
Mpmath implements four numerical inverse Laplace transform
|
||||
algorithms, attributed to: Talbot, Stehfest, and de Hoog,
|
||||
Knight and Stokes. These can be selected by using
|
||||
*method='talbot'*, *method='stehfest'*, *method='dehoog'* or
|
||||
*method='cohen'* or by passing the classes *method=FixedTalbot*,
|
||||
*method=Stehfest*, *method=deHoog*, or *method=Cohen*. The functions
|
||||
:func:`~mpmath.invlaptalbot`, :func:`~mpmath.invlapstehfest`,
|
||||
:func:`~mpmath.invlapdehoog`, and :func:`~mpmath.invlapcohen`
|
||||
are also available as shortcuts.
|
||||
|
||||
All four algorithms implement a heuristic balance between the
|
||||
requested precision and the precision used internally for the
|
||||
calculations. This has been tuned for a typical exponentially
|
||||
decaying function and precision up to few hundred decimal
|
||||
digits.
|
||||
|
||||
The Laplace transform converts the variable time (i.e., along
|
||||
a line) into a parameter given by the right half of the
|
||||
complex `p`-plane. Singularities, poles, and branch cuts in
|
||||
the complex `p`-plane contain all the information regarding
|
||||
the time behavior of the corresponding function. Any numerical
|
||||
method must therefore sample `p`-plane "close enough" to the
|
||||
singularities to accurately characterize them, while not
|
||||
getting too close to have catastrophic cancellation, overflow,
|
||||
or underflow issues. Most significantly, if one or more of the
|
||||
singularities in the `p`-plane is not on the left side of the
|
||||
Bromwich contour, its effects will be left out of the computed
|
||||
solution, and the answer will be completely wrong.
|
||||
|
||||
*Talbot*
|
||||
|
||||
The fixed Talbot method is high accuracy and fast, but the
|
||||
method can catastrophically fail for certain classes of time-domain
|
||||
behavior, including a Heaviside step function for positive
|
||||
time (e.g., `H(t-2)`), or some oscillatory behaviors. The
|
||||
Talbot method usually has adjustable parameters, but the
|
||||
"fixed" variety implemented here does not. This method
|
||||
deforms the Bromwich integral contour in the shape of a
|
||||
parabola towards `-\infty`, which leads to problems
|
||||
when the solution has a decaying exponential in it (e.g., a
|
||||
Heaviside step function is equivalent to multiplying by a
|
||||
decaying exponential in Laplace space).
|
||||
|
||||
*Stehfest*
|
||||
|
||||
The Stehfest algorithm only uses abscissa along the real axis
|
||||
of the complex `p`-plane to estimate the time-domain
|
||||
function. Oscillatory time-domain functions have poles away
|
||||
from the real axis, so this method does not work well with
|
||||
oscillatory functions, especially high-frequency ones. This
|
||||
method also depends on summation of terms in a series that
|
||||
grows very large, and will have catastrophic cancellation
|
||||
during summation if the working precision is too low.
|
||||
|
||||
*de Hoog et al.*
|
||||
|
||||
The de Hoog, Knight, and Stokes method is essentially a
|
||||
Fourier-series quadrature-type approximation to the Bromwich
|
||||
contour integral, with non-linear series acceleration and an
|
||||
analytical expression for the remainder term. This method is
|
||||
typically one of the most robust. This method also involves the
|
||||
greatest amount of overhead, so it is typically the slowest of the
|
||||
four methods at high precision.
|
||||
|
||||
*Cohen*
|
||||
|
||||
The Cohen method is a trapezoidal rule approximation to the Bromwich
|
||||
contour integral, with linear acceleration for alternating
|
||||
series. This method is as robust as the de Hoog et al method and the
|
||||
fastest of the four methods at high precision, and is therefore the
|
||||
default method.
|
||||
|
||||
**Singularities**
|
||||
|
||||
All numerical inverse Laplace transform methods have problems
|
||||
at large time when the Laplace-space function has poles,
|
||||
singularities, or branch cuts to the right of the origin in
|
||||
the complex plane. For simple poles in `\bar{f}(p)` at the
|
||||
`p`-plane origin, the time function is constant in time (e.g.,
|
||||
`\mathcal{L}\left\lbrace 1 \right\rbrace=1/p` has a pole at
|
||||
`p=0`). A pole in `\bar{f}(p)` to the left of the origin is a
|
||||
decreasing function of time (e.g., `\mathcal{L}\left\lbrace
|
||||
e^{-t/2} \right\rbrace=1/(p+1/2)` has a pole at `p=-1/2`), and
|
||||
a pole to the right of the origin leads to an increasing
|
||||
function in time (e.g., `\mathcal{L}\left\lbrace t e^{t/4}
|
||||
\right\rbrace = 1/(p-1/4)^2` has a pole at `p=1/4`). When
|
||||
singularities occur off the real `p` axis, the time-domain
|
||||
function is oscillatory. For example `\mathcal{L}\left\lbrace
|
||||
\mathrm{J}_0(t) \right\rbrace=1/\sqrt{p^2+1}` has a branch cut
|
||||
starting at `p=j=\sqrt{-1}` and is a decaying oscillatory
|
||||
function, This range of behaviors is illustrated in Duffy [3]
|
||||
Figure 4.10.4, p. 228.
|
||||
|
||||
In general as `p \rightarrow \infty` `t \rightarrow 0` and
|
||||
vice-versa. All numerical inverse Laplace transform methods
|
||||
require their abscissa to shift closer to the origin for
|
||||
larger times. If the abscissa shift left of the rightmost
|
||||
singularity in the Laplace domain, the answer will be
|
||||
completely wrong (the effect of singularities to the right of
|
||||
the Bromwich contour are not included in the results).
|
||||
|
||||
For example, the following exponentially growing function has
|
||||
a pole at `p=3`:
|
||||
|
||||
.. math ::
|
||||
|
||||
\bar{f}(p)=\frac{1}{p^2-9}
|
||||
|
||||
.. math ::
|
||||
|
||||
f(t)=\frac{1}{3}\sinh 3t
|
||||
|
||||
>>> mp.dps = 15; mp.pretty = True
|
||||
>>> fp = lambda p: 1/(p*p-9)
|
||||
>>> ft = lambda t: sinh(3*t)/3
|
||||
>>> tt = [0.01,0.1,1.0,10.0]
|
||||
>>> ft(tt[0]),invertlaplace(fp,tt[0],method='talbot')
|
||||
(0.0100015000675014, 0.0100015000675014)
|
||||
>>> ft(tt[1]),invertlaplace(fp,tt[1],method='talbot')
|
||||
(0.101506764482381, 0.101506764482381)
|
||||
>>> ft(tt[2]),invertlaplace(fp,tt[2],method='talbot')
|
||||
(3.33929164246997, 3.33929164246997)
|
||||
>>> ft(tt[3]),invertlaplace(fp,tt[3],method='talbot')
|
||||
(1781079096920.74, -1.61331069624091e-14)
|
||||
|
||||
**References**
|
||||
|
||||
1. [DLMF]_ section 1.14 (http://dlmf.nist.gov/1.14T4)
|
||||
2. Cohen, A.M. (2007). Numerical Methods for Laplace Transform
|
||||
Inversion, Springer.
|
||||
3. Duffy, D.G. (1998). Advanced Engineering Mathematics, CRC Press.
|
||||
|
||||
**Numerical Inverse Laplace Transform Reviews**
|
||||
|
||||
1. Bellman, R., R.E. Kalaba, J.A. Lockett (1966). *Numerical
|
||||
inversion of the Laplace transform: Applications to Biology,
|
||||
Economics, Engineering, and Physics*. Elsevier.
|
||||
2. Davies, B., B. Martin (1979). Numerical inversion of the
|
||||
Laplace transform: a survey and comparison of methods. *Journal
|
||||
of Computational Physics* 33:1-32,
|
||||
http://dx.doi.org/10.1016/0021-9991(79)90025-1
|
||||
3. Duffy, D.G. (1993). On the numerical inversion of Laplace
|
||||
transforms: Comparison of three new methods on characteristic
|
||||
problems from applications. *ACM Transactions on Mathematical
|
||||
Software* 19(3):333-359, http://dx.doi.org/10.1145/155743.155788
|
||||
4. Kuhlman, K.L., (2013). Review of Inverse Laplace Transform
|
||||
Algorithms for Laplace-Space Numerical Approaches, *Numerical
|
||||
Algorithms*, 63(2):339-355.
|
||||
http://dx.doi.org/10.1007/s11075-012-9625-3
|
||||
|
||||
"""
|
||||
|
||||
rule = kwargs.get('method', 'cohen')
|
||||
if type(rule) is str:
|
||||
lrule = rule.lower()
|
||||
if lrule == 'talbot':
|
||||
rule = ctx._fixed_talbot
|
||||
elif lrule == 'stehfest':
|
||||
rule = ctx._stehfest
|
||||
elif lrule == 'dehoog':
|
||||
rule = ctx._de_hoog
|
||||
elif rule == 'cohen':
|
||||
rule = ctx._cohen
|
||||
else:
|
||||
raise ValueError("unknown invlap algorithm: %s" % rule)
|
||||
else:
|
||||
rule = rule(ctx)
|
||||
|
||||
# determine the vector of Laplace-space parameter
|
||||
# needed for the requested method and desired time
|
||||
rule.calc_laplace_parameter(t, **kwargs)
|
||||
|
||||
# compute the Laplace-space function evalutations
|
||||
# at the required abscissa.
|
||||
fp = [f(p) for p in rule.p]
|
||||
|
||||
# compute the time-domain solution from the
|
||||
# Laplace-space function evaluations
|
||||
return rule.calc_time_domain_solution(fp, t)
|
||||
|
||||
# shortcuts for the above function for specific methods
|
||||
def invlaptalbot(ctx, *args, **kwargs):
|
||||
kwargs['method'] = 'talbot'
|
||||
return ctx.invertlaplace(*args, **kwargs)
|
||||
|
||||
def invlapstehfest(ctx, *args, **kwargs):
|
||||
kwargs['method'] = 'stehfest'
|
||||
return ctx.invertlaplace(*args, **kwargs)
|
||||
|
||||
def invlapdehoog(ctx, *args, **kwargs):
|
||||
kwargs['method'] = 'dehoog'
|
||||
return ctx.invertlaplace(*args, **kwargs)
|
||||
|
||||
def invlapcohen(ctx, *args, **kwargs):
|
||||
kwargs['method'] = 'cohen'
|
||||
return ctx.invertlaplace(*args, **kwargs)
|
||||
|
||||
|
||||
# ****************************************
|
||||
|
||||
if __name__ == '__main__':
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
@@ -0,0 +1,288 @@
|
||||
from bisect import bisect
|
||||
from ..libmp.backend import xrange
|
||||
|
||||
class ODEMethods(object):
|
||||
pass
|
||||
|
||||
def ode_taylor(ctx, derivs, x0, y0, tol_prec, n):
|
||||
h = tol = ctx.ldexp(1, -tol_prec)
|
||||
dim = len(y0)
|
||||
xs = [x0]
|
||||
ys = [y0]
|
||||
x = x0
|
||||
y = y0
|
||||
orig = ctx.prec
|
||||
try:
|
||||
ctx.prec = orig*(1+n)
|
||||
# Use n steps with Euler's method to get
|
||||
# evaluation points for derivatives
|
||||
for i in range(n):
|
||||
fxy = derivs(x, y)
|
||||
y = [y[i]+h*fxy[i] for i in xrange(len(y))]
|
||||
x += h
|
||||
xs.append(x)
|
||||
ys.append(y)
|
||||
# Compute derivatives
|
||||
ser = [[] for d in range(dim)]
|
||||
for j in range(n+1):
|
||||
s = [0]*dim
|
||||
b = (-1) ** (j & 1)
|
||||
k = 1
|
||||
for i in range(j+1):
|
||||
for d in range(dim):
|
||||
s[d] += b * ys[i][d]
|
||||
b = (b * (j-k+1)) // (-k)
|
||||
k += 1
|
||||
scale = h**(-j) / ctx.fac(j)
|
||||
for d in range(dim):
|
||||
s[d] = s[d] * scale
|
||||
ser[d].append(s[d])
|
||||
finally:
|
||||
ctx.prec = orig
|
||||
# Estimate radius for which we can get full accuracy.
|
||||
# XXX: do this right for zeros
|
||||
radius = ctx.one
|
||||
for ts in ser:
|
||||
if ts[-1]:
|
||||
radius = min(radius, ctx.nthroot(tol/abs(ts[-1]), n))
|
||||
radius /= 2 # XXX
|
||||
return ser, x0+radius
|
||||
|
||||
def odefun(ctx, F, x0, y0, tol=None, degree=None, method='taylor', verbose=False):
|
||||
r"""
|
||||
Returns a function `y(x) = [y_0(x), y_1(x), \ldots, y_n(x)]`
|
||||
that is a numerical solution of the `n+1`-dimensional first-order
|
||||
ordinary differential equation (ODE) system
|
||||
|
||||
.. math ::
|
||||
|
||||
y_0'(x) = F_0(x, [y_0(x), y_1(x), \ldots, y_n(x)])
|
||||
|
||||
y_1'(x) = F_1(x, [y_0(x), y_1(x), \ldots, y_n(x)])
|
||||
|
||||
\vdots
|
||||
|
||||
y_n'(x) = F_n(x, [y_0(x), y_1(x), \ldots, y_n(x)])
|
||||
|
||||
The derivatives are specified by the vector-valued function
|
||||
*F* that evaluates
|
||||
`[y_0', \ldots, y_n'] = F(x, [y_0, \ldots, y_n])`.
|
||||
The initial point `x_0` is specified by the scalar argument *x0*,
|
||||
and the initial value `y(x_0) = [y_0(x_0), \ldots, y_n(x_0)]` is
|
||||
specified by the vector argument *y0*.
|
||||
|
||||
For convenience, if the system is one-dimensional, you may optionally
|
||||
provide just a scalar value for *y0*. In this case, *F* should accept
|
||||
a scalar *y* argument and return a scalar. The solution function
|
||||
*y* will return scalar values instead of length-1 vectors.
|
||||
|
||||
Evaluation of the solution function `y(x)` is permitted
|
||||
for any `x \ge x_0`.
|
||||
|
||||
A high-order ODE can be solved by transforming it into first-order
|
||||
vector form. This transformation is described in standard texts
|
||||
on ODEs. Examples will also be given below.
|
||||
|
||||
**Options, speed and accuracy**
|
||||
|
||||
By default, :func:`~mpmath.odefun` uses a high-order Taylor series
|
||||
method. For reasonably well-behaved problems, the solution will
|
||||
be fully accurate to within the working precision. Note that
|
||||
*F* must be possible to evaluate to very high precision
|
||||
for the generation of Taylor series to work.
|
||||
|
||||
To get a faster but less accurate solution, you can set a large
|
||||
value for *tol* (which defaults roughly to *eps*). If you just
|
||||
want to plot the solution or perform a basic simulation,
|
||||
*tol = 0.01* is likely sufficient.
|
||||
|
||||
The *degree* argument controls the degree of the solver (with
|
||||
*method='taylor'*, this is the degree of the Taylor series
|
||||
expansion). A higher degree means that a longer step can be taken
|
||||
before a new local solution must be generated from *F*,
|
||||
meaning that fewer steps are required to get from `x_0` to a given
|
||||
`x_1`. On the other hand, a higher degree also means that each
|
||||
local solution becomes more expensive (i.e., more evaluations of
|
||||
*F* are required per step, and at higher precision).
|
||||
|
||||
The optimal setting therefore involves a tradeoff. Generally,
|
||||
decreasing the *degree* for Taylor series is likely to give faster
|
||||
solution at low precision, while increasing is likely to be better
|
||||
at higher precision.
|
||||
|
||||
The function
|
||||
object returned by :func:`~mpmath.odefun` caches the solutions at all step
|
||||
points and uses polynomial interpolation between step points.
|
||||
Therefore, once `y(x_1)` has been evaluated for some `x_1`,
|
||||
`y(x)` can be evaluated very quickly for any `x_0 \le x \le x_1`.
|
||||
and continuing the evaluation up to `x_2 > x_1` is also fast.
|
||||
|
||||
**Examples of first-order ODEs**
|
||||
|
||||
We will solve the standard test problem `y'(x) = y(x), y(0) = 1`
|
||||
which has explicit solution `y(x) = \exp(x)`::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 15; mp.pretty = True
|
||||
>>> f = odefun(lambda x, y: y, 0, 1)
|
||||
>>> for x in [0, 1, 2.5]:
|
||||
... print((f(x), exp(x)))
|
||||
...
|
||||
(1.0, 1.0)
|
||||
(2.71828182845905, 2.71828182845905)
|
||||
(12.1824939607035, 12.1824939607035)
|
||||
|
||||
The solution with high precision::
|
||||
|
||||
>>> mp.dps = 50
|
||||
>>> f = odefun(lambda x, y: y, 0, 1)
|
||||
>>> f(1)
|
||||
2.7182818284590452353602874713526624977572470937
|
||||
>>> exp(1)
|
||||
2.7182818284590452353602874713526624977572470937
|
||||
|
||||
Using the more general vectorized form, the test problem
|
||||
can be input as (note that *f* returns a 1-element vector)::
|
||||
|
||||
>>> mp.dps = 15
|
||||
>>> f = odefun(lambda x, y: [y[0]], 0, [1])
|
||||
>>> f(1)
|
||||
[2.71828182845905]
|
||||
|
||||
:func:`~mpmath.odefun` can solve nonlinear ODEs, which are generally
|
||||
impossible (and at best difficult) to solve analytically. As
|
||||
an example of a nonlinear ODE, we will solve `y'(x) = x \sin(y(x))`
|
||||
for `y(0) = \pi/2`. An exact solution happens to be known
|
||||
for this problem, and is given by
|
||||
`y(x) = 2 \tan^{-1}\left(\exp\left(x^2/2\right)\right)`::
|
||||
|
||||
>>> f = odefun(lambda x, y: x*sin(y), 0, pi/2)
|
||||
>>> for x in [2, 5, 10]:
|
||||
... print((f(x), 2*atan(exp(mpf(x)**2/2))))
|
||||
...
|
||||
(2.87255666284091, 2.87255666284091)
|
||||
(3.14158520028345, 3.14158520028345)
|
||||
(3.14159265358979, 3.14159265358979)
|
||||
|
||||
If `F` is independent of `y`, an ODE can be solved using direct
|
||||
integration. We can therefore obtain a reference solution with
|
||||
:func:`~mpmath.quad`::
|
||||
|
||||
>>> f = lambda x: (1+x**2)/(1+x**3)
|
||||
>>> g = odefun(lambda x, y: f(x), pi, 0)
|
||||
>>> g(2*pi)
|
||||
0.72128263801696
|
||||
>>> quad(f, [pi, 2*pi])
|
||||
0.72128263801696
|
||||
|
||||
**Examples of second-order ODEs**
|
||||
|
||||
We will solve the harmonic oscillator equation `y''(x) + y(x) = 0`.
|
||||
To do this, we introduce the helper functions `y_0 = y, y_1 = y_0'`
|
||||
whereby the original equation can be written as `y_1' + y_0' = 0`. Put
|
||||
together, we get the first-order, two-dimensional vector ODE
|
||||
|
||||
.. math ::
|
||||
|
||||
\begin{cases}
|
||||
y_0' = y_1 \\
|
||||
y_1' = -y_0
|
||||
\end{cases}
|
||||
|
||||
To get a well-defined IVP, we need two initial values. With
|
||||
`y(0) = y_0(0) = 1` and `-y'(0) = y_1(0) = 0`, the problem will of
|
||||
course be solved by `y(x) = y_0(x) = \cos(x)` and
|
||||
`-y'(x) = y_1(x) = \sin(x)`. We check this::
|
||||
|
||||
>>> f = odefun(lambda x, y: [-y[1], y[0]], 0, [1, 0])
|
||||
>>> for x in [0, 1, 2.5, 10]:
|
||||
... nprint(f(x), 15)
|
||||
... nprint([cos(x), sin(x)], 15)
|
||||
... print("---")
|
||||
...
|
||||
[1.0, 0.0]
|
||||
[1.0, 0.0]
|
||||
---
|
||||
[0.54030230586814, 0.841470984807897]
|
||||
[0.54030230586814, 0.841470984807897]
|
||||
---
|
||||
[-0.801143615546934, 0.598472144103957]
|
||||
[-0.801143615546934, 0.598472144103957]
|
||||
---
|
||||
[-0.839071529076452, -0.54402111088937]
|
||||
[-0.839071529076452, -0.54402111088937]
|
||||
---
|
||||
|
||||
Note that we get both the sine and the cosine solutions
|
||||
simultaneously.
|
||||
|
||||
**TODO**
|
||||
|
||||
* Better automatic choice of degree and step size
|
||||
* Make determination of Taylor series convergence radius
|
||||
more robust
|
||||
* Allow solution for `x < x_0`
|
||||
* Allow solution for complex `x`
|
||||
* Test for difficult (ill-conditioned) problems
|
||||
* Implement Runge-Kutta and other algorithms
|
||||
|
||||
"""
|
||||
if tol:
|
||||
tol_prec = int(-ctx.log(tol, 2))+10
|
||||
else:
|
||||
tol_prec = ctx.prec+10
|
||||
degree = degree or (3 + int(3*ctx.dps/2.))
|
||||
workprec = ctx.prec + 40
|
||||
try:
|
||||
len(y0)
|
||||
return_vector = True
|
||||
except TypeError:
|
||||
F_ = F
|
||||
F = lambda x, y: [F_(x, y[0])]
|
||||
y0 = [y0]
|
||||
return_vector = False
|
||||
ser, xb = ode_taylor(ctx, F, x0, y0, tol_prec, degree)
|
||||
series_boundaries = [x0, xb]
|
||||
series_data = [(ser, x0, xb)]
|
||||
# We will be working with vectors of Taylor series
|
||||
def mpolyval(ser, a):
|
||||
return [ctx.polyval(s[::-1], a) for s in ser]
|
||||
# Find nearest expansion point; compute if necessary
|
||||
def get_series(x):
|
||||
if x < x0:
|
||||
raise ValueError
|
||||
n = bisect(series_boundaries, x)
|
||||
if n < len(series_boundaries):
|
||||
return series_data[n-1]
|
||||
while 1:
|
||||
ser, xa, xb = series_data[-1]
|
||||
if verbose:
|
||||
print("Computing Taylor series for [%f, %f]" % (xa, xb))
|
||||
y = mpolyval(ser, xb-xa)
|
||||
xa = xb
|
||||
ser, xb = ode_taylor(ctx, F, xb, y, tol_prec, degree)
|
||||
series_boundaries.append(xb)
|
||||
series_data.append((ser, xa, xb))
|
||||
if x <= xb:
|
||||
return series_data[-1]
|
||||
# Evaluation function
|
||||
def interpolant(x):
|
||||
x = ctx.convert(x)
|
||||
orig = ctx.prec
|
||||
try:
|
||||
ctx.prec = workprec
|
||||
ser, xa, xb = get_series(x)
|
||||
y = mpolyval(ser, x-xa)
|
||||
finally:
|
||||
ctx.prec = orig
|
||||
if return_vector:
|
||||
return [+yk for yk in y]
|
||||
else:
|
||||
return +y[0]
|
||||
return interpolant
|
||||
|
||||
ODEMethods.odefun = odefun
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,213 @@
|
||||
from ..libmp.backend import xrange
|
||||
from .calculus import defun
|
||||
|
||||
#----------------------------------------------------------------------------#
|
||||
# Polynomials #
|
||||
#----------------------------------------------------------------------------#
|
||||
|
||||
# XXX: extra precision
|
||||
@defun
|
||||
def polyval(ctx, coeffs, x, derivative=False):
|
||||
r"""
|
||||
Given coefficients `[c_n, \ldots, c_2, c_1, c_0]` and a number `x`,
|
||||
:func:`~mpmath.polyval` evaluates the polynomial
|
||||
|
||||
.. math ::
|
||||
|
||||
P(x) = c_n x^n + \ldots + c_2 x^2 + c_1 x + c_0.
|
||||
|
||||
If *derivative=True* is set, :func:`~mpmath.polyval` simultaneously
|
||||
evaluates `P(x)` with the derivative, `P'(x)`, and returns the
|
||||
tuple `(P(x), P'(x))`.
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.pretty = True
|
||||
>>> polyval([3, 0, 2], 0.5)
|
||||
2.75
|
||||
>>> polyval([3, 0, 2], 0.5, derivative=True)
|
||||
(2.75, 3.0)
|
||||
|
||||
The coefficients and the evaluation point may be any combination
|
||||
of real or complex numbers.
|
||||
"""
|
||||
if not coeffs:
|
||||
return ctx.zero
|
||||
p = ctx.convert(coeffs[0])
|
||||
q = ctx.zero
|
||||
for c in coeffs[1:]:
|
||||
if derivative:
|
||||
q = p + x*q
|
||||
p = c + x*p
|
||||
if derivative:
|
||||
return p, q
|
||||
else:
|
||||
return p
|
||||
|
||||
@defun
|
||||
def polyroots(ctx, coeffs, maxsteps=50, cleanup=True, extraprec=10,
|
||||
error=False, roots_init=None):
|
||||
"""
|
||||
Computes all roots (real or complex) of a given polynomial.
|
||||
|
||||
The roots are returned as a sorted list, where real roots appear first
|
||||
followed by complex conjugate roots as adjacent elements. The polynomial
|
||||
should be given as a list of coefficients, in the format used by
|
||||
:func:`~mpmath.polyval`. The leading coefficient must be nonzero.
|
||||
|
||||
With *error=True*, :func:`~mpmath.polyroots` returns a tuple *(roots, err)*
|
||||
where *err* is an estimate of the maximum error among the computed roots.
|
||||
|
||||
**Examples**
|
||||
|
||||
Finding the three real roots of `x^3 - x^2 - 14x + 24`::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 15; mp.pretty = True
|
||||
>>> nprint(polyroots([1,-1,-14,24]), 4)
|
||||
[-4.0, 2.0, 3.0]
|
||||
|
||||
Finding the two complex conjugate roots of `4x^2 + 3x + 2`, with an
|
||||
error estimate::
|
||||
|
||||
>>> roots, err = polyroots([4,3,2], error=True)
|
||||
>>> for r in roots:
|
||||
... print(r)
|
||||
...
|
||||
(-0.375 + 0.59947894041409j)
|
||||
(-0.375 - 0.59947894041409j)
|
||||
>>>
|
||||
>>> err
|
||||
2.22044604925031e-16
|
||||
>>>
|
||||
>>> polyval([4,3,2], roots[0])
|
||||
(2.22044604925031e-16 + 0.0j)
|
||||
>>> polyval([4,3,2], roots[1])
|
||||
(2.22044604925031e-16 + 0.0j)
|
||||
|
||||
The following example computes all the 5th roots of unity; that is,
|
||||
the roots of `x^5 - 1`::
|
||||
|
||||
>>> mp.dps = 20
|
||||
>>> for r in polyroots([1, 0, 0, 0, 0, -1]):
|
||||
... print(r)
|
||||
...
|
||||
1.0
|
||||
(-0.8090169943749474241 + 0.58778525229247312917j)
|
||||
(-0.8090169943749474241 - 0.58778525229247312917j)
|
||||
(0.3090169943749474241 + 0.95105651629515357212j)
|
||||
(0.3090169943749474241 - 0.95105651629515357212j)
|
||||
|
||||
**Precision and conditioning**
|
||||
|
||||
The roots are computed to the current working precision accuracy. If this
|
||||
accuracy cannot be achieved in ``maxsteps`` steps, then a
|
||||
``NoConvergence`` exception is raised. The algorithm internally is using
|
||||
the current working precision extended by ``extraprec``. If
|
||||
``NoConvergence`` was raised, that is caused either by not having enough
|
||||
extra precision to achieve convergence (in which case increasing
|
||||
``extraprec`` should fix the problem) or too low ``maxsteps`` (in which
|
||||
case increasing ``maxsteps`` should fix the problem), or a combination of
|
||||
both.
|
||||
|
||||
The user should always do a convergence study with regards to
|
||||
``extraprec`` to ensure accurate results. It is possible to get
|
||||
convergence to a wrong answer with too low ``extraprec``.
|
||||
|
||||
Provided there are no repeated roots, :func:`~mpmath.polyroots` can
|
||||
typically compute all roots of an arbitrary polynomial to high precision::
|
||||
|
||||
>>> mp.dps = 60
|
||||
>>> for r in polyroots([1, 0, -10, 0, 1]):
|
||||
... print(r)
|
||||
...
|
||||
-3.14626436994197234232913506571557044551247712918732870123249
|
||||
-0.317837245195782244725757617296174288373133378433432554879127
|
||||
0.317837245195782244725757617296174288373133378433432554879127
|
||||
3.14626436994197234232913506571557044551247712918732870123249
|
||||
>>>
|
||||
>>> sqrt(3) + sqrt(2)
|
||||
3.14626436994197234232913506571557044551247712918732870123249
|
||||
>>> sqrt(3) - sqrt(2)
|
||||
0.317837245195782244725757617296174288373133378433432554879127
|
||||
|
||||
**Algorithm**
|
||||
|
||||
:func:`~mpmath.polyroots` implements the Durand-Kerner method [1], which
|
||||
uses complex arithmetic to locate all roots simultaneously.
|
||||
The Durand-Kerner method can be viewed as approximately performing
|
||||
simultaneous Newton iteration for all the roots. In particular,
|
||||
the convergence to simple roots is quadratic, just like Newton's
|
||||
method.
|
||||
|
||||
Although all roots are internally calculated using complex arithmetic, any
|
||||
root found to have an imaginary part smaller than the estimated numerical
|
||||
error is truncated to a real number (small real parts are also chopped).
|
||||
Real roots are placed first in the returned list, sorted by value. The
|
||||
remaining complex roots are sorted by their real parts so that conjugate
|
||||
roots end up next to each other.
|
||||
|
||||
**References**
|
||||
|
||||
1. http://en.wikipedia.org/wiki/Durand-Kerner_method
|
||||
|
||||
"""
|
||||
if len(coeffs) <= 1:
|
||||
if not coeffs or not coeffs[0]:
|
||||
raise ValueError("Input to polyroots must not be the zero polynomial")
|
||||
# Constant polynomial with no roots
|
||||
return []
|
||||
|
||||
orig = ctx.prec
|
||||
tol = +ctx.eps
|
||||
with ctx.extraprec(extraprec):
|
||||
deg = len(coeffs) - 1
|
||||
# Must be monic
|
||||
lead = ctx.convert(coeffs[0])
|
||||
if lead == 1:
|
||||
coeffs = [ctx.convert(c) for c in coeffs]
|
||||
else:
|
||||
coeffs = [c/lead for c in coeffs]
|
||||
f = lambda x: ctx.polyval(coeffs, x)
|
||||
if roots_init is None:
|
||||
roots = [ctx.mpc((0.4+0.9j)**n) for n in xrange(deg)]
|
||||
else:
|
||||
roots = [None]*deg;
|
||||
deg_init = min(deg, len(roots_init))
|
||||
roots[:deg_init] = list(roots_init[:deg_init])
|
||||
roots[deg_init:] = [ctx.mpc((0.4+0.9j)**n) for n
|
||||
in xrange(deg_init,deg)]
|
||||
err = [ctx.one for n in xrange(deg)]
|
||||
# Durand-Kerner iteration until convergence
|
||||
for step in xrange(maxsteps):
|
||||
if abs(max(err)) < tol:
|
||||
break
|
||||
for i in xrange(deg):
|
||||
p = roots[i]
|
||||
x = f(p)
|
||||
for j in range(deg):
|
||||
if i != j:
|
||||
try:
|
||||
x /= (p-roots[j])
|
||||
except ZeroDivisionError:
|
||||
continue
|
||||
roots[i] = p - x
|
||||
err[i] = abs(x)
|
||||
if abs(max(err)) >= tol:
|
||||
raise ctx.NoConvergence("Didn't converge in maxsteps=%d steps." \
|
||||
% maxsteps)
|
||||
# Remove small real or imaginary parts
|
||||
if cleanup:
|
||||
for i in xrange(deg):
|
||||
if abs(roots[i]) < tol:
|
||||
roots[i] = ctx.zero
|
||||
elif abs(ctx._im(roots[i])) < tol:
|
||||
roots[i] = roots[i].real
|
||||
elif abs(ctx._re(roots[i])) < tol:
|
||||
roots[i] = roots[i].imag * 1j
|
||||
roots.sort(key=lambda x: (abs(ctx._im(x)), ctx._re(x)))
|
||||
if error:
|
||||
err = max(err)
|
||||
err = max(err, ctx.ldexp(1, -orig+1))
|
||||
return [+r for r in roots], +err
|
||||
else:
|
||||
return [+r for r in roots]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,494 @@
|
||||
from operator import gt, lt
|
||||
|
||||
from .libmp.backend import xrange
|
||||
|
||||
from .functions.functions import SpecialFunctions
|
||||
from .functions.rszeta import RSCache
|
||||
from .calculus.quadrature import QuadratureMethods
|
||||
from .calculus.inverselaplace import LaplaceTransformInversionMethods
|
||||
from .calculus.calculus import CalculusMethods
|
||||
from .calculus.optimization import OptimizationMethods
|
||||
from .calculus.odes import ODEMethods
|
||||
from .matrices.matrices import MatrixMethods
|
||||
from .matrices.calculus import MatrixCalculusMethods
|
||||
from .matrices.linalg import LinearAlgebraMethods
|
||||
from .matrices.eigen import Eigen
|
||||
from .identification import IdentificationMethods
|
||||
from .visualization import VisualizationMethods
|
||||
|
||||
from . import libmp
|
||||
|
||||
class Context(object):
|
||||
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 *
|
||||
>>> mp.dps = 15; mp.pretty = False
|
||||
>>> 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 *
|
||||
>>> mp.dps = 15
|
||||
>>> 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
|
||||
|
||||
"""
|
||||
t = ctx.convert(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 :func:`~mpmath.range` function
|
||||
that accepts fractional endpoints and step sizes and
|
||||
returns a list of ``mpf`` instances. Like :func:`~mpmath.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 :func:`~mpmath.range`, the endpoint is not included. To
|
||||
produce ranges where the endpoint is included, :func:`~mpmath.linspace`
|
||||
is more convenient.
|
||||
|
||||
**Examples**
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 15; mp.pretty = False
|
||||
>>> 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 *
|
||||
>>> mp.dps = 15; mp.pretty = False
|
||||
>>> 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 not 'endpoint' 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 xrange(n)]
|
||||
y[-1] = b
|
||||
else:
|
||||
step = (b - a) / ctx.mpf(n)
|
||||
y = [i*step + a for i in xrange(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.gcd)
|
||||
list_primes = staticmethod(libmp.list_primes)
|
||||
isprime = staticmethod(libmp.isprime)
|
||||
bernfrac = staticmethod(libmp.bernfrac)
|
||||
moebius = staticmethod(libmp.moebius)
|
||||
_ifac = staticmethod(libmp.ifac)
|
||||
_eulernum = staticmethod(libmp.eulernum)
|
||||
_stirling1 = staticmethod(libmp.stirling1)
|
||||
_stirling2 = staticmethod(libmp.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
|
||||
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
|
||||
`x^y = \exp(y \log(x))`::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> 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
|
||||
"""
|
||||
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 *
|
||||
>>> mp.dps = 15
|
||||
>>> f = maxcalls(sin, 10)
|
||||
>>> print(sum(f(n) for n in range(10)))
|
||||
1.95520948210738
|
||||
>>> f(10) # doctest: +IGNORE_EXCEPTION_DETAIL
|
||||
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 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 *
|
||||
>>> mp.dps = 15; mp.pretty = True
|
||||
>>> f = memoize(maxcalls(sin, 1))
|
||||
>>> f(2)
|
||||
0.909297426825682
|
||||
>>> f(2)
|
||||
0.909297426825682
|
||||
>>> mp.dps = 25
|
||||
>>> f(2) # doctest: +IGNORE_EXCEPTION_DETAIL
|
||||
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
|
||||
@@ -0,0 +1,253 @@
|
||||
from .ctx_base import StandardBaseContext
|
||||
|
||||
import math
|
||||
import cmath
|
||||
from . import math2
|
||||
|
||||
from . import function_docs
|
||||
|
||||
from .libmp import mpf_bernoulli, to_float, int_types
|
||||
from . import libmp
|
||||
|
||||
class FPContext(StandardBaseContext):
|
||||
"""
|
||||
Context for fast low-precision arithmetic (53-bit precision, giving at most
|
||||
about 15-digit accuracy), using Python's builtin float and complex.
|
||||
"""
|
||||
|
||||
def __init__(ctx):
|
||||
StandardBaseContext.__init__(ctx)
|
||||
|
||||
# Override SpecialFunctions implementation
|
||||
ctx.loggamma = math2.loggamma
|
||||
ctx._bernoulli_cache = {}
|
||||
ctx.pretty = False
|
||||
|
||||
ctx._init_aliases()
|
||||
|
||||
_mpq = lambda cls, x: float(x[0])/x[1]
|
||||
|
||||
NoConvergence = libmp.NoConvergence
|
||||
|
||||
def _get_prec(ctx): return 53
|
||||
def _set_prec(ctx, p): return
|
||||
def _get_dps(ctx): return 15
|
||||
def _set_dps(ctx, p): return
|
||||
|
||||
_fixed_precision = True
|
||||
|
||||
prec = property(_get_prec, _set_prec)
|
||||
dps = property(_get_dps, _set_dps)
|
||||
|
||||
zero = 0.0
|
||||
one = 1.0
|
||||
eps = math2.EPS
|
||||
inf = math2.INF
|
||||
ninf = math2.NINF
|
||||
nan = math2.NAN
|
||||
j = 1j
|
||||
|
||||
# Called by SpecialFunctions.__init__()
|
||||
@classmethod
|
||||
def _wrap_specfun(cls, name, f, wrap):
|
||||
if wrap:
|
||||
def f_wrapped(ctx, *args, **kwargs):
|
||||
convert = ctx.convert
|
||||
args = [convert(a) for a in args]
|
||||
return f(ctx, *args, **kwargs)
|
||||
else:
|
||||
f_wrapped = f
|
||||
f_wrapped.__doc__ = function_docs.__dict__.get(name, f.__doc__)
|
||||
setattr(cls, name, f_wrapped)
|
||||
|
||||
def bernoulli(ctx, n):
|
||||
cache = ctx._bernoulli_cache
|
||||
if n in cache:
|
||||
return cache[n]
|
||||
cache[n] = to_float(mpf_bernoulli(n, 53, 'n'), strict=True)
|
||||
return cache[n]
|
||||
|
||||
pi = math2.pi
|
||||
e = math2.e
|
||||
euler = math2.euler
|
||||
sqrt2 = 1.4142135623730950488
|
||||
sqrt5 = 2.2360679774997896964
|
||||
phi = 1.6180339887498948482
|
||||
ln2 = 0.69314718055994530942
|
||||
ln10 = 2.302585092994045684
|
||||
euler = 0.57721566490153286061
|
||||
catalan = 0.91596559417721901505
|
||||
khinchin = 2.6854520010653064453
|
||||
apery = 1.2020569031595942854
|
||||
glaisher = 1.2824271291006226369
|
||||
|
||||
absmin = absmax = abs
|
||||
|
||||
def is_special(ctx, x):
|
||||
return x - x != 0.0
|
||||
|
||||
def isnan(ctx, x):
|
||||
return x != x
|
||||
|
||||
def isinf(ctx, x):
|
||||
return abs(x) == math2.INF
|
||||
|
||||
def isnormal(ctx, x):
|
||||
if x:
|
||||
return x - x == 0.0
|
||||
return False
|
||||
|
||||
def isnpint(ctx, x):
|
||||
if type(x) is complex:
|
||||
if x.imag:
|
||||
return False
|
||||
x = x.real
|
||||
return x <= 0.0 and round(x) == x
|
||||
|
||||
mpf = float
|
||||
mpc = complex
|
||||
|
||||
def convert(ctx, x):
|
||||
try:
|
||||
return float(x)
|
||||
except:
|
||||
return complex(x)
|
||||
|
||||
power = staticmethod(math2.pow)
|
||||
sqrt = staticmethod(math2.sqrt)
|
||||
exp = staticmethod(math2.exp)
|
||||
ln = log = staticmethod(math2.log)
|
||||
cos = staticmethod(math2.cos)
|
||||
sin = staticmethod(math2.sin)
|
||||
tan = staticmethod(math2.tan)
|
||||
cos_sin = staticmethod(math2.cos_sin)
|
||||
acos = staticmethod(math2.acos)
|
||||
asin = staticmethod(math2.asin)
|
||||
atan = staticmethod(math2.atan)
|
||||
cosh = staticmethod(math2.cosh)
|
||||
sinh = staticmethod(math2.sinh)
|
||||
tanh = staticmethod(math2.tanh)
|
||||
gamma = staticmethod(math2.gamma)
|
||||
rgamma = staticmethod(math2.rgamma)
|
||||
fac = factorial = staticmethod(math2.factorial)
|
||||
floor = staticmethod(math2.floor)
|
||||
ceil = staticmethod(math2.ceil)
|
||||
cospi = staticmethod(math2.cospi)
|
||||
sinpi = staticmethod(math2.sinpi)
|
||||
cbrt = staticmethod(math2.cbrt)
|
||||
_nthroot = staticmethod(math2.nthroot)
|
||||
_ei = staticmethod(math2.ei)
|
||||
_e1 = staticmethod(math2.e1)
|
||||
_zeta = _zeta_int = staticmethod(math2.zeta)
|
||||
|
||||
# XXX: math2
|
||||
def arg(ctx, z):
|
||||
z = complex(z)
|
||||
return math.atan2(z.imag, z.real)
|
||||
|
||||
def expj(ctx, x):
|
||||
return ctx.exp(ctx.j*x)
|
||||
|
||||
def expjpi(ctx, x):
|
||||
return ctx.exp(ctx.j*ctx.pi*x)
|
||||
|
||||
ldexp = math.ldexp
|
||||
frexp = math.frexp
|
||||
|
||||
def mag(ctx, z):
|
||||
if z:
|
||||
return ctx.frexp(abs(z))[1]
|
||||
return ctx.ninf
|
||||
|
||||
def isint(ctx, z):
|
||||
if hasattr(z, "imag"): # float/int don't have .real/.imag in py2.5
|
||||
if z.imag:
|
||||
return False
|
||||
z = z.real
|
||||
try:
|
||||
return z == int(z)
|
||||
except:
|
||||
return False
|
||||
|
||||
def nint_distance(ctx, z):
|
||||
if hasattr(z, "imag"): # float/int don't have .real/.imag in py2.5
|
||||
n = round(z.real)
|
||||
else:
|
||||
n = round(z)
|
||||
if n == z:
|
||||
return n, ctx.ninf
|
||||
return n, ctx.mag(abs(z-n))
|
||||
|
||||
def _convert_param(ctx, z):
|
||||
if type(z) is tuple:
|
||||
p, q = z
|
||||
return ctx.mpf(p) / q, 'R'
|
||||
if hasattr(z, "imag"): # float/int don't have .real/.imag in py2.5
|
||||
intz = int(z.real)
|
||||
else:
|
||||
intz = int(z)
|
||||
if z == intz:
|
||||
return intz, 'Z'
|
||||
return z, 'R'
|
||||
|
||||
def _is_real_type(ctx, z):
|
||||
return isinstance(z, float) or isinstance(z, int_types)
|
||||
|
||||
def _is_complex_type(ctx, z):
|
||||
return isinstance(z, complex)
|
||||
|
||||
def hypsum(ctx, p, q, types, coeffs, z, maxterms=6000, **kwargs):
|
||||
coeffs = list(coeffs)
|
||||
num = range(p)
|
||||
den = range(p,p+q)
|
||||
tol = ctx.eps
|
||||
s = t = 1.0
|
||||
k = 0
|
||||
while 1:
|
||||
for i in num: t *= (coeffs[i]+k)
|
||||
for i in den: t /= (coeffs[i]+k)
|
||||
k += 1; t /= k; t *= z; s += t
|
||||
if abs(t) < tol:
|
||||
return s
|
||||
if k > maxterms:
|
||||
raise ctx.NoConvergence
|
||||
|
||||
def atan2(ctx, x, y):
|
||||
return math.atan2(x, y)
|
||||
|
||||
def psi(ctx, m, z):
|
||||
m = int(m)
|
||||
if m == 0:
|
||||
return ctx.digamma(z)
|
||||
return (-1)**(m+1) * ctx.fac(m) * ctx.zeta(m+1, z)
|
||||
|
||||
digamma = staticmethod(math2.digamma)
|
||||
|
||||
def harmonic(ctx, x):
|
||||
x = ctx.convert(x)
|
||||
if x == 0 or x == 1:
|
||||
return x
|
||||
return ctx.digamma(x+1) + ctx.euler
|
||||
|
||||
nstr = str
|
||||
|
||||
def to_fixed(ctx, x, prec):
|
||||
return int(math.ldexp(x, prec))
|
||||
|
||||
def rand(ctx):
|
||||
import random
|
||||
return random.random()
|
||||
|
||||
_erf = staticmethod(math2.erf)
|
||||
_erfc = staticmethod(math2.erfc)
|
||||
|
||||
def sum_accurately(ctx, terms, check_step=1):
|
||||
s = ctx.zero
|
||||
k = 0
|
||||
for term in terms():
|
||||
s += term
|
||||
if (not k % check_step) and term:
|
||||
if abs(term) <= 1e-18*abs(s):
|
||||
break
|
||||
k += 1
|
||||
return s
|
||||
@@ -0,0 +1,551 @@
|
||||
import operator
|
||||
|
||||
from . import libmp
|
||||
|
||||
from .libmp.backend import basestring
|
||||
|
||||
from .libmp import (
|
||||
int_types, MPZ_ONE,
|
||||
prec_to_dps, dps_to_prec, repr_dps,
|
||||
round_floor, round_ceiling,
|
||||
fzero, finf, fninf, fnan,
|
||||
mpf_le, mpf_neg,
|
||||
from_int, from_float, from_str, from_rational,
|
||||
mpi_mid, mpi_delta, mpi_str,
|
||||
mpi_abs, mpi_pos, mpi_neg, mpi_add, mpi_sub,
|
||||
mpi_mul, mpi_div, mpi_pow_int, mpi_pow,
|
||||
mpi_from_str,
|
||||
mpci_pos, mpci_neg, mpci_add, mpci_sub, mpci_mul, mpci_div, mpci_pow,
|
||||
mpci_abs, mpci_pow, mpci_exp, mpci_log,
|
||||
ComplexResult,
|
||||
mpf_hash, mpc_hash)
|
||||
from .matrices.matrices import _matrix
|
||||
|
||||
mpi_zero = (fzero, fzero)
|
||||
|
||||
from .ctx_base import StandardBaseContext
|
||||
|
||||
new = object.__new__
|
||||
|
||||
def convert_mpf_(x, prec, rounding):
|
||||
if hasattr(x, "_mpf_"): return x._mpf_
|
||||
if isinstance(x, int_types): return from_int(x, prec, rounding)
|
||||
if isinstance(x, float): return from_float(x, prec, rounding)
|
||||
if isinstance(x, basestring): return from_str(x, prec, rounding)
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class ivmpf(object):
|
||||
"""
|
||||
Interval arithmetic class. Precision is controlled by iv.prec.
|
||||
"""
|
||||
|
||||
def __new__(cls, x=0):
|
||||
return cls.ctx.convert(x)
|
||||
|
||||
def cast(self, cls, f_convert):
|
||||
a, b = self._mpi_
|
||||
if a == b:
|
||||
return cls(f_convert(a))
|
||||
raise ValueError
|
||||
|
||||
def __int__(self):
|
||||
return self.cast(int, libmp.to_int)
|
||||
|
||||
def __float__(self):
|
||||
return self.cast(float, libmp.to_float)
|
||||
|
||||
def __complex__(self):
|
||||
return self.cast(complex, libmp.to_float)
|
||||
|
||||
def __hash__(self):
|
||||
a, b = self._mpi_
|
||||
if a == b:
|
||||
return mpf_hash(a)
|
||||
else:
|
||||
return hash(self._mpi_)
|
||||
|
||||
@property
|
||||
def real(self): return self
|
||||
|
||||
@property
|
||||
def imag(self): return self.ctx.zero
|
||||
|
||||
def conjugate(self): return self
|
||||
|
||||
@property
|
||||
def a(self):
|
||||
a, b = self._mpi_
|
||||
return self.ctx.make_mpf((a, a))
|
||||
|
||||
@property
|
||||
def b(self):
|
||||
a, b = self._mpi_
|
||||
return self.ctx.make_mpf((b, b))
|
||||
|
||||
@property
|
||||
def mid(self):
|
||||
ctx = self.ctx
|
||||
v = mpi_mid(self._mpi_, ctx.prec)
|
||||
return ctx.make_mpf((v, v))
|
||||
|
||||
@property
|
||||
def delta(self):
|
||||
ctx = self.ctx
|
||||
v = mpi_delta(self._mpi_, ctx.prec)
|
||||
return ctx.make_mpf((v,v))
|
||||
|
||||
@property
|
||||
def _mpci_(self):
|
||||
return self._mpi_, mpi_zero
|
||||
|
||||
def _compare(*args):
|
||||
raise TypeError("no ordering relation is defined for intervals")
|
||||
|
||||
__gt__ = _compare
|
||||
__le__ = _compare
|
||||
__gt__ = _compare
|
||||
__ge__ = _compare
|
||||
|
||||
def __contains__(self, t):
|
||||
t = self.ctx.mpf(t)
|
||||
return (self.a <= t.a) and (t.b <= self.b)
|
||||
|
||||
def __str__(self):
|
||||
return mpi_str(self._mpi_, self.ctx.prec)
|
||||
|
||||
def __repr__(self):
|
||||
if self.ctx.pretty:
|
||||
return str(self)
|
||||
a, b = self._mpi_
|
||||
n = repr_dps(self.ctx.prec)
|
||||
a = libmp.to_str(a, n)
|
||||
b = libmp.to_str(b, n)
|
||||
return "mpi(%r, %r)" % (a, b)
|
||||
|
||||
def _compare(s, t, cmpfun):
|
||||
if not hasattr(t, "_mpi_"):
|
||||
try:
|
||||
t = s.ctx.convert(t)
|
||||
except:
|
||||
return NotImplemented
|
||||
return cmpfun(s._mpi_, t._mpi_)
|
||||
|
||||
def __eq__(s, t): return s._compare(t, libmp.mpi_eq)
|
||||
def __ne__(s, t): return s._compare(t, libmp.mpi_ne)
|
||||
def __lt__(s, t): return s._compare(t, libmp.mpi_lt)
|
||||
def __le__(s, t): return s._compare(t, libmp.mpi_le)
|
||||
def __gt__(s, t): return s._compare(t, libmp.mpi_gt)
|
||||
def __ge__(s, t): return s._compare(t, libmp.mpi_ge)
|
||||
|
||||
def __abs__(self):
|
||||
return self.ctx.make_mpf(mpi_abs(self._mpi_, self.ctx.prec))
|
||||
def __pos__(self):
|
||||
return self.ctx.make_mpf(mpi_pos(self._mpi_, self.ctx.prec))
|
||||
def __neg__(self):
|
||||
return self.ctx.make_mpf(mpi_neg(self._mpi_, self.ctx.prec))
|
||||
|
||||
def ae(s, t, rel_eps=None, abs_eps=None):
|
||||
return s.ctx.almosteq(s, t, rel_eps, abs_eps)
|
||||
|
||||
class ivmpc(object):
|
||||
|
||||
def __new__(cls, re=0, im=0):
|
||||
re = cls.ctx.convert(re)
|
||||
im = cls.ctx.convert(im)
|
||||
y = new(cls)
|
||||
y._mpci_ = re._mpi_, im._mpi_
|
||||
return y
|
||||
|
||||
def __hash__(self):
|
||||
(a, b), (c,d) = self._mpci_
|
||||
if a == b and c == d:
|
||||
return mpc_hash((a, c))
|
||||
else:
|
||||
return hash(self._mpci_)
|
||||
|
||||
def __repr__(s):
|
||||
if s.ctx.pretty:
|
||||
return str(s)
|
||||
return "iv.mpc(%s, %s)" % (repr(s.real), repr(s.imag))
|
||||
|
||||
def __str__(s):
|
||||
return "(%s + %s*j)" % (str(s.real), str(s.imag))
|
||||
|
||||
@property
|
||||
def a(self):
|
||||
(a, b), (c,d) = self._mpci_
|
||||
return self.ctx.make_mpf((a, a))
|
||||
|
||||
@property
|
||||
def b(self):
|
||||
(a, b), (c,d) = self._mpci_
|
||||
return self.ctx.make_mpf((b, b))
|
||||
|
||||
@property
|
||||
def c(self):
|
||||
(a, b), (c,d) = self._mpci_
|
||||
return self.ctx.make_mpf((c, c))
|
||||
|
||||
@property
|
||||
def d(self):
|
||||
(a, b), (c,d) = self._mpci_
|
||||
return self.ctx.make_mpf((d, d))
|
||||
|
||||
@property
|
||||
def real(s):
|
||||
return s.ctx.make_mpf(s._mpci_[0])
|
||||
|
||||
@property
|
||||
def imag(s):
|
||||
return s.ctx.make_mpf(s._mpci_[1])
|
||||
|
||||
def conjugate(s):
|
||||
a, b = s._mpci_
|
||||
return s.ctx.make_mpc((a, mpf_neg(b)))
|
||||
|
||||
def overlap(s, t):
|
||||
t = s.ctx.convert(t)
|
||||
real_overlap = (s.a <= t.a <= s.b) or (s.a <= t.b <= s.b) or (t.a <= s.a <= t.b) or (t.a <= s.b <= t.b)
|
||||
imag_overlap = (s.c <= t.c <= s.d) or (s.c <= t.d <= s.d) or (t.c <= s.c <= t.d) or (t.c <= s.d <= t.d)
|
||||
return real_overlap and imag_overlap
|
||||
|
||||
def __contains__(s, t):
|
||||
t = s.ctx.convert(t)
|
||||
return t.real in s.real and t.imag in s.imag
|
||||
|
||||
def _compare(s, t, ne=False):
|
||||
if not isinstance(t, s.ctx._types):
|
||||
try:
|
||||
t = s.ctx.convert(t)
|
||||
except:
|
||||
return NotImplemented
|
||||
if hasattr(t, '_mpi_'):
|
||||
tval = t._mpi_, mpi_zero
|
||||
elif hasattr(t, '_mpci_'):
|
||||
tval = t._mpci_
|
||||
if ne:
|
||||
return s._mpci_ != tval
|
||||
return s._mpci_ == tval
|
||||
|
||||
def __eq__(s, t): return s._compare(t)
|
||||
def __ne__(s, t): return s._compare(t, True)
|
||||
|
||||
def __lt__(s, t): raise TypeError("complex intervals cannot be ordered")
|
||||
__le__ = __gt__ = __ge__ = __lt__
|
||||
|
||||
def __neg__(s): return s.ctx.make_mpc(mpci_neg(s._mpci_, s.ctx.prec))
|
||||
def __pos__(s): return s.ctx.make_mpc(mpci_pos(s._mpci_, s.ctx.prec))
|
||||
def __abs__(s): return s.ctx.make_mpf(mpci_abs(s._mpci_, s.ctx.prec))
|
||||
|
||||
def ae(s, t, rel_eps=None, abs_eps=None):
|
||||
return s.ctx.almosteq(s, t, rel_eps, abs_eps)
|
||||
|
||||
def _binary_op(f_real, f_complex):
|
||||
def g_complex(ctx, sval, tval):
|
||||
return ctx.make_mpc(f_complex(sval, tval, ctx.prec))
|
||||
def g_real(ctx, sval, tval):
|
||||
try:
|
||||
return ctx.make_mpf(f_real(sval, tval, ctx.prec))
|
||||
except ComplexResult:
|
||||
sval = (sval, mpi_zero)
|
||||
tval = (tval, mpi_zero)
|
||||
return g_complex(ctx, sval, tval)
|
||||
def lop_real(s, t):
|
||||
if isinstance(t, _matrix): return NotImplemented
|
||||
ctx = s.ctx
|
||||
if not isinstance(t, ctx._types): t = ctx.convert(t)
|
||||
if hasattr(t, "_mpi_"): return g_real(ctx, s._mpi_, t._mpi_)
|
||||
if hasattr(t, "_mpci_"): return g_complex(ctx, (s._mpi_, mpi_zero), t._mpci_)
|
||||
return NotImplemented
|
||||
def rop_real(s, t):
|
||||
ctx = s.ctx
|
||||
if not isinstance(t, ctx._types): t = ctx.convert(t)
|
||||
if hasattr(t, "_mpi_"): return g_real(ctx, t._mpi_, s._mpi_)
|
||||
if hasattr(t, "_mpci_"): return g_complex(ctx, t._mpci_, (s._mpi_, mpi_zero))
|
||||
return NotImplemented
|
||||
def lop_complex(s, t):
|
||||
if isinstance(t, _matrix): return NotImplemented
|
||||
ctx = s.ctx
|
||||
if not isinstance(t, s.ctx._types):
|
||||
try:
|
||||
t = s.ctx.convert(t)
|
||||
except (ValueError, TypeError):
|
||||
return NotImplemented
|
||||
return g_complex(ctx, s._mpci_, t._mpci_)
|
||||
def rop_complex(s, t):
|
||||
ctx = s.ctx
|
||||
if not isinstance(t, s.ctx._types):
|
||||
t = s.ctx.convert(t)
|
||||
return g_complex(ctx, t._mpci_, s._mpci_)
|
||||
return lop_real, rop_real, lop_complex, rop_complex
|
||||
|
||||
ivmpf.__add__, ivmpf.__radd__, ivmpc.__add__, ivmpc.__radd__ = _binary_op(mpi_add, mpci_add)
|
||||
ivmpf.__sub__, ivmpf.__rsub__, ivmpc.__sub__, ivmpc.__rsub__ = _binary_op(mpi_sub, mpci_sub)
|
||||
ivmpf.__mul__, ivmpf.__rmul__, ivmpc.__mul__, ivmpc.__rmul__ = _binary_op(mpi_mul, mpci_mul)
|
||||
ivmpf.__div__, ivmpf.__rdiv__, ivmpc.__div__, ivmpc.__rdiv__ = _binary_op(mpi_div, mpci_div)
|
||||
ivmpf.__pow__, ivmpf.__rpow__, ivmpc.__pow__, ivmpc.__rpow__ = _binary_op(mpi_pow, mpci_pow)
|
||||
|
||||
ivmpf.__truediv__ = ivmpf.__div__; ivmpf.__rtruediv__ = ivmpf.__rdiv__
|
||||
ivmpc.__truediv__ = ivmpc.__div__; ivmpc.__rtruediv__ = ivmpc.__rdiv__
|
||||
|
||||
class ivmpf_constant(ivmpf):
|
||||
def __new__(cls, f):
|
||||
self = new(cls)
|
||||
self._f = f
|
||||
return self
|
||||
def _get_mpi_(self):
|
||||
prec = self.ctx._prec[0]
|
||||
a = self._f(prec, round_floor)
|
||||
b = self._f(prec, round_ceiling)
|
||||
return a, b
|
||||
_mpi_ = property(_get_mpi_)
|
||||
|
||||
class MPIntervalContext(StandardBaseContext):
|
||||
|
||||
def __init__(ctx):
|
||||
ctx.mpf = type('ivmpf', (ivmpf,), {})
|
||||
ctx.mpc = type('ivmpc', (ivmpc,), {})
|
||||
ctx._types = (ctx.mpf, ctx.mpc)
|
||||
ctx._constant = type('ivmpf_constant', (ivmpf_constant,), {})
|
||||
ctx._prec = [53]
|
||||
ctx._set_prec(53)
|
||||
ctx._constant._ctxdata = ctx.mpf._ctxdata = ctx.mpc._ctxdata = [ctx.mpf, new, ctx._prec]
|
||||
ctx._constant.ctx = ctx.mpf.ctx = ctx.mpc.ctx = ctx
|
||||
ctx.pretty = False
|
||||
StandardBaseContext.__init__(ctx)
|
||||
ctx._init_builtins()
|
||||
|
||||
def _mpi(ctx, a, b=None):
|
||||
if b is None:
|
||||
return ctx.mpf(a)
|
||||
return ctx.mpf((a,b))
|
||||
|
||||
def _init_builtins(ctx):
|
||||
ctx.one = ctx.mpf(1)
|
||||
ctx.zero = ctx.mpf(0)
|
||||
ctx.inf = ctx.mpf('inf')
|
||||
ctx.ninf = -ctx.inf
|
||||
ctx.nan = ctx.mpf('nan')
|
||||
ctx.j = ctx.mpc(0,1)
|
||||
ctx.exp = ctx._wrap_mpi_function(libmp.mpi_exp, libmp.mpci_exp)
|
||||
ctx.sqrt = ctx._wrap_mpi_function(libmp.mpi_sqrt)
|
||||
ctx.ln = ctx._wrap_mpi_function(libmp.mpi_log, libmp.mpci_log)
|
||||
ctx.cos = ctx._wrap_mpi_function(libmp.mpi_cos, libmp.mpci_cos)
|
||||
ctx.sin = ctx._wrap_mpi_function(libmp.mpi_sin, libmp.mpci_sin)
|
||||
ctx.tan = ctx._wrap_mpi_function(libmp.mpi_tan)
|
||||
ctx.gamma = ctx._wrap_mpi_function(libmp.mpi_gamma, libmp.mpci_gamma)
|
||||
ctx.loggamma = ctx._wrap_mpi_function(libmp.mpi_loggamma, libmp.mpci_loggamma)
|
||||
ctx.rgamma = ctx._wrap_mpi_function(libmp.mpi_rgamma, libmp.mpci_rgamma)
|
||||
ctx.factorial = ctx._wrap_mpi_function(libmp.mpi_factorial, libmp.mpci_factorial)
|
||||
ctx.fac = ctx.factorial
|
||||
|
||||
ctx.eps = ctx._constant(lambda prec, rnd: (0, MPZ_ONE, 1-prec, 1))
|
||||
ctx.pi = ctx._constant(libmp.mpf_pi)
|
||||
ctx.e = ctx._constant(libmp.mpf_e)
|
||||
ctx.ln2 = ctx._constant(libmp.mpf_ln2)
|
||||
ctx.ln10 = ctx._constant(libmp.mpf_ln10)
|
||||
ctx.phi = ctx._constant(libmp.mpf_phi)
|
||||
ctx.euler = ctx._constant(libmp.mpf_euler)
|
||||
ctx.catalan = ctx._constant(libmp.mpf_catalan)
|
||||
ctx.glaisher = ctx._constant(libmp.mpf_glaisher)
|
||||
ctx.khinchin = ctx._constant(libmp.mpf_khinchin)
|
||||
ctx.twinprime = ctx._constant(libmp.mpf_twinprime)
|
||||
|
||||
def _wrap_mpi_function(ctx, f_real, f_complex=None):
|
||||
def g(x, **kwargs):
|
||||
if kwargs:
|
||||
prec = kwargs.get('prec', ctx._prec[0])
|
||||
else:
|
||||
prec = ctx._prec[0]
|
||||
x = ctx.convert(x)
|
||||
if hasattr(x, "_mpi_"):
|
||||
return ctx.make_mpf(f_real(x._mpi_, prec))
|
||||
if hasattr(x, "_mpci_"):
|
||||
return ctx.make_mpc(f_complex(x._mpci_, prec))
|
||||
raise ValueError
|
||||
return g
|
||||
|
||||
@classmethod
|
||||
def _wrap_specfun(cls, name, f, wrap):
|
||||
if wrap:
|
||||
def f_wrapped(ctx, *args, **kwargs):
|
||||
convert = ctx.convert
|
||||
args = [convert(a) for a in args]
|
||||
prec = ctx.prec
|
||||
try:
|
||||
ctx.prec += 10
|
||||
retval = f(ctx, *args, **kwargs)
|
||||
finally:
|
||||
ctx.prec = prec
|
||||
return +retval
|
||||
else:
|
||||
f_wrapped = f
|
||||
setattr(cls, name, f_wrapped)
|
||||
|
||||
def _set_prec(ctx, n):
|
||||
ctx._prec[0] = max(1, int(n))
|
||||
ctx._dps = prec_to_dps(n)
|
||||
|
||||
def _set_dps(ctx, n):
|
||||
ctx._prec[0] = dps_to_prec(n)
|
||||
ctx._dps = max(1, int(n))
|
||||
|
||||
prec = property(lambda ctx: ctx._prec[0], _set_prec)
|
||||
dps = property(lambda ctx: ctx._dps, _set_dps)
|
||||
|
||||
def make_mpf(ctx, v):
|
||||
a = new(ctx.mpf)
|
||||
a._mpi_ = v
|
||||
return a
|
||||
|
||||
def make_mpc(ctx, v):
|
||||
a = new(ctx.mpc)
|
||||
a._mpci_ = v
|
||||
return a
|
||||
|
||||
def _mpq(ctx, pq):
|
||||
p, q = pq
|
||||
a = libmp.from_rational(p, q, ctx.prec, round_floor)
|
||||
b = libmp.from_rational(p, q, ctx.prec, round_ceiling)
|
||||
return ctx.make_mpf((a, b))
|
||||
|
||||
def convert(ctx, x):
|
||||
if isinstance(x, (ctx.mpf, ctx.mpc)):
|
||||
return x
|
||||
if isinstance(x, ctx._constant):
|
||||
return +x
|
||||
if isinstance(x, complex) or hasattr(x, "_mpc_"):
|
||||
re = ctx.convert(x.real)
|
||||
im = ctx.convert(x.imag)
|
||||
return ctx.mpc(re,im)
|
||||
if isinstance(x, basestring):
|
||||
v = mpi_from_str(x, ctx.prec)
|
||||
return ctx.make_mpf(v)
|
||||
if hasattr(x, "_mpi_"):
|
||||
a, b = x._mpi_
|
||||
else:
|
||||
try:
|
||||
a, b = x
|
||||
except (TypeError, ValueError):
|
||||
a = b = x
|
||||
if hasattr(a, "_mpi_"):
|
||||
a = a._mpi_[0]
|
||||
else:
|
||||
a = convert_mpf_(a, ctx.prec, round_floor)
|
||||
if hasattr(b, "_mpi_"):
|
||||
b = b._mpi_[1]
|
||||
else:
|
||||
b = convert_mpf_(b, ctx.prec, round_ceiling)
|
||||
if a == fnan or b == fnan:
|
||||
a = fninf
|
||||
b = finf
|
||||
assert mpf_le(a, b), "endpoints must be properly ordered"
|
||||
return ctx.make_mpf((a, b))
|
||||
|
||||
def nstr(ctx, x, n=5, **kwargs):
|
||||
x = ctx.convert(x)
|
||||
if hasattr(x, "_mpi_"):
|
||||
return libmp.mpi_to_str(x._mpi_, n, **kwargs)
|
||||
if hasattr(x, "_mpci_"):
|
||||
re = libmp.mpi_to_str(x._mpci_[0], n, **kwargs)
|
||||
im = libmp.mpi_to_str(x._mpci_[1], n, **kwargs)
|
||||
return "(%s + %s*j)" % (re, im)
|
||||
|
||||
def mag(ctx, x):
|
||||
x = ctx.convert(x)
|
||||
if isinstance(x, ctx.mpc):
|
||||
return max(ctx.mag(x.real), ctx.mag(x.imag)) + 1
|
||||
a, b = libmp.mpi_abs(x._mpi_)
|
||||
sign, man, exp, bc = b
|
||||
if man:
|
||||
return exp+bc
|
||||
if b == fzero:
|
||||
return ctx.ninf
|
||||
if b == fnan:
|
||||
return ctx.nan
|
||||
return ctx.inf
|
||||
|
||||
def isnan(ctx, x):
|
||||
return False
|
||||
|
||||
def isinf(ctx, x):
|
||||
return x == ctx.inf
|
||||
|
||||
def isint(ctx, x):
|
||||
x = ctx.convert(x)
|
||||
a, b = x._mpi_
|
||||
if a == b:
|
||||
sign, man, exp, bc = a
|
||||
if man:
|
||||
return exp >= 0
|
||||
return a == fzero
|
||||
return None
|
||||
|
||||
def ldexp(ctx, x, n):
|
||||
a, b = ctx.convert(x)._mpi_
|
||||
a = libmp.mpf_shift(a, n)
|
||||
b = libmp.mpf_shift(b, n)
|
||||
return ctx.make_mpf((a,b))
|
||||
|
||||
def absmin(ctx, x):
|
||||
return abs(ctx.convert(x)).a
|
||||
|
||||
def absmax(ctx, x):
|
||||
return abs(ctx.convert(x)).b
|
||||
|
||||
def atan2(ctx, y, x):
|
||||
y = ctx.convert(y)._mpi_
|
||||
x = ctx.convert(x)._mpi_
|
||||
return ctx.make_mpf(libmp.mpi_atan2(y,x,ctx.prec))
|
||||
|
||||
def _convert_param(ctx, x):
|
||||
if isinstance(x, libmp.int_types):
|
||||
return x, 'Z'
|
||||
if isinstance(x, tuple):
|
||||
p, q = x
|
||||
return (ctx.mpf(p) / ctx.mpf(q), 'R')
|
||||
x = ctx.convert(x)
|
||||
if isinstance(x, ctx.mpf):
|
||||
return x, 'R'
|
||||
if isinstance(x, ctx.mpc):
|
||||
return x, 'C'
|
||||
raise ValueError
|
||||
|
||||
def _is_real_type(ctx, z):
|
||||
return isinstance(z, ctx.mpf) or isinstance(z, int_types)
|
||||
|
||||
def _is_complex_type(ctx, z):
|
||||
return isinstance(z, ctx.mpc)
|
||||
|
||||
def hypsum(ctx, p, q, types, coeffs, z, maxterms=6000, **kwargs):
|
||||
coeffs = list(coeffs)
|
||||
num = range(p)
|
||||
den = range(p,p+q)
|
||||
#tol = ctx.eps
|
||||
s = t = ctx.one
|
||||
k = 0
|
||||
while 1:
|
||||
for i in num: t *= (coeffs[i]+k)
|
||||
for i in den: t /= (coeffs[i]+k)
|
||||
k += 1; t /= k; t *= z; s += t
|
||||
if t == 0:
|
||||
return s
|
||||
#if abs(t) < tol:
|
||||
# return s
|
||||
if k > maxterms:
|
||||
raise ctx.NoConvergence
|
||||
|
||||
|
||||
# Register with "numbers" ABC
|
||||
# We do not subclass, hence we do not use the @abstractmethod checks. While
|
||||
# this is less invasive it may turn out that we do not actually support
|
||||
# parts of the expected interfaces. See
|
||||
# http://docs.python.org/2/library/numbers.html for list of abstract
|
||||
# methods.
|
||||
try:
|
||||
import numbers
|
||||
numbers.Complex.register(ivmpc)
|
||||
numbers.Real.register(ivmpf)
|
||||
except ImportError:
|
||||
pass
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
from . import functions
|
||||
# Hack to update methods
|
||||
from . import factorials
|
||||
from . import hypergeometric
|
||||
from . import expintegrals
|
||||
from . import bessel
|
||||
from . import orthogonal
|
||||
from . import theta
|
||||
from . import elliptic
|
||||
from . import signals
|
||||
from . import zeta
|
||||
from . import rszeta
|
||||
from . import zetazeros
|
||||
from . import qfunctions
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,425 @@
|
||||
from .functions import defun, defun_wrapped
|
||||
|
||||
@defun_wrapped
|
||||
def _erf_complex(ctx, z):
|
||||
z2 = ctx.square_exp_arg(z, -1)
|
||||
#z2 = -z**2
|
||||
v = (2/ctx.sqrt(ctx.pi))*z * ctx.hyp1f1((1,2),(3,2), z2)
|
||||
if not ctx._re(z):
|
||||
v = ctx._im(v)*ctx.j
|
||||
return v
|
||||
|
||||
@defun_wrapped
|
||||
def _erfc_complex(ctx, z):
|
||||
if ctx.re(z) > 2:
|
||||
z2 = ctx.square_exp_arg(z)
|
||||
nz2 = ctx.fneg(z2, exact=True)
|
||||
v = ctx.exp(nz2)/ctx.sqrt(ctx.pi) * ctx.hyperu((1,2),(1,2), z2)
|
||||
else:
|
||||
v = 1 - ctx._erf_complex(z)
|
||||
if not ctx._re(z):
|
||||
v = 1+ctx._im(v)*ctx.j
|
||||
return v
|
||||
|
||||
@defun
|
||||
def erf(ctx, z):
|
||||
z = ctx.convert(z)
|
||||
if ctx._is_real_type(z):
|
||||
try:
|
||||
return ctx._erf(z)
|
||||
except NotImplementedError:
|
||||
pass
|
||||
if ctx._is_complex_type(z) and not z.imag:
|
||||
try:
|
||||
return type(z)(ctx._erf(z.real))
|
||||
except NotImplementedError:
|
||||
pass
|
||||
return ctx._erf_complex(z)
|
||||
|
||||
@defun
|
||||
def erfc(ctx, z):
|
||||
z = ctx.convert(z)
|
||||
if ctx._is_real_type(z):
|
||||
try:
|
||||
return ctx._erfc(z)
|
||||
except NotImplementedError:
|
||||
pass
|
||||
if ctx._is_complex_type(z) and not z.imag:
|
||||
try:
|
||||
return type(z)(ctx._erfc(z.real))
|
||||
except NotImplementedError:
|
||||
pass
|
||||
return ctx._erfc_complex(z)
|
||||
|
||||
@defun
|
||||
def square_exp_arg(ctx, z, mult=1, reciprocal=False):
|
||||
prec = ctx.prec*4+20
|
||||
if reciprocal:
|
||||
z2 = ctx.fmul(z, z, prec=prec)
|
||||
z2 = ctx.fdiv(ctx.one, z2, prec=prec)
|
||||
else:
|
||||
z2 = ctx.fmul(z, z, prec=prec)
|
||||
if mult != 1:
|
||||
z2 = ctx.fmul(z2, mult, exact=True)
|
||||
return z2
|
||||
|
||||
@defun_wrapped
|
||||
def erfi(ctx, z):
|
||||
if not z:
|
||||
return z
|
||||
z2 = ctx.square_exp_arg(z)
|
||||
v = (2/ctx.sqrt(ctx.pi)*z) * ctx.hyp1f1((1,2), (3,2), z2)
|
||||
if not ctx._re(z):
|
||||
v = ctx._im(v)*ctx.j
|
||||
return v
|
||||
|
||||
@defun_wrapped
|
||||
def erfinv(ctx, x):
|
||||
xre = ctx._re(x)
|
||||
if (xre != x) or (xre < -1) or (xre > 1):
|
||||
return ctx.bad_domain("erfinv(x) is defined only for -1 <= x <= 1")
|
||||
x = xre
|
||||
#if ctx.isnan(x): return x
|
||||
if not x: return x
|
||||
if x == 1: return ctx.inf
|
||||
if x == -1: return ctx.ninf
|
||||
if abs(x) < 0.9:
|
||||
a = 0.53728*x**3 + 0.813198*x
|
||||
else:
|
||||
# An asymptotic formula
|
||||
u = ctx.ln(2/ctx.pi/(abs(x)-1)**2)
|
||||
a = ctx.sign(x) * ctx.sqrt(u - ctx.ln(u))/ctx.sqrt(2)
|
||||
ctx.prec += 10
|
||||
return ctx.findroot(lambda t: ctx.erf(t)-x, a)
|
||||
|
||||
@defun_wrapped
|
||||
def npdf(ctx, x, mu=0, sigma=1):
|
||||
sigma = ctx.convert(sigma)
|
||||
return ctx.exp(-(x-mu)**2/(2*sigma**2)) / (sigma*ctx.sqrt(2*ctx.pi))
|
||||
|
||||
@defun_wrapped
|
||||
def ncdf(ctx, x, mu=0, sigma=1):
|
||||
a = (x-mu)/(sigma*ctx.sqrt(2))
|
||||
if a < 0:
|
||||
return ctx.erfc(-a)/2
|
||||
else:
|
||||
return (1+ctx.erf(a))/2
|
||||
|
||||
@defun_wrapped
|
||||
def betainc(ctx, a, b, x1=0, x2=1, regularized=False):
|
||||
if x1 == x2:
|
||||
v = 0
|
||||
elif not x1:
|
||||
if x1 == 0 and x2 == 1:
|
||||
v = ctx.beta(a, b)
|
||||
else:
|
||||
v = x2**a * ctx.hyp2f1(a, 1-b, a+1, x2) / a
|
||||
else:
|
||||
m, d = ctx.nint_distance(a)
|
||||
if m <= 0:
|
||||
if d < -ctx.prec:
|
||||
h = +ctx.eps
|
||||
ctx.prec *= 2
|
||||
a += h
|
||||
elif d < -4:
|
||||
ctx.prec -= d
|
||||
s1 = x2**a * ctx.hyp2f1(a,1-b,a+1,x2)
|
||||
s2 = x1**a * ctx.hyp2f1(a,1-b,a+1,x1)
|
||||
v = (s1 - s2) / a
|
||||
if regularized:
|
||||
v /= ctx.beta(a,b)
|
||||
return v
|
||||
|
||||
@defun
|
||||
def gammainc(ctx, z, a=0, b=None, regularized=False):
|
||||
regularized = bool(regularized)
|
||||
z = ctx.convert(z)
|
||||
if a is None:
|
||||
a = ctx.zero
|
||||
lower_modified = False
|
||||
else:
|
||||
a = ctx.convert(a)
|
||||
lower_modified = a != ctx.zero
|
||||
if b is None:
|
||||
b = ctx.inf
|
||||
upper_modified = False
|
||||
else:
|
||||
b = ctx.convert(b)
|
||||
upper_modified = b != ctx.inf
|
||||
# Complete gamma function
|
||||
if not (upper_modified or lower_modified):
|
||||
if regularized:
|
||||
if ctx.re(z) < 0:
|
||||
return ctx.inf
|
||||
elif ctx.re(z) > 0:
|
||||
return ctx.one
|
||||
else:
|
||||
return ctx.nan
|
||||
return ctx.gamma(z)
|
||||
if a == b:
|
||||
return ctx.zero
|
||||
# Standardize
|
||||
if ctx.re(a) > ctx.re(b):
|
||||
return -ctx.gammainc(z, b, a, regularized)
|
||||
# Generalized gamma
|
||||
if upper_modified and lower_modified:
|
||||
return +ctx._gamma3(z, a, b, regularized)
|
||||
# Upper gamma
|
||||
elif lower_modified:
|
||||
return ctx._upper_gamma(z, a, regularized)
|
||||
# Lower gamma
|
||||
elif upper_modified:
|
||||
return ctx._lower_gamma(z, b, regularized)
|
||||
|
||||
@defun
|
||||
def _lower_gamma(ctx, z, b, regularized=False):
|
||||
# Pole
|
||||
if ctx.isnpint(z):
|
||||
return type(z)(ctx.inf)
|
||||
G = [z] * regularized
|
||||
negb = ctx.fneg(b, exact=True)
|
||||
def h(z):
|
||||
T1 = [ctx.exp(negb), b, z], [1, z, -1], [], G, [1], [1+z], b
|
||||
return (T1,)
|
||||
return ctx.hypercomb(h, [z])
|
||||
|
||||
@defun
|
||||
def _upper_gamma(ctx, z, a, regularized=False):
|
||||
# Fast integer case, when available
|
||||
if ctx.isint(z):
|
||||
try:
|
||||
if regularized:
|
||||
# Gamma pole
|
||||
if ctx.isnpint(z):
|
||||
return type(z)(ctx.zero)
|
||||
orig = ctx.prec
|
||||
try:
|
||||
ctx.prec += 10
|
||||
return ctx._gamma_upper_int(z, a) / ctx.gamma(z)
|
||||
finally:
|
||||
ctx.prec = orig
|
||||
else:
|
||||
return ctx._gamma_upper_int(z, a)
|
||||
except NotImplementedError:
|
||||
pass
|
||||
# hypercomb is unable to detect the exact zeros, so handle them here
|
||||
if z == 2 and a == -1:
|
||||
return (z+a)*0
|
||||
if z == 3 and (a == -1-1j or a == -1+1j):
|
||||
return (z+a)*0
|
||||
nega = ctx.fneg(a, exact=True)
|
||||
G = [z] * regularized
|
||||
# Use 2F0 series when possible; fall back to lower gamma representation
|
||||
try:
|
||||
def h(z):
|
||||
r = z-1
|
||||
return [([ctx.exp(nega), a], [1, r], [], G, [1, -r], [], 1/nega)]
|
||||
return ctx.hypercomb(h, [z], force_series=True)
|
||||
except ctx.NoConvergence:
|
||||
def h(z):
|
||||
T1 = [], [1, z-1], [z], G, [], [], 0
|
||||
T2 = [-ctx.exp(nega), a, z], [1, z, -1], [], G, [1], [1+z], a
|
||||
return T1, T2
|
||||
return ctx.hypercomb(h, [z])
|
||||
|
||||
@defun
|
||||
def _gamma3(ctx, z, a, b, regularized=False):
|
||||
pole = ctx.isnpint(z)
|
||||
if regularized and pole:
|
||||
return ctx.zero
|
||||
try:
|
||||
ctx.prec += 15
|
||||
# We don't know in advance whether it's better to write as a difference
|
||||
# of lower or upper gamma functions, so try both
|
||||
T1 = ctx.gammainc(z, a, regularized=regularized)
|
||||
T2 = ctx.gammainc(z, b, regularized=regularized)
|
||||
R = T1 - T2
|
||||
if ctx.mag(R) - max(ctx.mag(T1), ctx.mag(T2)) > -10:
|
||||
return R
|
||||
if not pole:
|
||||
T1 = ctx.gammainc(z, 0, b, regularized=regularized)
|
||||
T2 = ctx.gammainc(z, 0, a, regularized=regularized)
|
||||
R = T1 - T2
|
||||
# May be ok, but should probably at least print a warning
|
||||
# about possible cancellation
|
||||
if 1: #ctx.mag(R) - max(ctx.mag(T1), ctx.mag(T2)) > -10:
|
||||
return R
|
||||
finally:
|
||||
ctx.prec -= 15
|
||||
raise NotImplementedError
|
||||
|
||||
@defun_wrapped
|
||||
def expint(ctx, n, z):
|
||||
if ctx.isint(n) and ctx._is_real_type(z):
|
||||
try:
|
||||
return ctx._expint_int(n, z)
|
||||
except NotImplementedError:
|
||||
pass
|
||||
if ctx.isnan(n) or ctx.isnan(z):
|
||||
return z*n
|
||||
if z == ctx.inf:
|
||||
return 1/z
|
||||
if z == 0:
|
||||
# integral from 1 to infinity of t^n
|
||||
if ctx.re(n) <= 1:
|
||||
# TODO: reasonable sign of infinity
|
||||
return type(z)(ctx.inf)
|
||||
else:
|
||||
return ctx.one/(n-1)
|
||||
if n == 0:
|
||||
return ctx.exp(-z)/z
|
||||
if n == -1:
|
||||
return ctx.exp(-z)*(z+1)/z**2
|
||||
return z**(n-1) * ctx.gammainc(1-n, z)
|
||||
|
||||
@defun_wrapped
|
||||
def li(ctx, z, offset=False):
|
||||
if offset:
|
||||
if z == 2:
|
||||
return ctx.zero
|
||||
return ctx.ei(ctx.ln(z)) - ctx.ei(ctx.ln2)
|
||||
if not z:
|
||||
return z
|
||||
if z == 1:
|
||||
return ctx.ninf
|
||||
return ctx.ei(ctx.ln(z))
|
||||
|
||||
@defun
|
||||
def ei(ctx, z):
|
||||
try:
|
||||
return ctx._ei(z)
|
||||
except NotImplementedError:
|
||||
return ctx._ei_generic(z)
|
||||
|
||||
@defun_wrapped
|
||||
def _ei_generic(ctx, z):
|
||||
# Note: the following is currently untested because mp and fp
|
||||
# both use special-case ei code
|
||||
if z == ctx.inf:
|
||||
return z
|
||||
if z == ctx.ninf:
|
||||
return ctx.zero
|
||||
if ctx.mag(z) > 1:
|
||||
try:
|
||||
r = ctx.one/z
|
||||
v = ctx.exp(z)*ctx.hyper([1,1],[],r,
|
||||
maxterms=ctx.prec, force_series=True)/z
|
||||
im = ctx._im(z)
|
||||
if im > 0:
|
||||
v += ctx.pi*ctx.j
|
||||
if im < 0:
|
||||
v -= ctx.pi*ctx.j
|
||||
return v
|
||||
except ctx.NoConvergence:
|
||||
pass
|
||||
v = z*ctx.hyp2f2(1,1,2,2,z) + ctx.euler
|
||||
if ctx._im(z):
|
||||
v += 0.5*(ctx.log(z) - ctx.log(ctx.one/z))
|
||||
else:
|
||||
v += ctx.log(abs(z))
|
||||
return v
|
||||
|
||||
@defun
|
||||
def e1(ctx, z):
|
||||
try:
|
||||
return ctx._e1(z)
|
||||
except NotImplementedError:
|
||||
return ctx.expint(1, z)
|
||||
|
||||
@defun
|
||||
def ci(ctx, z):
|
||||
try:
|
||||
return ctx._ci(z)
|
||||
except NotImplementedError:
|
||||
return ctx._ci_generic(z)
|
||||
|
||||
@defun_wrapped
|
||||
def _ci_generic(ctx, z):
|
||||
if ctx.isinf(z):
|
||||
if z == ctx.inf: return ctx.zero
|
||||
if z == ctx.ninf: return ctx.pi*1j
|
||||
jz = ctx.fmul(ctx.j,z,exact=True)
|
||||
njz = ctx.fneg(jz,exact=True)
|
||||
v = 0.5*(ctx.ei(jz) + ctx.ei(njz))
|
||||
zreal = ctx._re(z)
|
||||
zimag = ctx._im(z)
|
||||
if zreal == 0:
|
||||
if zimag > 0: v += ctx.pi*0.5j
|
||||
if zimag < 0: v -= ctx.pi*0.5j
|
||||
if zreal < 0:
|
||||
if zimag >= 0: v += ctx.pi*1j
|
||||
if zimag < 0: v -= ctx.pi*1j
|
||||
if ctx._is_real_type(z) and zreal > 0:
|
||||
v = ctx._re(v)
|
||||
return v
|
||||
|
||||
@defun
|
||||
def si(ctx, z):
|
||||
try:
|
||||
return ctx._si(z)
|
||||
except NotImplementedError:
|
||||
return ctx._si_generic(z)
|
||||
|
||||
@defun_wrapped
|
||||
def _si_generic(ctx, z):
|
||||
if ctx.isinf(z):
|
||||
if z == ctx.inf: return 0.5*ctx.pi
|
||||
if z == ctx.ninf: return -0.5*ctx.pi
|
||||
# Suffers from cancellation near 0
|
||||
if ctx.mag(z) >= -1:
|
||||
jz = ctx.fmul(ctx.j,z,exact=True)
|
||||
njz = ctx.fneg(jz,exact=True)
|
||||
v = (-0.5j)*(ctx.ei(jz) - ctx.ei(njz))
|
||||
zreal = ctx._re(z)
|
||||
if zreal > 0:
|
||||
v -= 0.5*ctx.pi
|
||||
if zreal < 0:
|
||||
v += 0.5*ctx.pi
|
||||
if ctx._is_real_type(z):
|
||||
v = ctx._re(v)
|
||||
return v
|
||||
else:
|
||||
return z*ctx.hyp1f2((1,2),(3,2),(3,2),-0.25*z*z)
|
||||
|
||||
@defun_wrapped
|
||||
def chi(ctx, z):
|
||||
nz = ctx.fneg(z, exact=True)
|
||||
v = 0.5*(ctx.ei(z) + ctx.ei(nz))
|
||||
zreal = ctx._re(z)
|
||||
zimag = ctx._im(z)
|
||||
if zimag > 0:
|
||||
v += ctx.pi*0.5j
|
||||
elif zimag < 0:
|
||||
v -= ctx.pi*0.5j
|
||||
elif zreal < 0:
|
||||
v += ctx.pi*1j
|
||||
return v
|
||||
|
||||
@defun_wrapped
|
||||
def shi(ctx, z):
|
||||
# Suffers from cancellation near 0
|
||||
if ctx.mag(z) >= -1:
|
||||
nz = ctx.fneg(z, exact=True)
|
||||
v = 0.5*(ctx.ei(z) - ctx.ei(nz))
|
||||
zimag = ctx._im(z)
|
||||
if zimag > 0: v -= 0.5j*ctx.pi
|
||||
if zimag < 0: v += 0.5j*ctx.pi
|
||||
return v
|
||||
else:
|
||||
return z * ctx.hyp1f2((1,2),(3,2),(3,2),0.25*z*z)
|
||||
|
||||
@defun_wrapped
|
||||
def fresnels(ctx, z):
|
||||
if z == ctx.inf:
|
||||
return ctx.mpf(0.5)
|
||||
if z == ctx.ninf:
|
||||
return ctx.mpf(-0.5)
|
||||
return ctx.pi*z**3/6*ctx.hyp1f2((3,4),(3,2),(7,4),-ctx.pi**2*z**4/16)
|
||||
|
||||
@defun_wrapped
|
||||
def fresnelc(ctx, z):
|
||||
if z == ctx.inf:
|
||||
return ctx.mpf(0.5)
|
||||
if z == ctx.ninf:
|
||||
return ctx.mpf(-0.5)
|
||||
return z*ctx.hyp1f2((1,4),(1,2),(5,4),-ctx.pi**2*z**4/16)
|
||||
@@ -0,0 +1,187 @@
|
||||
from ..libmp.backend import xrange
|
||||
from .functions import defun, defun_wrapped
|
||||
|
||||
@defun
|
||||
def gammaprod(ctx, a, b, _infsign=False):
|
||||
a = [ctx.convert(x) for x in a]
|
||||
b = [ctx.convert(x) for x in b]
|
||||
poles_num = []
|
||||
poles_den = []
|
||||
regular_num = []
|
||||
regular_den = []
|
||||
for x in a: [regular_num, poles_num][ctx.isnpint(x)].append(x)
|
||||
for x in b: [regular_den, poles_den][ctx.isnpint(x)].append(x)
|
||||
# One more pole in numerator or denominator gives 0 or inf
|
||||
if len(poles_num) < len(poles_den): return ctx.zero
|
||||
if len(poles_num) > len(poles_den):
|
||||
# Get correct sign of infinity for x+h, h -> 0 from above
|
||||
# XXX: hack, this should be done properly
|
||||
if _infsign:
|
||||
a = [x and x*(1+ctx.eps) or x+ctx.eps for x in poles_num]
|
||||
b = [x and x*(1+ctx.eps) or x+ctx.eps for x in poles_den]
|
||||
return ctx.sign(ctx.gammaprod(a+regular_num,b+regular_den)) * ctx.inf
|
||||
else:
|
||||
return ctx.inf
|
||||
# All poles cancel
|
||||
# lim G(i)/G(j) = (-1)**(i+j) * gamma(1-j) / gamma(1-i)
|
||||
p = ctx.one
|
||||
orig = ctx.prec
|
||||
try:
|
||||
ctx.prec = orig + 15
|
||||
while poles_num:
|
||||
i = poles_num.pop()
|
||||
j = poles_den.pop()
|
||||
p *= (-1)**(i+j) * ctx.gamma(1-j) / ctx.gamma(1-i)
|
||||
for x in regular_num: p *= ctx.gamma(x)
|
||||
for x in regular_den: p /= ctx.gamma(x)
|
||||
finally:
|
||||
ctx.prec = orig
|
||||
return +p
|
||||
|
||||
@defun
|
||||
def beta(ctx, x, y):
|
||||
x = ctx.convert(x)
|
||||
y = ctx.convert(y)
|
||||
if ctx.isinf(y):
|
||||
x, y = y, x
|
||||
if ctx.isinf(x):
|
||||
if x == ctx.inf and not ctx._im(y):
|
||||
if y == ctx.ninf:
|
||||
return ctx.nan
|
||||
if y > 0:
|
||||
return ctx.zero
|
||||
if ctx.isint(y):
|
||||
return ctx.nan
|
||||
if y < 0:
|
||||
return ctx.sign(ctx.gamma(y)) * ctx.inf
|
||||
return ctx.nan
|
||||
xy = ctx.fadd(x, y, prec=2*ctx.prec)
|
||||
return ctx.gammaprod([x, y], [xy])
|
||||
|
||||
@defun
|
||||
def binomial(ctx, n, k):
|
||||
n1 = ctx.fadd(n, 1, prec=2*ctx.prec)
|
||||
k1 = ctx.fadd(k, 1, prec=2*ctx.prec)
|
||||
nk1 = ctx.fsub(n1, k, prec=2*ctx.prec)
|
||||
return ctx.gammaprod([n1], [k1, nk1])
|
||||
|
||||
@defun
|
||||
def rf(ctx, x, n):
|
||||
xn = ctx.fadd(x, n, prec=2*ctx.prec)
|
||||
return ctx.gammaprod([xn], [x])
|
||||
|
||||
@defun
|
||||
def ff(ctx, x, n):
|
||||
x1 = ctx.fadd(x, 1, prec=2*ctx.prec)
|
||||
xn1 = ctx.fadd(ctx.fsub(x, n, prec=2*ctx.prec), 1, prec=2*ctx.prec)
|
||||
return ctx.gammaprod([x1], [xn1])
|
||||
|
||||
@defun_wrapped
|
||||
def fac2(ctx, x):
|
||||
if ctx.isinf(x):
|
||||
if x == ctx.inf:
|
||||
return x
|
||||
return ctx.nan
|
||||
return 2**(x/2)*(ctx.pi/2)**((ctx.cospi(x)-1)/4)*ctx.gamma(x/2+1)
|
||||
|
||||
@defun_wrapped
|
||||
def barnesg(ctx, z):
|
||||
if ctx.isinf(z):
|
||||
if z == ctx.inf:
|
||||
return z
|
||||
return ctx.nan
|
||||
if ctx.isnan(z):
|
||||
return z
|
||||
if (not ctx._im(z)) and ctx._re(z) <= 0 and ctx.isint(ctx._re(z)):
|
||||
return z*0
|
||||
# Account for size (would not be needed if computing log(G))
|
||||
if abs(z) > 5:
|
||||
ctx.dps += 2*ctx.log(abs(z),2)
|
||||
# Reflection formula
|
||||
if ctx.re(z) < -ctx.dps:
|
||||
w = 1-z
|
||||
pi2 = 2*ctx.pi
|
||||
u = ctx.expjpi(2*w)
|
||||
v = ctx.j*ctx.pi/12 - ctx.j*ctx.pi*w**2/2 + w*ctx.ln(1-u) - \
|
||||
ctx.j*ctx.polylog(2, u)/pi2
|
||||
v = ctx.barnesg(2-z)*ctx.exp(v)/pi2**w
|
||||
if ctx._is_real_type(z):
|
||||
v = ctx._re(v)
|
||||
return v
|
||||
# Estimate terms for asymptotic expansion
|
||||
# TODO: fixme, obviously
|
||||
N = ctx.dps // 2 + 5
|
||||
G = 1
|
||||
while abs(z) < N or ctx.re(z) < 1:
|
||||
G /= ctx.gamma(z)
|
||||
z += 1
|
||||
z -= 1
|
||||
s = ctx.mpf(1)/12
|
||||
s -= ctx.log(ctx.glaisher)
|
||||
s += z*ctx.log(2*ctx.pi)/2
|
||||
s += (z**2/2-ctx.mpf(1)/12)*ctx.log(z)
|
||||
s -= 3*z**2/4
|
||||
z2k = z2 = z**2
|
||||
for k in xrange(1, N+1):
|
||||
t = ctx.bernoulli(2*k+2) / (4*k*(k+1)*z2k)
|
||||
if abs(t) < ctx.eps:
|
||||
#print k, N # check how many terms were needed
|
||||
break
|
||||
z2k *= z2
|
||||
s += t
|
||||
#if k == N:
|
||||
# print "warning: series for barnesg failed to converge", ctx.dps
|
||||
return G*ctx.exp(s)
|
||||
|
||||
@defun
|
||||
def superfac(ctx, z):
|
||||
return ctx.barnesg(z+2)
|
||||
|
||||
@defun_wrapped
|
||||
def hyperfac(ctx, z):
|
||||
# XXX: estimate needed extra bits accurately
|
||||
if z == ctx.inf:
|
||||
return z
|
||||
if abs(z) > 5:
|
||||
extra = 4*int(ctx.log(abs(z),2))
|
||||
else:
|
||||
extra = 0
|
||||
ctx.prec += extra
|
||||
if not ctx._im(z) and ctx._re(z) < 0 and ctx.isint(ctx._re(z)):
|
||||
n = int(ctx.re(z))
|
||||
h = ctx.hyperfac(-n-1)
|
||||
if ((n+1)//2) & 1:
|
||||
h = -h
|
||||
if ctx._is_complex_type(z):
|
||||
return h + 0j
|
||||
return h
|
||||
zp1 = z+1
|
||||
# Wrong branch cut
|
||||
#v = ctx.gamma(zp1)**z
|
||||
#ctx.prec -= extra
|
||||
#return v / ctx.barnesg(zp1)
|
||||
v = ctx.exp(z*ctx.loggamma(zp1))
|
||||
ctx.prec -= extra
|
||||
return v / ctx.barnesg(zp1)
|
||||
|
||||
'''
|
||||
@defun
|
||||
def psi0(ctx, z):
|
||||
"""Shortcut for psi(0,z) (the digamma function)"""
|
||||
return ctx.psi(0, z)
|
||||
|
||||
@defun
|
||||
def psi1(ctx, z):
|
||||
"""Shortcut for psi(1,z) (the trigamma function)"""
|
||||
return ctx.psi(1, z)
|
||||
|
||||
@defun
|
||||
def psi2(ctx, z):
|
||||
"""Shortcut for psi(2,z) (the tetragamma function)"""
|
||||
return ctx.psi(2, z)
|
||||
|
||||
@defun
|
||||
def psi3(ctx, z):
|
||||
"""Shortcut for psi(3,z) (the pentagamma function)"""
|
||||
return ctx.psi(3, z)
|
||||
'''
|
||||
@@ -0,0 +1,645 @@
|
||||
from ..libmp.backend import xrange
|
||||
|
||||
class SpecialFunctions(object):
|
||||
"""
|
||||
This class implements special functions using high-level code.
|
||||
|
||||
Elementary and some other functions (e.g. gamma function, basecase
|
||||
hypergeometric series) are assumed to be predefined by the context as
|
||||
"builtins" or "low-level" functions.
|
||||
"""
|
||||
defined_functions = {}
|
||||
|
||||
# The series for the Jacobi theta functions converge for |q| < 1;
|
||||
# in the current implementation they throw a ValueError for
|
||||
# abs(q) > THETA_Q_LIM
|
||||
THETA_Q_LIM = 1 - 10**-7
|
||||
|
||||
def __init__(self):
|
||||
cls = self.__class__
|
||||
for name in cls.defined_functions:
|
||||
f, wrap = cls.defined_functions[name]
|
||||
cls._wrap_specfun(name, f, wrap)
|
||||
|
||||
self.mpq_1 = self._mpq((1,1))
|
||||
self.mpq_0 = self._mpq((0,1))
|
||||
self.mpq_1_2 = self._mpq((1,2))
|
||||
self.mpq_3_2 = self._mpq((3,2))
|
||||
self.mpq_1_4 = self._mpq((1,4))
|
||||
self.mpq_1_16 = self._mpq((1,16))
|
||||
self.mpq_3_16 = self._mpq((3,16))
|
||||
self.mpq_5_2 = self._mpq((5,2))
|
||||
self.mpq_3_4 = self._mpq((3,4))
|
||||
self.mpq_7_4 = self._mpq((7,4))
|
||||
self.mpq_5_4 = self._mpq((5,4))
|
||||
self.mpq_1_3 = self._mpq((1,3))
|
||||
self.mpq_2_3 = self._mpq((2,3))
|
||||
self.mpq_4_3 = self._mpq((4,3))
|
||||
self.mpq_1_6 = self._mpq((1,6))
|
||||
self.mpq_5_6 = self._mpq((5,6))
|
||||
self.mpq_5_3 = self._mpq((5,3))
|
||||
|
||||
self._misc_const_cache = {}
|
||||
|
||||
self._aliases.update({
|
||||
'phase' : 'arg',
|
||||
'conjugate' : 'conj',
|
||||
'nthroot' : 'root',
|
||||
'polygamma' : 'psi',
|
||||
'hurwitz' : 'zeta',
|
||||
#'digamma' : 'psi0',
|
||||
#'trigamma' : 'psi1',
|
||||
#'tetragamma' : 'psi2',
|
||||
#'pentagamma' : 'psi3',
|
||||
'fibonacci' : 'fib',
|
||||
'factorial' : 'fac',
|
||||
})
|
||||
|
||||
self.zetazero_memoized = self.memoize(self.zetazero)
|
||||
|
||||
# Default -- do nothing
|
||||
@classmethod
|
||||
def _wrap_specfun(cls, name, f, wrap):
|
||||
setattr(cls, name, f)
|
||||
|
||||
# Optional fast versions of common functions in common cases.
|
||||
# If not overridden, default (generic hypergeometric series)
|
||||
# implementations will be used
|
||||
def _besselj(ctx, n, z): raise NotImplementedError
|
||||
def _erf(ctx, z): raise NotImplementedError
|
||||
def _erfc(ctx, z): raise NotImplementedError
|
||||
def _gamma_upper_int(ctx, z, a): raise NotImplementedError
|
||||
def _expint_int(ctx, n, z): raise NotImplementedError
|
||||
def _zeta(ctx, s): raise NotImplementedError
|
||||
def _zetasum_fast(ctx, s, a, n, derivatives, reflect): raise NotImplementedError
|
||||
def _ei(ctx, z): raise NotImplementedError
|
||||
def _e1(ctx, z): raise NotImplementedError
|
||||
def _ci(ctx, z): raise NotImplementedError
|
||||
def _si(ctx, z): raise NotImplementedError
|
||||
def _altzeta(ctx, s): raise NotImplementedError
|
||||
|
||||
def defun_wrapped(f):
|
||||
SpecialFunctions.defined_functions[f.__name__] = f, True
|
||||
return f
|
||||
|
||||
def defun(f):
|
||||
SpecialFunctions.defined_functions[f.__name__] = f, False
|
||||
return f
|
||||
|
||||
def defun_static(f):
|
||||
setattr(SpecialFunctions, f.__name__, f)
|
||||
return f
|
||||
|
||||
@defun_wrapped
|
||||
def cot(ctx, z): return ctx.one / ctx.tan(z)
|
||||
|
||||
@defun_wrapped
|
||||
def sec(ctx, z): return ctx.one / ctx.cos(z)
|
||||
|
||||
@defun_wrapped
|
||||
def csc(ctx, z): return ctx.one / ctx.sin(z)
|
||||
|
||||
@defun_wrapped
|
||||
def coth(ctx, z): return ctx.one / ctx.tanh(z)
|
||||
|
||||
@defun_wrapped
|
||||
def sech(ctx, z): return ctx.one / ctx.cosh(z)
|
||||
|
||||
@defun_wrapped
|
||||
def csch(ctx, z): return ctx.one / ctx.sinh(z)
|
||||
|
||||
@defun_wrapped
|
||||
def acot(ctx, z):
|
||||
if not z:
|
||||
return ctx.pi * 0.5
|
||||
else:
|
||||
return ctx.atan(ctx.one / z)
|
||||
|
||||
@defun_wrapped
|
||||
def asec(ctx, z): return ctx.acos(ctx.one / z)
|
||||
|
||||
@defun_wrapped
|
||||
def acsc(ctx, z): return ctx.asin(ctx.one / z)
|
||||
|
||||
@defun_wrapped
|
||||
def acoth(ctx, z):
|
||||
if not z:
|
||||
return ctx.pi * 0.5j
|
||||
else:
|
||||
return ctx.atanh(ctx.one / z)
|
||||
|
||||
|
||||
@defun_wrapped
|
||||
def asech(ctx, z): return ctx.acosh(ctx.one / z)
|
||||
|
||||
@defun_wrapped
|
||||
def acsch(ctx, z): return ctx.asinh(ctx.one / z)
|
||||
|
||||
@defun
|
||||
def sign(ctx, x):
|
||||
x = ctx.convert(x)
|
||||
if not x or ctx.isnan(x):
|
||||
return x
|
||||
if ctx._is_real_type(x):
|
||||
if x > 0:
|
||||
return ctx.one
|
||||
else:
|
||||
return -ctx.one
|
||||
return x / abs(x)
|
||||
|
||||
@defun
|
||||
def agm(ctx, a, b=1):
|
||||
if b == 1:
|
||||
return ctx.agm1(a)
|
||||
a = ctx.convert(a)
|
||||
b = ctx.convert(b)
|
||||
return ctx._agm(a, b)
|
||||
|
||||
@defun_wrapped
|
||||
def sinc(ctx, x):
|
||||
if ctx.isinf(x):
|
||||
return 1/x
|
||||
if not x:
|
||||
return x+1
|
||||
return ctx.sin(x)/x
|
||||
|
||||
@defun_wrapped
|
||||
def sincpi(ctx, x):
|
||||
if ctx.isinf(x):
|
||||
return 1/x
|
||||
if not x:
|
||||
return x+1
|
||||
return ctx.sinpi(x)/(ctx.pi*x)
|
||||
|
||||
# TODO: tests; improve implementation
|
||||
@defun_wrapped
|
||||
def expm1(ctx, x):
|
||||
if not x:
|
||||
return ctx.zero
|
||||
# exp(x) - 1 ~ x
|
||||
if ctx.mag(x) < -ctx.prec:
|
||||
return x + 0.5*x**2
|
||||
# TODO: accurately eval the smaller of the real/imag parts
|
||||
return ctx.sum_accurately(lambda: iter([ctx.exp(x),-1]),1)
|
||||
|
||||
@defun_wrapped
|
||||
def log1p(ctx, x):
|
||||
if not x:
|
||||
return ctx.zero
|
||||
if ctx.mag(x) < -ctx.prec:
|
||||
return x - 0.5*x**2
|
||||
return ctx.log(ctx.fadd(1, x, prec=2*ctx.prec))
|
||||
|
||||
@defun_wrapped
|
||||
def powm1(ctx, x, y):
|
||||
mag = ctx.mag
|
||||
one = ctx.one
|
||||
w = x**y - one
|
||||
M = mag(w)
|
||||
# Only moderate cancellation
|
||||
if M > -8:
|
||||
return w
|
||||
# Check for the only possible exact cases
|
||||
if not w:
|
||||
if (not y) or (x in (1, -1, 1j, -1j) and ctx.isint(y)):
|
||||
return w
|
||||
x1 = x - one
|
||||
magy = mag(y)
|
||||
lnx = ctx.ln(x)
|
||||
# Small y: x^y - 1 ~ log(x)*y + O(log(x)^2 * y^2)
|
||||
if magy + mag(lnx) < -ctx.prec:
|
||||
return lnx*y + (lnx*y)**2/2
|
||||
# TODO: accurately eval the smaller of the real/imag part
|
||||
return ctx.sum_accurately(lambda: iter([x**y, -1]), 1)
|
||||
|
||||
@defun
|
||||
def _rootof1(ctx, k, n):
|
||||
k = int(k)
|
||||
n = int(n)
|
||||
k %= n
|
||||
if not k:
|
||||
return ctx.one
|
||||
elif 2*k == n:
|
||||
return -ctx.one
|
||||
elif 4*k == n:
|
||||
return ctx.j
|
||||
elif 4*k == 3*n:
|
||||
return -ctx.j
|
||||
return ctx.expjpi(2*ctx.mpf(k)/n)
|
||||
|
||||
@defun
|
||||
def root(ctx, x, n, k=0):
|
||||
n = int(n)
|
||||
x = ctx.convert(x)
|
||||
if k:
|
||||
# Special case: there is an exact real root
|
||||
if (n & 1 and 2*k == n-1) and (not ctx.im(x)) and (ctx.re(x) < 0):
|
||||
return -ctx.root(-x, n)
|
||||
# Multiply by root of unity
|
||||
prec = ctx.prec
|
||||
try:
|
||||
ctx.prec += 10
|
||||
v = ctx.root(x, n, 0) * ctx._rootof1(k, n)
|
||||
finally:
|
||||
ctx.prec = prec
|
||||
return +v
|
||||
return ctx._nthroot(x, n)
|
||||
|
||||
@defun
|
||||
def unitroots(ctx, n, primitive=False):
|
||||
gcd = ctx._gcd
|
||||
prec = ctx.prec
|
||||
try:
|
||||
ctx.prec += 10
|
||||
if primitive:
|
||||
v = [ctx._rootof1(k,n) for k in range(n) if gcd(k,n) == 1]
|
||||
else:
|
||||
# TODO: this can be done *much* faster
|
||||
v = [ctx._rootof1(k,n) for k in range(n)]
|
||||
finally:
|
||||
ctx.prec = prec
|
||||
return [+x for x in v]
|
||||
|
||||
@defun
|
||||
def arg(ctx, x):
|
||||
x = ctx.convert(x)
|
||||
re = ctx._re(x)
|
||||
im = ctx._im(x)
|
||||
return ctx.atan2(im, re)
|
||||
|
||||
@defun
|
||||
def fabs(ctx, x):
|
||||
return abs(ctx.convert(x))
|
||||
|
||||
@defun
|
||||
def re(ctx, x):
|
||||
x = ctx.convert(x)
|
||||
if hasattr(x, "real"): # py2.5 doesn't have .real/.imag for all numbers
|
||||
return x.real
|
||||
return x
|
||||
|
||||
@defun
|
||||
def im(ctx, x):
|
||||
x = ctx.convert(x)
|
||||
if hasattr(x, "imag"): # py2.5 doesn't have .real/.imag for all numbers
|
||||
return x.imag
|
||||
return ctx.zero
|
||||
|
||||
@defun
|
||||
def conj(ctx, x):
|
||||
x = ctx.convert(x)
|
||||
try:
|
||||
return x.conjugate()
|
||||
except AttributeError:
|
||||
return x
|
||||
|
||||
@defun
|
||||
def polar(ctx, z):
|
||||
return (ctx.fabs(z), ctx.arg(z))
|
||||
|
||||
@defun_wrapped
|
||||
def rect(ctx, r, phi):
|
||||
return r * ctx.mpc(*ctx.cos_sin(phi))
|
||||
|
||||
@defun
|
||||
def log(ctx, x, b=None):
|
||||
if b is None:
|
||||
return ctx.ln(x)
|
||||
wp = ctx.prec + 20
|
||||
return ctx.ln(x, prec=wp) / ctx.ln(b, prec=wp)
|
||||
|
||||
@defun
|
||||
def log10(ctx, x):
|
||||
return ctx.log(x, 10)
|
||||
|
||||
@defun
|
||||
def fmod(ctx, x, y):
|
||||
return ctx.convert(x) % ctx.convert(y)
|
||||
|
||||
@defun
|
||||
def degrees(ctx, x):
|
||||
return x / ctx.degree
|
||||
|
||||
@defun
|
||||
def radians(ctx, x):
|
||||
return x * ctx.degree
|
||||
|
||||
def _lambertw_special(ctx, z, k):
|
||||
# W(0,0) = 0; all other branches are singular
|
||||
if not z:
|
||||
if not k:
|
||||
return z
|
||||
return ctx.ninf + z
|
||||
if z == ctx.inf:
|
||||
if k == 0:
|
||||
return z
|
||||
else:
|
||||
return z + 2*k*ctx.pi*ctx.j
|
||||
if z == ctx.ninf:
|
||||
return (-z) + (2*k+1)*ctx.pi*ctx.j
|
||||
# Some kind of nan or complex inf/nan?
|
||||
return ctx.ln(z)
|
||||
|
||||
import math
|
||||
import cmath
|
||||
|
||||
def _lambertw_approx_hybrid(z, k):
|
||||
imag_sign = 0
|
||||
if hasattr(z, "imag"):
|
||||
x = float(z.real)
|
||||
y = z.imag
|
||||
if y:
|
||||
imag_sign = (-1) ** (y < 0)
|
||||
y = float(y)
|
||||
else:
|
||||
x = float(z)
|
||||
y = 0.0
|
||||
imag_sign = 0
|
||||
# hack to work regardless of whether Python supports -0.0
|
||||
if not y:
|
||||
y = 0.0
|
||||
z = complex(x,y)
|
||||
if k == 0:
|
||||
if -4.0 < y < 4.0 and -1.0 < x < 2.5:
|
||||
if imag_sign:
|
||||
# Taylor series in upper/lower half-plane
|
||||
if y > 1.00: return (0.876+0.645j) + (0.118-0.174j)*(z-(0.75+2.5j))
|
||||
if y > 0.25: return (0.505+0.204j) + (0.375-0.132j)*(z-(0.75+0.5j))
|
||||
if y < -1.00: return (0.876-0.645j) + (0.118+0.174j)*(z-(0.75-2.5j))
|
||||
if y < -0.25: return (0.505-0.204j) + (0.375+0.132j)*(z-(0.75-0.5j))
|
||||
# Taylor series near -1
|
||||
if x < -0.5:
|
||||
if imag_sign >= 0:
|
||||
return (-0.318+1.34j) + (-0.697-0.593j)*(z+1)
|
||||
else:
|
||||
return (-0.318-1.34j) + (-0.697+0.593j)*(z+1)
|
||||
# return real type
|
||||
r = -0.367879441171442
|
||||
if (not imag_sign) and x > r:
|
||||
z = x
|
||||
# Singularity near -1/e
|
||||
if x < -0.2:
|
||||
return -1 + 2.33164398159712*(z-r)**0.5 - 1.81218788563936*(z-r)
|
||||
# Taylor series near 0
|
||||
if x < 0.5: return z
|
||||
# Simple linear approximation
|
||||
return 0.2 + 0.3*z
|
||||
if (not imag_sign) and x > 0.0:
|
||||
L1 = math.log(x); L2 = math.log(L1)
|
||||
else:
|
||||
L1 = cmath.log(z); L2 = cmath.log(L1)
|
||||
elif k == -1:
|
||||
# return real type
|
||||
r = -0.367879441171442
|
||||
if (not imag_sign) and r < x < 0.0:
|
||||
z = x
|
||||
if (imag_sign >= 0) and y < 0.1 and -0.6 < x < -0.2:
|
||||
return -1 - 2.33164398159712*(z-r)**0.5 - 1.81218788563936*(z-r)
|
||||
if (not imag_sign) and -0.2 <= x < 0.0:
|
||||
L1 = math.log(-x)
|
||||
return L1 - math.log(-L1)
|
||||
else:
|
||||
if imag_sign == -1 and (not y) and x < 0.0:
|
||||
L1 = cmath.log(z) - 3.1415926535897932j
|
||||
else:
|
||||
L1 = cmath.log(z) - 6.2831853071795865j
|
||||
L2 = cmath.log(L1)
|
||||
return L1 - L2 + L2/L1 + L2*(L2-2)/(2*L1**2)
|
||||
|
||||
def _lambertw_series(ctx, z, k, tol):
|
||||
"""
|
||||
Return rough approximation for W_k(z) from an asymptotic series,
|
||||
sufficiently accurate for the Halley iteration to converge to
|
||||
the correct value.
|
||||
"""
|
||||
magz = ctx.mag(z)
|
||||
if (-10 < magz < 900) and (-1000 < k < 1000):
|
||||
# Near the branch point at -1/e
|
||||
if magz < 1 and abs(z+0.36787944117144) < 0.05:
|
||||
if k == 0 or (k == -1 and ctx._im(z) >= 0) or \
|
||||
(k == 1 and ctx._im(z) < 0):
|
||||
delta = ctx.sum_accurately(lambda: [z, ctx.exp(-1)])
|
||||
cancellation = -ctx.mag(delta)
|
||||
ctx.prec += cancellation
|
||||
# Use series given in Corless et al.
|
||||
p = ctx.sqrt(2*(ctx.e*z+1))
|
||||
ctx.prec -= cancellation
|
||||
u = {0:ctx.mpf(-1), 1:ctx.mpf(1)}
|
||||
a = {0:ctx.mpf(2), 1:ctx.mpf(-1)}
|
||||
if k != 0:
|
||||
p = -p
|
||||
s = ctx.zero
|
||||
# The series converges, so we could use it directly, but unless
|
||||
# *extremely* close, it is better to just use the first few
|
||||
# terms to get a good approximation for the iteration
|
||||
for l in xrange(max(2,cancellation)):
|
||||
if l not in u:
|
||||
a[l] = ctx.fsum(u[j]*u[l+1-j] for j in xrange(2,l))
|
||||
u[l] = (l-1)*(u[l-2]/2+a[l-2]/4)/(l+1)-a[l]/2-u[l-1]/(l+1)
|
||||
term = u[l] * p**l
|
||||
s += term
|
||||
if ctx.mag(term) < -tol:
|
||||
return s, True
|
||||
l += 1
|
||||
ctx.prec += cancellation//2
|
||||
return s, False
|
||||
if k == 0 or k == -1:
|
||||
return _lambertw_approx_hybrid(z, k), False
|
||||
if k == 0:
|
||||
if magz < -1:
|
||||
return z*(1-z), False
|
||||
L1 = ctx.ln(z)
|
||||
L2 = ctx.ln(L1)
|
||||
elif k == -1 and (not ctx._im(z)) and (-0.36787944117144 < ctx._re(z) < 0):
|
||||
L1 = ctx.ln(-z)
|
||||
return L1 - ctx.ln(-L1), False
|
||||
else:
|
||||
# This holds both as z -> 0 and z -> inf.
|
||||
# Relative error is O(1/log(z)).
|
||||
L1 = ctx.ln(z) + 2j*ctx.pi*k
|
||||
L2 = ctx.ln(L1)
|
||||
return L1 - L2 + L2/L1 + L2*(L2-2)/(2*L1**2), False
|
||||
|
||||
@defun
|
||||
def lambertw(ctx, z, k=0):
|
||||
z = ctx.convert(z)
|
||||
k = int(k)
|
||||
if not ctx.isnormal(z):
|
||||
return _lambertw_special(ctx, z, k)
|
||||
prec = ctx.prec
|
||||
ctx.prec += 20 + ctx.mag(k or 1)
|
||||
wp = ctx.prec
|
||||
tol = wp - 5
|
||||
w, done = _lambertw_series(ctx, z, k, tol)
|
||||
if not done:
|
||||
# Use Halley iteration to solve w*exp(w) = z
|
||||
two = ctx.mpf(2)
|
||||
for i in xrange(100):
|
||||
ew = ctx.exp(w)
|
||||
wew = w*ew
|
||||
wewz = wew-z
|
||||
wn = w - wewz/(wew+ew-(w+two)*wewz/(two*w+two))
|
||||
if ctx.mag(wn-w) <= ctx.mag(wn) - tol:
|
||||
w = wn
|
||||
break
|
||||
else:
|
||||
w = wn
|
||||
if i == 100:
|
||||
ctx.warn("Lambert W iteration failed to converge for z = %s" % z)
|
||||
ctx.prec = prec
|
||||
return +w
|
||||
|
||||
@defun_wrapped
|
||||
def bell(ctx, n, x=1):
|
||||
x = ctx.convert(x)
|
||||
if not n:
|
||||
if ctx.isnan(x):
|
||||
return x
|
||||
return type(x)(1)
|
||||
if ctx.isinf(x) or ctx.isinf(n) or ctx.isnan(x) or ctx.isnan(n):
|
||||
return x**n
|
||||
if n == 1: return x
|
||||
if n == 2: return x*(x+1)
|
||||
if x == 0: return ctx.sincpi(n)
|
||||
return _polyexp(ctx, n, x, True) / ctx.exp(x)
|
||||
|
||||
def _polyexp(ctx, n, x, extra=False):
|
||||
def _terms():
|
||||
if extra:
|
||||
yield ctx.sincpi(n)
|
||||
t = x
|
||||
k = 1
|
||||
while 1:
|
||||
yield k**n * t
|
||||
k += 1
|
||||
t = t*x/k
|
||||
return ctx.sum_accurately(_terms, check_step=4)
|
||||
|
||||
@defun_wrapped
|
||||
def polyexp(ctx, s, z):
|
||||
if ctx.isinf(z) or ctx.isinf(s) or ctx.isnan(z) or ctx.isnan(s):
|
||||
return z**s
|
||||
if z == 0: return z*s
|
||||
if s == 0: return ctx.expm1(z)
|
||||
if s == 1: return ctx.exp(z)*z
|
||||
if s == 2: return ctx.exp(z)*z*(z+1)
|
||||
return _polyexp(ctx, s, z)
|
||||
|
||||
@defun_wrapped
|
||||
def cyclotomic(ctx, n, z):
|
||||
n = int(n)
|
||||
if n < 0:
|
||||
raise ValueError("n cannot be negative")
|
||||
p = ctx.one
|
||||
if n == 0:
|
||||
return p
|
||||
if n == 1:
|
||||
return z - p
|
||||
if n == 2:
|
||||
return z + p
|
||||
# Use divisor product representation. Unfortunately, this sometimes
|
||||
# includes singularities for roots of unity, which we have to cancel out.
|
||||
# Matching zeros/poles pairwise, we have (1-z^a)/(1-z^b) ~ a/b + O(z-1).
|
||||
a_prod = 1
|
||||
b_prod = 1
|
||||
num_zeros = 0
|
||||
num_poles = 0
|
||||
for d in range(1,n+1):
|
||||
if not n % d:
|
||||
w = ctx.moebius(n//d)
|
||||
# Use powm1 because it is important that we get 0 only
|
||||
# if it really is exactly 0
|
||||
b = -ctx.powm1(z, d)
|
||||
if b:
|
||||
p *= b**w
|
||||
else:
|
||||
if w == 1:
|
||||
a_prod *= d
|
||||
num_zeros += 1
|
||||
elif w == -1:
|
||||
b_prod *= d
|
||||
num_poles += 1
|
||||
#print n, num_zeros, num_poles
|
||||
if num_zeros:
|
||||
if num_zeros > num_poles:
|
||||
p *= 0
|
||||
else:
|
||||
p *= a_prod
|
||||
p /= b_prod
|
||||
return p
|
||||
|
||||
@defun
|
||||
def mangoldt(ctx, n):
|
||||
r"""
|
||||
Evaluates the von Mangoldt function `\Lambda(n) = \log p`
|
||||
if `n = p^k` a power of a prime, and `\Lambda(n) = 0` otherwise.
|
||||
|
||||
**Examples**
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 25; mp.pretty = True
|
||||
>>> [mangoldt(n) for n in range(-2,3)]
|
||||
[0.0, 0.0, 0.0, 0.0, 0.6931471805599453094172321]
|
||||
>>> mangoldt(6)
|
||||
0.0
|
||||
>>> mangoldt(7)
|
||||
1.945910149055313305105353
|
||||
>>> mangoldt(8)
|
||||
0.6931471805599453094172321
|
||||
>>> fsum(mangoldt(n) for n in range(101))
|
||||
94.04531122935739224600493
|
||||
>>> fsum(mangoldt(n) for n in range(10001))
|
||||
10013.39669326311478372032
|
||||
|
||||
"""
|
||||
n = int(n)
|
||||
if n < 2:
|
||||
return ctx.zero
|
||||
if n % 2 == 0:
|
||||
# Must be a power of two
|
||||
if n & (n-1) == 0:
|
||||
return +ctx.ln2
|
||||
else:
|
||||
return ctx.zero
|
||||
# TODO: the following could be generalized into a perfect
|
||||
# power testing function
|
||||
# ---
|
||||
# Look for a small factor
|
||||
for p in (3,5,7,11,13,17,19,23,29,31):
|
||||
if not n % p:
|
||||
q, r = n // p, 0
|
||||
while q > 1:
|
||||
q, r = divmod(q, p)
|
||||
if r:
|
||||
return ctx.zero
|
||||
return ctx.ln(p)
|
||||
if ctx.isprime(n):
|
||||
return ctx.ln(n)
|
||||
# Obviously, we could use arbitrary-precision arithmetic for this...
|
||||
if n > 10**30:
|
||||
raise NotImplementedError
|
||||
k = 2
|
||||
while 1:
|
||||
p = int(n**(1./k) + 0.5)
|
||||
if p < 2:
|
||||
return ctx.zero
|
||||
if p ** k == n:
|
||||
if ctx.isprime(p):
|
||||
return ctx.ln(p)
|
||||
k += 1
|
||||
|
||||
@defun
|
||||
def stirling1(ctx, n, k, exact=False):
|
||||
v = ctx._stirling1(int(n), int(k))
|
||||
if exact:
|
||||
return int(v)
|
||||
else:
|
||||
return ctx.mpf(v)
|
||||
|
||||
@defun
|
||||
def stirling2(ctx, n, k, exact=False):
|
||||
v = ctx._stirling2(int(n), int(k))
|
||||
if exact:
|
||||
return int(v)
|
||||
else:
|
||||
return ctx.mpf(v)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,493 @@
|
||||
from .functions import defun, defun_wrapped
|
||||
|
||||
def _hermite_param(ctx, n, z, parabolic_cylinder):
|
||||
"""
|
||||
Combined calculation of the Hermite polynomial H_n(z) (and its
|
||||
generalization to complex n) and the parabolic cylinder
|
||||
function D.
|
||||
"""
|
||||
n, ntyp = ctx._convert_param(n)
|
||||
z = ctx.convert(z)
|
||||
q = -ctx.mpq_1_2
|
||||
# For re(z) > 0, 2F0 -- http://functions.wolfram.com/
|
||||
# HypergeometricFunctions/HermiteHGeneral/06/02/0009/
|
||||
# Otherwise, there is a reflection formula
|
||||
# 2F0 + http://functions.wolfram.com/HypergeometricFunctions/
|
||||
# HermiteHGeneral/16/01/01/0006/
|
||||
#
|
||||
# TODO:
|
||||
# An alternative would be to use
|
||||
# http://functions.wolfram.com/HypergeometricFunctions/
|
||||
# HermiteHGeneral/06/02/0006/
|
||||
#
|
||||
# Also, the 1F1 expansion
|
||||
# http://functions.wolfram.com/HypergeometricFunctions/
|
||||
# HermiteHGeneral/26/01/02/0001/
|
||||
# should probably be used for tiny z
|
||||
if not z:
|
||||
T1 = [2, ctx.pi], [n, 0.5], [], [q*(n-1)], [], [], 0
|
||||
if parabolic_cylinder:
|
||||
T1[1][0] += q*n
|
||||
return T1,
|
||||
can_use_2f0 = ctx.isnpint(-n) or ctx.re(z) > 0 or \
|
||||
(ctx.re(z) == 0 and ctx.im(z) > 0)
|
||||
expprec = ctx.prec*4 + 20
|
||||
if parabolic_cylinder:
|
||||
u = ctx.fmul(ctx.fmul(z,z,prec=expprec), -0.25, exact=True)
|
||||
w = ctx.fmul(z, ctx.sqrt(0.5,prec=expprec), prec=expprec)
|
||||
else:
|
||||
w = z
|
||||
w2 = ctx.fmul(w, w, prec=expprec)
|
||||
rw2 = ctx.fdiv(1, w2, prec=expprec)
|
||||
nrw2 = ctx.fneg(rw2, exact=True)
|
||||
nw = ctx.fneg(w, exact=True)
|
||||
if can_use_2f0:
|
||||
T1 = [2, w], [n, n], [], [], [q*n, q*(n-1)], [], nrw2
|
||||
terms = [T1]
|
||||
else:
|
||||
T1 = [2, nw], [n, n], [], [], [q*n, q*(n-1)], [], nrw2
|
||||
T2 = [2, ctx.pi, nw], [n+2, 0.5, 1], [], [q*n], [q*(n-1)], [1-q], w2
|
||||
terms = [T1,T2]
|
||||
# Multiply by prefactor for D_n
|
||||
if parabolic_cylinder:
|
||||
expu = ctx.exp(u)
|
||||
for i in range(len(terms)):
|
||||
terms[i][1][0] += q*n
|
||||
terms[i][0].append(expu)
|
||||
terms[i][1].append(1)
|
||||
return tuple(terms)
|
||||
|
||||
@defun
|
||||
def hermite(ctx, n, z, **kwargs):
|
||||
return ctx.hypercomb(lambda: _hermite_param(ctx, n, z, 0), [], **kwargs)
|
||||
|
||||
@defun
|
||||
def pcfd(ctx, n, z, **kwargs):
|
||||
r"""
|
||||
Gives the parabolic cylinder function in Whittaker's notation
|
||||
`D_n(z) = U(-n-1/2, z)` (see :func:`~mpmath.pcfu`).
|
||||
It solves the differential equation
|
||||
|
||||
.. math ::
|
||||
|
||||
y'' + \left(n + \frac{1}{2} - \frac{1}{4} z^2\right) y = 0.
|
||||
|
||||
and can be represented in terms of Hermite polynomials
|
||||
(see :func:`~mpmath.hermite`) as
|
||||
|
||||
.. math ::
|
||||
|
||||
D_n(z) = 2^{-n/2} e^{-z^2/4} H_n\left(\frac{z}{\sqrt{2}}\right).
|
||||
|
||||
**Plots**
|
||||
|
||||
.. literalinclude :: /plots/pcfd.py
|
||||
.. image :: /plots/pcfd.png
|
||||
|
||||
**Examples**
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 25; mp.pretty = True
|
||||
>>> pcfd(0,0); pcfd(1,0); pcfd(2,0); pcfd(3,0)
|
||||
1.0
|
||||
0.0
|
||||
-1.0
|
||||
0.0
|
||||
>>> pcfd(4,0); pcfd(-3,0)
|
||||
3.0
|
||||
0.6266570686577501256039413
|
||||
>>> pcfd('1/2', 2+3j)
|
||||
(-5.363331161232920734849056 - 3.858877821790010714163487j)
|
||||
>>> pcfd(2, -10)
|
||||
1.374906442631438038871515e-9
|
||||
|
||||
Verifying the differential equation::
|
||||
|
||||
>>> n = mpf(2.5)
|
||||
>>> y = lambda z: pcfd(n,z)
|
||||
>>> z = 1.75
|
||||
>>> chop(diff(y,z,2) + (n+0.5-0.25*z**2)*y(z))
|
||||
0.0
|
||||
|
||||
Rational Taylor series expansion when `n` is an integer::
|
||||
|
||||
>>> taylor(lambda z: pcfd(5,z), 0, 7)
|
||||
[0.0, 15.0, 0.0, -13.75, 0.0, 3.96875, 0.0, -0.6015625]
|
||||
|
||||
"""
|
||||
return ctx.hypercomb(lambda: _hermite_param(ctx, n, z, 1), [], **kwargs)
|
||||
|
||||
@defun
|
||||
def pcfu(ctx, a, z, **kwargs):
|
||||
r"""
|
||||
Gives the parabolic cylinder function `U(a,z)`, which may be
|
||||
defined for `\Re(z) > 0` in terms of the confluent
|
||||
U-function (see :func:`~mpmath.hyperu`) by
|
||||
|
||||
.. math ::
|
||||
|
||||
U(a,z) = 2^{-\frac{1}{4}-\frac{a}{2}} e^{-\frac{1}{4} z^2}
|
||||
U\left(\frac{a}{2}+\frac{1}{4},
|
||||
\frac{1}{2}, \frac{1}{2}z^2\right)
|
||||
|
||||
or, for arbitrary `z`,
|
||||
|
||||
.. math ::
|
||||
|
||||
e^{-\frac{1}{4}z^2} U(a,z) =
|
||||
U(a,0) \,_1F_1\left(-\tfrac{a}{2}+\tfrac{1}{4};
|
||||
\tfrac{1}{2}; -\tfrac{1}{2}z^2\right) +
|
||||
U'(a,0) z \,_1F_1\left(-\tfrac{a}{2}+\tfrac{3}{4};
|
||||
\tfrac{3}{2}; -\tfrac{1}{2}z^2\right).
|
||||
|
||||
**Examples**
|
||||
|
||||
Connection to other functions::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 25; mp.pretty = True
|
||||
>>> z = mpf(3)
|
||||
>>> pcfu(0.5,z)
|
||||
0.03210358129311151450551963
|
||||
>>> sqrt(pi/2)*exp(z**2/4)*erfc(z/sqrt(2))
|
||||
0.03210358129311151450551963
|
||||
>>> pcfu(0.5,-z)
|
||||
23.75012332835297233711255
|
||||
>>> sqrt(pi/2)*exp(z**2/4)*erfc(-z/sqrt(2))
|
||||
23.75012332835297233711255
|
||||
>>> pcfu(0.5,-z)
|
||||
23.75012332835297233711255
|
||||
>>> sqrt(pi/2)*exp(z**2/4)*erfc(-z/sqrt(2))
|
||||
23.75012332835297233711255
|
||||
|
||||
"""
|
||||
n, _ = ctx._convert_param(a)
|
||||
return ctx.pcfd(-n-ctx.mpq_1_2, z)
|
||||
|
||||
@defun
|
||||
def pcfv(ctx, a, z, **kwargs):
|
||||
r"""
|
||||
Gives the parabolic cylinder function `V(a,z)`, which can be
|
||||
represented in terms of :func:`~mpmath.pcfu` as
|
||||
|
||||
.. math ::
|
||||
|
||||
V(a,z) = \frac{\Gamma(a+\tfrac{1}{2}) (U(a,-z)-\sin(\pi a) U(a,z)}{\pi}.
|
||||
|
||||
**Examples**
|
||||
|
||||
Wronskian relation between `U` and `V`::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 25; mp.pretty = True
|
||||
>>> a, z = 2, 3
|
||||
>>> pcfu(a,z)*diff(pcfv,(a,z),(0,1))-diff(pcfu,(a,z),(0,1))*pcfv(a,z)
|
||||
0.7978845608028653558798921
|
||||
>>> sqrt(2/pi)
|
||||
0.7978845608028653558798921
|
||||
>>> a, z = 2.5, 3
|
||||
>>> pcfu(a,z)*diff(pcfv,(a,z),(0,1))-diff(pcfu,(a,z),(0,1))*pcfv(a,z)
|
||||
0.7978845608028653558798921
|
||||
>>> a, z = 0.25, -1
|
||||
>>> pcfu(a,z)*diff(pcfv,(a,z),(0,1))-diff(pcfu,(a,z),(0,1))*pcfv(a,z)
|
||||
0.7978845608028653558798921
|
||||
>>> a, z = 2+1j, 2+3j
|
||||
>>> chop(pcfu(a,z)*diff(pcfv,(a,z),(0,1))-diff(pcfu,(a,z),(0,1))*pcfv(a,z))
|
||||
0.7978845608028653558798921
|
||||
|
||||
"""
|
||||
n, ntype = ctx._convert_param(a)
|
||||
z = ctx.convert(z)
|
||||
q = ctx.mpq_1_2
|
||||
r = ctx.mpq_1_4
|
||||
if ntype == 'Q' and ctx.isint(n*2):
|
||||
# Faster for half-integers
|
||||
def h():
|
||||
jz = ctx.fmul(z, -1j, exact=True)
|
||||
T1terms = _hermite_param(ctx, -n-q, z, 1)
|
||||
T2terms = _hermite_param(ctx, n-q, jz, 1)
|
||||
for T in T1terms:
|
||||
T[0].append(1j)
|
||||
T[1].append(1)
|
||||
T[3].append(q-n)
|
||||
u = ctx.expjpi((q*n-r)) * ctx.sqrt(2/ctx.pi)
|
||||
for T in T2terms:
|
||||
T[0].append(u)
|
||||
T[1].append(1)
|
||||
return T1terms + T2terms
|
||||
v = ctx.hypercomb(h, [], **kwargs)
|
||||
if ctx._is_real_type(n) and ctx._is_real_type(z):
|
||||
v = ctx._re(v)
|
||||
return v
|
||||
else:
|
||||
def h(n):
|
||||
w = ctx.square_exp_arg(z, -0.25)
|
||||
u = ctx.square_exp_arg(z, 0.5)
|
||||
e = ctx.exp(w)
|
||||
l = [ctx.pi, q, ctx.exp(w)]
|
||||
Y1 = l, [-q, n*q+r, 1], [r-q*n], [], [q*n+r], [q], u
|
||||
Y2 = l + [z], [-q, n*q-r, 1, 1], [1-r-q*n], [], [q*n+1-r], [1+q], u
|
||||
c, s = ctx.cospi_sinpi(r+q*n)
|
||||
Y1[0].append(s)
|
||||
Y2[0].append(c)
|
||||
for Y in (Y1, Y2):
|
||||
Y[1].append(1)
|
||||
Y[3].append(q-n)
|
||||
return Y1, Y2
|
||||
return ctx.hypercomb(h, [n], **kwargs)
|
||||
|
||||
|
||||
@defun
|
||||
def pcfw(ctx, a, z, **kwargs):
|
||||
r"""
|
||||
Gives the parabolic cylinder function `W(a,z)` defined in (DLMF 12.14).
|
||||
|
||||
**Examples**
|
||||
|
||||
Value at the origin::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 25; mp.pretty = True
|
||||
>>> a = mpf(0.25)
|
||||
>>> pcfw(a,0)
|
||||
0.9722833245718180765617104
|
||||
>>> power(2,-0.75)*sqrt(abs(gamma(0.25+0.5j*a)/gamma(0.75+0.5j*a)))
|
||||
0.9722833245718180765617104
|
||||
>>> diff(pcfw,(a,0),(0,1))
|
||||
-0.5142533944210078966003624
|
||||
>>> -power(2,-0.25)*sqrt(abs(gamma(0.75+0.5j*a)/gamma(0.25+0.5j*a)))
|
||||
-0.5142533944210078966003624
|
||||
|
||||
"""
|
||||
n, _ = ctx._convert_param(a)
|
||||
z = ctx.convert(z)
|
||||
def terms():
|
||||
phi2 = ctx.arg(ctx.gamma(0.5 + ctx.j*n))
|
||||
phi2 = (ctx.loggamma(0.5+ctx.j*n) - ctx.loggamma(0.5-ctx.j*n))/2j
|
||||
rho = ctx.pi/8 + 0.5*phi2
|
||||
# XXX: cancellation computing k
|
||||
k = ctx.sqrt(1 + ctx.exp(2*ctx.pi*n)) - ctx.exp(ctx.pi*n)
|
||||
C = ctx.sqrt(k/2) * ctx.exp(0.25*ctx.pi*n)
|
||||
yield C * ctx.expj(rho) * ctx.pcfu(ctx.j*n, z*ctx.expjpi(-0.25))
|
||||
yield C * ctx.expj(-rho) * ctx.pcfu(-ctx.j*n, z*ctx.expjpi(0.25))
|
||||
v = ctx.sum_accurately(terms)
|
||||
if ctx._is_real_type(n) and ctx._is_real_type(z):
|
||||
v = ctx._re(v)
|
||||
return v
|
||||
|
||||
"""
|
||||
Even/odd PCFs. Useful?
|
||||
|
||||
@defun
|
||||
def pcfy1(ctx, a, z, **kwargs):
|
||||
a, _ = ctx._convert_param(n)
|
||||
z = ctx.convert(z)
|
||||
def h():
|
||||
w = ctx.square_exp_arg(z)
|
||||
w1 = ctx.fmul(w, -0.25, exact=True)
|
||||
w2 = ctx.fmul(w, 0.5, exact=True)
|
||||
e = ctx.exp(w1)
|
||||
return [e], [1], [], [], [ctx.mpq_1_2*a+ctx.mpq_1_4], [ctx.mpq_1_2], w2
|
||||
return ctx.hypercomb(h, [], **kwargs)
|
||||
|
||||
@defun
|
||||
def pcfy2(ctx, a, z, **kwargs):
|
||||
a, _ = ctx._convert_param(n)
|
||||
z = ctx.convert(z)
|
||||
def h():
|
||||
w = ctx.square_exp_arg(z)
|
||||
w1 = ctx.fmul(w, -0.25, exact=True)
|
||||
w2 = ctx.fmul(w, 0.5, exact=True)
|
||||
e = ctx.exp(w1)
|
||||
return [e, z], [1, 1], [], [], [ctx.mpq_1_2*a+ctx.mpq_3_4], \
|
||||
[ctx.mpq_3_2], w2
|
||||
return ctx.hypercomb(h, [], **kwargs)
|
||||
"""
|
||||
|
||||
@defun_wrapped
|
||||
def gegenbauer(ctx, n, a, z, **kwargs):
|
||||
# Special cases: a+0.5, a*2 poles
|
||||
if ctx.isnpint(a):
|
||||
return 0*(z+n)
|
||||
if ctx.isnpint(a+0.5):
|
||||
# TODO: something else is required here
|
||||
# E.g.: gegenbauer(-2, -0.5, 3) == -12
|
||||
if ctx.isnpint(n+1):
|
||||
raise NotImplementedError("Gegenbauer function with two limits")
|
||||
def h(a):
|
||||
a2 = 2*a
|
||||
T = [], [], [n+a2], [n+1, a2], [-n, n+a2], [a+0.5], 0.5*(1-z)
|
||||
return [T]
|
||||
return ctx.hypercomb(h, [a], **kwargs)
|
||||
def h(n):
|
||||
a2 = 2*a
|
||||
T = [], [], [n+a2], [n+1, a2], [-n, n+a2], [a+0.5], 0.5*(1-z)
|
||||
return [T]
|
||||
return ctx.hypercomb(h, [n], **kwargs)
|
||||
|
||||
@defun_wrapped
|
||||
def jacobi(ctx, n, a, b, x, **kwargs):
|
||||
if not ctx.isnpint(a):
|
||||
def h(n):
|
||||
return (([], [], [a+n+1], [n+1, a+1], [-n, a+b+n+1], [a+1], (1-x)*0.5),)
|
||||
return ctx.hypercomb(h, [n], **kwargs)
|
||||
if not ctx.isint(b):
|
||||
def h(n, a):
|
||||
return (([], [], [-b], [n+1, -b-n], [-n, a+b+n+1], [b+1], (x+1)*0.5),)
|
||||
return ctx.hypercomb(h, [n, a], **kwargs)
|
||||
# XXX: determine appropriate limit
|
||||
return ctx.binomial(n+a,n) * ctx.hyp2f1(-n,1+n+a+b,a+1,(1-x)/2, **kwargs)
|
||||
|
||||
@defun_wrapped
|
||||
def laguerre(ctx, n, a, z, **kwargs):
|
||||
# XXX: limits, poles
|
||||
#if ctx.isnpint(n):
|
||||
# return 0*(a+z)
|
||||
def h(a):
|
||||
return (([], [], [a+n+1], [a+1, n+1], [-n], [a+1], z),)
|
||||
return ctx.hypercomb(h, [a], **kwargs)
|
||||
|
||||
@defun_wrapped
|
||||
def legendre(ctx, n, x, **kwargs):
|
||||
if ctx.isint(n):
|
||||
n = int(n)
|
||||
# Accuracy near zeros
|
||||
if (n + (n < 0)) & 1:
|
||||
if not x:
|
||||
return x
|
||||
mag = ctx.mag(x)
|
||||
if mag < -2*ctx.prec-10:
|
||||
return x
|
||||
if mag < -5:
|
||||
ctx.prec += -mag
|
||||
return ctx.hyp2f1(-n,n+1,1,(1-x)/2, **kwargs)
|
||||
|
||||
@defun
|
||||
def legenp(ctx, n, m, z, type=2, **kwargs):
|
||||
# Legendre function, 1st kind
|
||||
n = ctx.convert(n)
|
||||
m = ctx.convert(m)
|
||||
# Faster
|
||||
if not m:
|
||||
return ctx.legendre(n, z, **kwargs)
|
||||
# TODO: correct evaluation at singularities
|
||||
if type == 2:
|
||||
def h(n,m):
|
||||
g = m*0.5
|
||||
T = [1+z, 1-z], [g, -g], [], [1-m], [-n, n+1], [1-m], 0.5*(1-z)
|
||||
return (T,)
|
||||
return ctx.hypercomb(h, [n,m], **kwargs)
|
||||
if type == 3:
|
||||
def h(n,m):
|
||||
g = m*0.5
|
||||
T = [z+1, z-1], [g, -g], [], [1-m], [-n, n+1], [1-m], 0.5*(1-z)
|
||||
return (T,)
|
||||
return ctx.hypercomb(h, [n,m], **kwargs)
|
||||
raise ValueError("requires type=2 or type=3")
|
||||
|
||||
@defun
|
||||
def legenq(ctx, n, m, z, type=2, **kwargs):
|
||||
# Legendre function, 2nd kind
|
||||
n = ctx.convert(n)
|
||||
m = ctx.convert(m)
|
||||
z = ctx.convert(z)
|
||||
if z in (1, -1):
|
||||
#if ctx.isint(m):
|
||||
# return ctx.nan
|
||||
#return ctx.inf # unsigned
|
||||
return ctx.nan
|
||||
if type == 2:
|
||||
def h(n, m):
|
||||
cos, sin = ctx.cospi_sinpi(m)
|
||||
s = 2 * sin / ctx.pi
|
||||
c = cos
|
||||
a = 1+z
|
||||
b = 1-z
|
||||
u = m/2
|
||||
w = (1-z)/2
|
||||
T1 = [s, c, a, b], [-1, 1, u, -u], [], [1-m], \
|
||||
[-n, n+1], [1-m], w
|
||||
T2 = [-s, a, b], [-1, -u, u], [n+m+1], [n-m+1, m+1], \
|
||||
[-n, n+1], [m+1], w
|
||||
return T1, T2
|
||||
return ctx.hypercomb(h, [n, m], **kwargs)
|
||||
if type == 3:
|
||||
# The following is faster when there only is a single series
|
||||
# Note: not valid for -1 < z < 0 (?)
|
||||
if abs(z) > 1:
|
||||
def h(n, m):
|
||||
T1 = [ctx.expjpi(m), 2, ctx.pi, z, z-1, z+1], \
|
||||
[1, -n-1, 0.5, -n-m-1, 0.5*m, 0.5*m], \
|
||||
[n+m+1], [n+1.5], \
|
||||
[0.5*(2+n+m), 0.5*(1+n+m)], [n+1.5], z**(-2)
|
||||
return [T1]
|
||||
return ctx.hypercomb(h, [n, m], **kwargs)
|
||||
else:
|
||||
# not valid for 1 < z < inf ?
|
||||
def h(n, m):
|
||||
s = 2 * ctx.sinpi(m) / ctx.pi
|
||||
c = ctx.expjpi(m)
|
||||
a = 1+z
|
||||
b = z-1
|
||||
u = m/2
|
||||
w = (1-z)/2
|
||||
T1 = [s, c, a, b], [-1, 1, u, -u], [], [1-m], \
|
||||
[-n, n+1], [1-m], w
|
||||
T2 = [-s, c, a, b], [-1, 1, -u, u], [n+m+1], [n-m+1, m+1], \
|
||||
[-n, n+1], [m+1], w
|
||||
return T1, T2
|
||||
return ctx.hypercomb(h, [n, m], **kwargs)
|
||||
raise ValueError("requires type=2 or type=3")
|
||||
|
||||
@defun_wrapped
|
||||
def chebyt(ctx, n, x, **kwargs):
|
||||
if (not x) and ctx.isint(n) and int(ctx._re(n)) % 2 == 1:
|
||||
return x * 0
|
||||
return ctx.hyp2f1(-n,n,(1,2),(1-x)/2, **kwargs)
|
||||
|
||||
@defun_wrapped
|
||||
def chebyu(ctx, n, x, **kwargs):
|
||||
if (not x) and ctx.isint(n) and int(ctx._re(n)) % 2 == 1:
|
||||
return x * 0
|
||||
return (n+1) * ctx.hyp2f1(-n, n+2, (3,2), (1-x)/2, **kwargs)
|
||||
|
||||
@defun
|
||||
def spherharm(ctx, l, m, theta, phi, **kwargs):
|
||||
l = ctx.convert(l)
|
||||
m = ctx.convert(m)
|
||||
theta = ctx.convert(theta)
|
||||
phi = ctx.convert(phi)
|
||||
l_isint = ctx.isint(l)
|
||||
l_natural = l_isint and l >= 0
|
||||
m_isint = ctx.isint(m)
|
||||
if l_isint and l < 0 and m_isint:
|
||||
return ctx.spherharm(-(l+1), m, theta, phi, **kwargs)
|
||||
if theta == 0 and m_isint and m < 0:
|
||||
return ctx.zero * 1j
|
||||
if l_natural and m_isint:
|
||||
if abs(m) > l:
|
||||
return ctx.zero * 1j
|
||||
# http://functions.wolfram.com/Polynomials/
|
||||
# SphericalHarmonicY/26/01/02/0004/
|
||||
def h(l,m):
|
||||
absm = abs(m)
|
||||
C = [-1, ctx.expj(m*phi),
|
||||
(2*l+1)*ctx.fac(l+absm)/ctx.pi/ctx.fac(l-absm),
|
||||
ctx.sin(theta)**2,
|
||||
ctx.fac(absm), 2]
|
||||
P = [0.5*m*(ctx.sign(m)+1), 1, 0.5, 0.5*absm, -1, -absm-1]
|
||||
return ((C, P, [], [], [absm-l, l+absm+1], [absm+1],
|
||||
ctx.sin(0.5*theta)**2),)
|
||||
else:
|
||||
# http://functions.wolfram.com/HypergeometricFunctions/
|
||||
# SphericalHarmonicYGeneral/26/01/02/0001/
|
||||
def h(l,m):
|
||||
if ctx.isnpint(l-m+1) or ctx.isnpint(l+m+1) or ctx.isnpint(1-m):
|
||||
return (([0], [-1], [], [], [], [], 0),)
|
||||
cos, sin = ctx.cos_sin(0.5*theta)
|
||||
C = [0.5*ctx.expj(m*phi), (2*l+1)/ctx.pi,
|
||||
ctx.gamma(l-m+1), ctx.gamma(l+m+1),
|
||||
cos**2, sin**2]
|
||||
P = [1, 0.5, 0.5, -0.5, 0.5*m, -0.5*m]
|
||||
return ((C, P, [], [1-m], [-l,l+1], [1-m], sin**2),)
|
||||
return ctx.hypercomb(h, [l,m], **kwargs)
|
||||
@@ -0,0 +1,280 @@
|
||||
from .functions import defun, defun_wrapped
|
||||
|
||||
@defun
|
||||
def qp(ctx, a, q=None, n=None, **kwargs):
|
||||
r"""
|
||||
Evaluates the q-Pochhammer symbol (or q-rising factorial)
|
||||
|
||||
.. math ::
|
||||
|
||||
(a; q)_n = \prod_{k=0}^{n-1} (1-a q^k)
|
||||
|
||||
where `n = \infty` is permitted if `|q| < 1`. Called with two arguments,
|
||||
``qp(a,q)`` computes `(a;q)_{\infty}`; with a single argument, ``qp(q)``
|
||||
computes `(q;q)_{\infty}`. The special case
|
||||
|
||||
.. math ::
|
||||
|
||||
\phi(q) = (q; q)_{\infty} = \prod_{k=1}^{\infty} (1-q^k) =
|
||||
\sum_{k=-\infty}^{\infty} (-1)^k q^{(3k^2-k)/2}
|
||||
|
||||
is also known as the Euler function, or (up to a factor `q^{-1/24}`)
|
||||
the Dedekind eta function.
|
||||
|
||||
**Examples**
|
||||
|
||||
If `n` is a positive integer, the function amounts to a finite product::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 25; mp.pretty = True
|
||||
>>> qp(2,3,5)
|
||||
-725305.0
|
||||
>>> fprod(1-2*3**k for k in range(5))
|
||||
-725305.0
|
||||
>>> qp(2,3,0)
|
||||
1.0
|
||||
|
||||
Complex arguments are allowed::
|
||||
|
||||
>>> qp(2-1j, 0.75j)
|
||||
(0.4628842231660149089976379 + 4.481821753552703090628793j)
|
||||
|
||||
The regular Pochhammer symbol `(a)_n` is obtained in the
|
||||
following limit as `q \to 1`::
|
||||
|
||||
>>> a, n = 4, 7
|
||||
>>> limit(lambda q: qp(q**a,q,n) / (1-q)**n, 1)
|
||||
604800.0
|
||||
>>> rf(a,n)
|
||||
604800.0
|
||||
|
||||
The Taylor series of the reciprocal Euler function gives
|
||||
the partition function `P(n)`, i.e. the number of ways of writing
|
||||
`n` as a sum of positive integers::
|
||||
|
||||
>>> taylor(lambda q: 1/qp(q), 0, 10)
|
||||
[1.0, 1.0, 2.0, 3.0, 5.0, 7.0, 11.0, 15.0, 22.0, 30.0, 42.0]
|
||||
|
||||
Special values include::
|
||||
|
||||
>>> qp(0)
|
||||
1.0
|
||||
>>> findroot(diffun(qp), -0.4) # location of maximum
|
||||
-0.4112484791779547734440257
|
||||
>>> qp(_)
|
||||
1.228348867038575112586878
|
||||
|
||||
The q-Pochhammer symbol is related to the Jacobi theta functions.
|
||||
For example, the following identity holds::
|
||||
|
||||
>>> q = mpf(0.5) # arbitrary
|
||||
>>> qp(q)
|
||||
0.2887880950866024212788997
|
||||
>>> root(3,-2)*root(q,-24)*jtheta(2,pi/6,root(q,6))
|
||||
0.2887880950866024212788997
|
||||
|
||||
"""
|
||||
a = ctx.convert(a)
|
||||
if n is None:
|
||||
n = ctx.inf
|
||||
else:
|
||||
n = ctx.convert(n)
|
||||
if n < 0:
|
||||
raise ValueError("n cannot be negative")
|
||||
if q is None:
|
||||
q = a
|
||||
else:
|
||||
q = ctx.convert(q)
|
||||
if n == 0:
|
||||
return ctx.one + 0*(a+q)
|
||||
infinite = (n == ctx.inf)
|
||||
same = (a == q)
|
||||
if infinite:
|
||||
if abs(q) >= 1:
|
||||
if same and (q == -1 or q == 1):
|
||||
return ctx.zero * q
|
||||
raise ValueError("q-function only defined for |q| < 1")
|
||||
elif q == 0:
|
||||
return ctx.one - a
|
||||
maxterms = kwargs.get('maxterms', 50*ctx.prec)
|
||||
if infinite and same:
|
||||
# Euler's pentagonal theorem
|
||||
def terms():
|
||||
t = 1
|
||||
yield t
|
||||
k = 1
|
||||
x1 = q
|
||||
x2 = q**2
|
||||
while 1:
|
||||
yield (-1)**k * x1
|
||||
yield (-1)**k * x2
|
||||
x1 *= q**(3*k+1)
|
||||
x2 *= q**(3*k+2)
|
||||
k += 1
|
||||
if k > maxterms:
|
||||
raise ctx.NoConvergence
|
||||
return ctx.sum_accurately(terms)
|
||||
# return ctx.nprod(lambda k: 1-a*q**k, [0,n-1])
|
||||
def factors():
|
||||
k = 0
|
||||
r = ctx.one
|
||||
while 1:
|
||||
yield 1 - a*r
|
||||
r *= q
|
||||
k += 1
|
||||
if k >= n:
|
||||
return
|
||||
if k > maxterms:
|
||||
raise ctx.NoConvergence
|
||||
return ctx.mul_accurately(factors)
|
||||
|
||||
@defun_wrapped
|
||||
def qgamma(ctx, z, q, **kwargs):
|
||||
r"""
|
||||
Evaluates the q-gamma function
|
||||
|
||||
.. math ::
|
||||
|
||||
\Gamma_q(z) = \frac{(q; q)_{\infty}}{(q^z; q)_{\infty}} (1-q)^{1-z}.
|
||||
|
||||
|
||||
**Examples**
|
||||
|
||||
Evaluation for real and complex arguments::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 25; mp.pretty = True
|
||||
>>> qgamma(4,0.75)
|
||||
4.046875
|
||||
>>> qgamma(6,6)
|
||||
121226245.0
|
||||
>>> qgamma(3+4j, 0.5j)
|
||||
(0.1663082382255199834630088 + 0.01952474576025952984418217j)
|
||||
|
||||
The q-gamma function satisfies a functional equation similar
|
||||
to that of the ordinary gamma function::
|
||||
|
||||
>>> q = mpf(0.25)
|
||||
>>> z = mpf(2.5)
|
||||
>>> qgamma(z+1,q)
|
||||
1.428277424823760954685912
|
||||
>>> (1-q**z)/(1-q)*qgamma(z,q)
|
||||
1.428277424823760954685912
|
||||
|
||||
"""
|
||||
if abs(q) > 1:
|
||||
return ctx.qgamma(z,1/q)*q**((z-2)*(z-1)*0.5)
|
||||
return ctx.qp(q, q, None, **kwargs) / \
|
||||
ctx.qp(q**z, q, None, **kwargs) * (1-q)**(1-z)
|
||||
|
||||
@defun_wrapped
|
||||
def qfac(ctx, z, q, **kwargs):
|
||||
r"""
|
||||
Evaluates the q-factorial,
|
||||
|
||||
.. math ::
|
||||
|
||||
[n]_q! = (1+q)(1+q+q^2)\cdots(1+q+\cdots+q^{n-1})
|
||||
|
||||
or more generally
|
||||
|
||||
.. math ::
|
||||
|
||||
[z]_q! = \frac{(q;q)_z}{(1-q)^z}.
|
||||
|
||||
**Examples**
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 25; mp.pretty = True
|
||||
>>> qfac(0,0)
|
||||
1.0
|
||||
>>> qfac(4,3)
|
||||
2080.0
|
||||
>>> qfac(5,6)
|
||||
121226245.0
|
||||
>>> qfac(1+1j, 2+1j)
|
||||
(0.4370556551322672478613695 + 0.2609739839216039203708921j)
|
||||
|
||||
"""
|
||||
if ctx.isint(z) and ctx._re(z) > 0:
|
||||
n = int(ctx._re(z))
|
||||
return ctx.qp(q, q, n, **kwargs) / (1-q)**n
|
||||
return ctx.qgamma(z+1, q, **kwargs)
|
||||
|
||||
@defun
|
||||
def qhyper(ctx, a_s, b_s, q, z, **kwargs):
|
||||
r"""
|
||||
Evaluates the basic hypergeometric series or hypergeometric q-series
|
||||
|
||||
.. math ::
|
||||
|
||||
\,_r\phi_s \left[\begin{matrix}
|
||||
a_1 & a_2 & \ldots & a_r \\
|
||||
b_1 & b_2 & \ldots & b_s
|
||||
\end{matrix} ; q,z \right] =
|
||||
\sum_{n=0}^\infty
|
||||
\frac{(a_1;q)_n, \ldots, (a_r;q)_n}
|
||||
{(b_1;q)_n, \ldots, (b_s;q)_n}
|
||||
\left((-1)^n q^{n\choose 2}\right)^{1+s-r}
|
||||
\frac{z^n}{(q;q)_n}
|
||||
|
||||
where `(a;q)_n` denotes the q-Pochhammer symbol (see :func:`~mpmath.qp`).
|
||||
|
||||
**Examples**
|
||||
|
||||
Evaluation works for real and complex arguments::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 25; mp.pretty = True
|
||||
>>> qhyper([0.5], [2.25], 0.25, 4)
|
||||
-0.1975849091263356009534385
|
||||
>>> qhyper([0.5], [2.25], 0.25-0.25j, 4)
|
||||
(2.806330244925716649839237 + 3.568997623337943121769938j)
|
||||
>>> qhyper([1+j], [2,3+0.5j], 0.25, 3+4j)
|
||||
(9.112885171773400017270226 - 1.272756997166375050700388j)
|
||||
|
||||
Comparing with a summation of the defining series, using
|
||||
:func:`~mpmath.nsum`::
|
||||
|
||||
>>> b, q, z = 3, 0.25, 0.5
|
||||
>>> qhyper([], [b], q, z)
|
||||
0.6221136748254495583228324
|
||||
>>> nsum(lambda n: z**n / qp(q,q,n)/qp(b,q,n) * q**(n*(n-1)), [0,inf])
|
||||
0.6221136748254495583228324
|
||||
|
||||
"""
|
||||
#a_s = [ctx._convert_param(a)[0] for a in a_s]
|
||||
#b_s = [ctx._convert_param(b)[0] for b in b_s]
|
||||
#q = ctx._convert_param(q)[0]
|
||||
a_s = [ctx.convert(a) for a in a_s]
|
||||
b_s = [ctx.convert(b) for b in b_s]
|
||||
q = ctx.convert(q)
|
||||
z = ctx.convert(z)
|
||||
r = len(a_s)
|
||||
s = len(b_s)
|
||||
d = 1+s-r
|
||||
maxterms = kwargs.get('maxterms', 50*ctx.prec)
|
||||
def terms():
|
||||
t = ctx.one
|
||||
yield t
|
||||
qk = 1
|
||||
k = 0
|
||||
x = 1
|
||||
while 1:
|
||||
for a in a_s:
|
||||
p = 1 - a*qk
|
||||
t *= p
|
||||
for b in b_s:
|
||||
p = 1 - b*qk
|
||||
if not p:
|
||||
raise ValueError
|
||||
t /= p
|
||||
t *= z
|
||||
x *= (-1)**d * qk ** d
|
||||
qk *= q
|
||||
t /= (1 - qk)
|
||||
k += 1
|
||||
yield t * x
|
||||
if k > maxterms:
|
||||
raise ctx.NoConvergence
|
||||
return ctx.sum_accurately(terms)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,32 @@
|
||||
from .functions import defun_wrapped
|
||||
|
||||
@defun_wrapped
|
||||
def squarew(ctx, t, amplitude=1, period=1):
|
||||
P = period
|
||||
A = amplitude
|
||||
return A*((-1)**ctx.floor(2*t/P))
|
||||
|
||||
@defun_wrapped
|
||||
def trianglew(ctx, t, amplitude=1, period=1):
|
||||
A = amplitude
|
||||
P = period
|
||||
|
||||
return 2*A*(0.5 - ctx.fabs(1 - 2*ctx.frac(t/P + 0.25)))
|
||||
|
||||
@defun_wrapped
|
||||
def sawtoothw(ctx, t, amplitude=1, period=1):
|
||||
A = amplitude
|
||||
P = period
|
||||
return A*ctx.frac(t/P)
|
||||
|
||||
@defun_wrapped
|
||||
def unit_triangle(ctx, t, amplitude=1):
|
||||
A = amplitude
|
||||
if t <= -1 or t >= 1:
|
||||
return ctx.zero
|
||||
return A*(-ctx.fabs(t) + 1)
|
||||
|
||||
@defun_wrapped
|
||||
def sigmoid(ctx, t, amplitude=1):
|
||||
A = amplitude
|
||||
return A / (1 + ctx.exp(-t))
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,844 @@
|
||||
"""
|
||||
Implements the PSLQ algorithm for integer relation detection,
|
||||
and derivative algorithms for constant recognition.
|
||||
"""
|
||||
|
||||
from .libmp.backend import xrange
|
||||
from .libmp import int_types, sqrt_fixed
|
||||
|
||||
# round to nearest integer (can be done more elegantly...)
|
||||
def round_fixed(x, prec):
|
||||
return ((x + (1<<(prec-1))) >> prec) << prec
|
||||
|
||||
class IdentificationMethods(object):
|
||||
pass
|
||||
|
||||
|
||||
def pslq(ctx, x, tol=None, maxcoeff=1000, maxsteps=100, verbose=False):
|
||||
r"""
|
||||
Given a vector of real numbers `x = [x_0, x_1, ..., x_n]`, ``pslq(x)``
|
||||
uses the PSLQ algorithm to find a list of integers
|
||||
`[c_0, c_1, ..., c_n]` such that
|
||||
|
||||
.. math ::
|
||||
|
||||
|c_1 x_1 + c_2 x_2 + ... + c_n x_n| < \mathrm{tol}
|
||||
|
||||
and such that `\max |c_k| < \mathrm{maxcoeff}`. If no such vector
|
||||
exists, :func:`~mpmath.pslq` returns ``None``. The tolerance defaults to
|
||||
3/4 of the working precision.
|
||||
|
||||
**Examples**
|
||||
|
||||
Find rational approximations for `\pi`::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 15; mp.pretty = True
|
||||
>>> pslq([-1, pi], tol=0.01)
|
||||
[22, 7]
|
||||
>>> pslq([-1, pi], tol=0.001)
|
||||
[355, 113]
|
||||
>>> mpf(22)/7; mpf(355)/113; +pi
|
||||
3.14285714285714
|
||||
3.14159292035398
|
||||
3.14159265358979
|
||||
|
||||
Pi is not a rational number with denominator less than 1000::
|
||||
|
||||
>>> pslq([-1, pi])
|
||||
>>>
|
||||
|
||||
To within the standard precision, it can however be approximated
|
||||
by at least one rational number with denominator less than `10^{12}`::
|
||||
|
||||
>>> p, q = pslq([-1, pi], maxcoeff=10**12)
|
||||
>>> print(p); print(q)
|
||||
238410049439
|
||||
75888275702
|
||||
>>> mpf(p)/q
|
||||
3.14159265358979
|
||||
|
||||
The PSLQ algorithm can be applied to long vectors. For example,
|
||||
we can investigate the rational (in)dependence of integer square
|
||||
roots::
|
||||
|
||||
>>> mp.dps = 30
|
||||
>>> pslq([sqrt(n) for n in range(2, 5+1)])
|
||||
>>>
|
||||
>>> pslq([sqrt(n) for n in range(2, 6+1)])
|
||||
>>>
|
||||
>>> pslq([sqrt(n) for n in range(2, 8+1)])
|
||||
[2, 0, 0, 0, 0, 0, -1]
|
||||
|
||||
**Machin formulas**
|
||||
|
||||
A famous formula for `\pi` is Machin's,
|
||||
|
||||
.. math ::
|
||||
|
||||
\frac{\pi}{4} = 4 \operatorname{acot} 5 - \operatorname{acot} 239
|
||||
|
||||
There are actually infinitely many formulas of this type. Two
|
||||
others are
|
||||
|
||||
.. math ::
|
||||
|
||||
\frac{\pi}{4} = \operatorname{acot} 1
|
||||
|
||||
\frac{\pi}{4} = 12 \operatorname{acot} 49 + 32 \operatorname{acot} 57
|
||||
+ 5 \operatorname{acot} 239 + 12 \operatorname{acot} 110443
|
||||
|
||||
We can easily verify the formulas using the PSLQ algorithm::
|
||||
|
||||
>>> mp.dps = 30
|
||||
>>> pslq([pi/4, acot(1)])
|
||||
[1, -1]
|
||||
>>> pslq([pi/4, acot(5), acot(239)])
|
||||
[1, -4, 1]
|
||||
>>> pslq([pi/4, acot(49), acot(57), acot(239), acot(110443)])
|
||||
[1, -12, -32, 5, -12]
|
||||
|
||||
We could try to generate a custom Machin-like formula by running
|
||||
the PSLQ algorithm with a few inverse cotangent values, for example
|
||||
acot(2), acot(3) ... acot(10). Unfortunately, there is a linear
|
||||
dependence among these values, resulting in only that dependence
|
||||
being detected, with a zero coefficient for `\pi`::
|
||||
|
||||
>>> pslq([pi] + [acot(n) for n in range(2,11)])
|
||||
[0, 1, -1, 0, 0, 0, -1, 0, 0, 0]
|
||||
|
||||
We get better luck by removing linearly dependent terms::
|
||||
|
||||
>>> pslq([pi] + [acot(n) for n in range(2,11) if n not in (3, 5)])
|
||||
[1, -8, 0, 0, 4, 0, 0, 0]
|
||||
|
||||
In other words, we found the following formula::
|
||||
|
||||
>>> 8*acot(2) - 4*acot(7)
|
||||
3.14159265358979323846264338328
|
||||
>>> +pi
|
||||
3.14159265358979323846264338328
|
||||
|
||||
**Algorithm**
|
||||
|
||||
This is a fairly direct translation to Python of the pseudocode given by
|
||||
David Bailey, "The PSLQ Integer Relation Algorithm":
|
||||
http://www.cecm.sfu.ca/organics/papers/bailey/paper/html/node3.html
|
||||
|
||||
The present implementation uses fixed-point instead of floating-point
|
||||
arithmetic, since this is significantly (about 7x) faster.
|
||||
"""
|
||||
|
||||
n = len(x)
|
||||
if n < 2:
|
||||
raise ValueError("n cannot be less than 2")
|
||||
|
||||
# At too low precision, the algorithm becomes meaningless
|
||||
prec = ctx.prec
|
||||
if prec < 53:
|
||||
raise ValueError("prec cannot be less than 53")
|
||||
|
||||
if verbose and prec // max(2,n) < 5:
|
||||
print("Warning: precision for PSLQ may be too low")
|
||||
|
||||
target = int(prec * 0.75)
|
||||
|
||||
if tol is None:
|
||||
tol = ctx.mpf(2)**(-target)
|
||||
else:
|
||||
tol = ctx.convert(tol)
|
||||
|
||||
extra = 60
|
||||
prec += extra
|
||||
|
||||
if verbose:
|
||||
print("PSLQ using prec %i and tol %s" % (prec, ctx.nstr(tol)))
|
||||
|
||||
tol = ctx.to_fixed(tol, prec)
|
||||
assert tol
|
||||
|
||||
# Convert to fixed-point numbers. The dummy None is added so we can
|
||||
# use 1-based indexing. (This just allows us to be consistent with
|
||||
# Bailey's indexing. The algorithm is 100 lines long, so debugging
|
||||
# a single wrong index can be painful.)
|
||||
x = [None] + [ctx.to_fixed(ctx.mpf(xk), prec) for xk in x]
|
||||
|
||||
# Sanity check on magnitudes
|
||||
minx = min(abs(xx) for xx in x[1:])
|
||||
if not minx:
|
||||
raise ValueError("PSLQ requires a vector of nonzero numbers")
|
||||
if minx < tol//100:
|
||||
if verbose:
|
||||
print("STOPPING: (one number is too small)")
|
||||
return None
|
||||
|
||||
g = sqrt_fixed((4<<prec)//3, prec)
|
||||
A = {}
|
||||
B = {}
|
||||
H = {}
|
||||
# Initialization
|
||||
# step 1
|
||||
for i in xrange(1, n+1):
|
||||
for j in xrange(1, n+1):
|
||||
A[i,j] = B[i,j] = (i==j) << prec
|
||||
H[i,j] = 0
|
||||
# step 2
|
||||
s = [None] + [0] * n
|
||||
for k in xrange(1, n+1):
|
||||
t = 0
|
||||
for j in xrange(k, n+1):
|
||||
t += (x[j]**2 >> prec)
|
||||
s[k] = sqrt_fixed(t, prec)
|
||||
t = s[1]
|
||||
y = x[:]
|
||||
for k in xrange(1, n+1):
|
||||
y[k] = (x[k] << prec) // t
|
||||
s[k] = (s[k] << prec) // t
|
||||
# step 3
|
||||
for i in xrange(1, n+1):
|
||||
for j in xrange(i+1, n):
|
||||
H[i,j] = 0
|
||||
if i <= n-1:
|
||||
if s[i]:
|
||||
H[i,i] = (s[i+1] << prec) // s[i]
|
||||
else:
|
||||
H[i,i] = 0
|
||||
for j in range(1, i):
|
||||
sjj1 = s[j]*s[j+1]
|
||||
if sjj1:
|
||||
H[i,j] = ((-y[i]*y[j])<<prec)//sjj1
|
||||
else:
|
||||
H[i,j] = 0
|
||||
# step 4
|
||||
for i in xrange(2, n+1):
|
||||
for j in xrange(i-1, 0, -1):
|
||||
#t = floor(H[i,j]/H[j,j] + 0.5)
|
||||
if H[j,j]:
|
||||
t = round_fixed((H[i,j] << prec)//H[j,j], prec)
|
||||
else:
|
||||
#t = 0
|
||||
continue
|
||||
y[j] = y[j] + (t*y[i] >> prec)
|
||||
for k in xrange(1, j+1):
|
||||
H[i,k] = H[i,k] - (t*H[j,k] >> prec)
|
||||
for k in xrange(1, n+1):
|
||||
A[i,k] = A[i,k] - (t*A[j,k] >> prec)
|
||||
B[k,j] = B[k,j] + (t*B[k,i] >> prec)
|
||||
# Main algorithm
|
||||
for REP in range(maxsteps):
|
||||
# Step 1
|
||||
m = -1
|
||||
szmax = -1
|
||||
for i in range(1, n):
|
||||
h = H[i,i]
|
||||
sz = (g**i * abs(h)) >> (prec*(i-1))
|
||||
if sz > szmax:
|
||||
m = i
|
||||
szmax = sz
|
||||
# Step 2
|
||||
y[m], y[m+1] = y[m+1], y[m]
|
||||
for i in xrange(1,n+1): H[m,i], H[m+1,i] = H[m+1,i], H[m,i]
|
||||
for i in xrange(1,n+1): A[m,i], A[m+1,i] = A[m+1,i], A[m,i]
|
||||
for i in xrange(1,n+1): B[i,m], B[i,m+1] = B[i,m+1], B[i,m]
|
||||
# Step 3
|
||||
if m <= n - 2:
|
||||
t0 = sqrt_fixed((H[m,m]**2 + H[m,m+1]**2)>>prec, prec)
|
||||
# A zero element probably indicates that the precision has
|
||||
# been exhausted. XXX: this could be spurious, due to
|
||||
# using fixed-point arithmetic
|
||||
if not t0:
|
||||
break
|
||||
t1 = (H[m,m] << prec) // t0
|
||||
t2 = (H[m,m+1] << prec) // t0
|
||||
for i in xrange(m, n+1):
|
||||
t3 = H[i,m]
|
||||
t4 = H[i,m+1]
|
||||
H[i,m] = (t1*t3+t2*t4) >> prec
|
||||
H[i,m+1] = (-t2*t3+t1*t4) >> prec
|
||||
# Step 4
|
||||
for i in xrange(m+1, n+1):
|
||||
for j in xrange(min(i-1, m+1), 0, -1):
|
||||
try:
|
||||
t = round_fixed((H[i,j] << prec)//H[j,j], prec)
|
||||
# Precision probably exhausted
|
||||
except ZeroDivisionError:
|
||||
break
|
||||
y[j] = y[j] + ((t*y[i]) >> prec)
|
||||
for k in xrange(1, j+1):
|
||||
H[i,k] = H[i,k] - (t*H[j,k] >> prec)
|
||||
for k in xrange(1, n+1):
|
||||
A[i,k] = A[i,k] - (t*A[j,k] >> prec)
|
||||
B[k,j] = B[k,j] + (t*B[k,i] >> prec)
|
||||
# Until a relation is found, the error typically decreases
|
||||
# slowly (e.g. a factor 1-10) with each step TODO: we could
|
||||
# compare err from two successive iterations. If there is a
|
||||
# large drop (several orders of magnitude), that indicates a
|
||||
# "high quality" relation was detected. Reporting this to
|
||||
# the user somehow might be useful.
|
||||
best_err = maxcoeff<<prec
|
||||
for i in xrange(1, n+1):
|
||||
err = abs(y[i])
|
||||
# Maybe we are done?
|
||||
if err < tol:
|
||||
# We are done if the coefficients are acceptable
|
||||
vec = [int(round_fixed(B[j,i], prec) >> prec) for j in \
|
||||
range(1,n+1)]
|
||||
if max(abs(v) for v in vec) < maxcoeff:
|
||||
if verbose:
|
||||
print("FOUND relation at iter %i/%i, error: %s" % \
|
||||
(REP, maxsteps, ctx.nstr(err / ctx.mpf(2)**prec, 1)))
|
||||
return vec
|
||||
best_err = min(err, best_err)
|
||||
# Calculate a lower bound for the norm. We could do this
|
||||
# more exactly (using the Euclidean norm) but there is probably
|
||||
# no practical benefit.
|
||||
recnorm = max(abs(h) for h in H.values())
|
||||
if recnorm:
|
||||
norm = ((1 << (2*prec)) // recnorm) >> prec
|
||||
norm //= 100
|
||||
else:
|
||||
norm = ctx.inf
|
||||
if verbose:
|
||||
print("%i/%i: Error: %8s Norm: %s" % \
|
||||
(REP, maxsteps, ctx.nstr(best_err / ctx.mpf(2)**prec, 1), norm))
|
||||
if norm >= maxcoeff:
|
||||
break
|
||||
if verbose:
|
||||
print("CANCELLING after step %i/%i." % (REP, maxsteps))
|
||||
print("Could not find an integer relation. Norm bound: %s" % norm)
|
||||
return None
|
||||
|
||||
def findpoly(ctx, x, n=1, **kwargs):
|
||||
r"""
|
||||
``findpoly(x, n)`` returns the coefficients of an integer
|
||||
polynomial `P` of degree at most `n` such that `P(x) \approx 0`.
|
||||
If no polynomial having `x` as a root can be found,
|
||||
:func:`~mpmath.findpoly` returns ``None``.
|
||||
|
||||
:func:`~mpmath.findpoly` works by successively calling :func:`~mpmath.pslq` with
|
||||
the vectors `[1, x]`, `[1, x, x^2]`, `[1, x, x^2, x^3]`, ...,
|
||||
`[1, x, x^2, .., x^n]` as input. Keyword arguments given to
|
||||
:func:`~mpmath.findpoly` are forwarded verbatim to :func:`~mpmath.pslq`. In
|
||||
particular, you can specify a tolerance for `P(x)` with ``tol``
|
||||
and a maximum permitted coefficient size with ``maxcoeff``.
|
||||
|
||||
For large values of `n`, it is recommended to run :func:`~mpmath.findpoly`
|
||||
at high precision; preferably 50 digits or more.
|
||||
|
||||
**Examples**
|
||||
|
||||
By default (degree `n = 1`), :func:`~mpmath.findpoly` simply finds a linear
|
||||
polynomial with a rational root::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 15; mp.pretty = True
|
||||
>>> findpoly(0.7)
|
||||
[-10, 7]
|
||||
|
||||
The generated coefficient list is valid input to ``polyval`` and
|
||||
``polyroots``::
|
||||
|
||||
>>> nprint(polyval(findpoly(phi, 2), phi), 1)
|
||||
-2.0e-16
|
||||
>>> for r in polyroots(findpoly(phi, 2)):
|
||||
... print(r)
|
||||
...
|
||||
-0.618033988749895
|
||||
1.61803398874989
|
||||
|
||||
Numbers of the form `m + n \sqrt p` for integers `(m, n, p)` are
|
||||
solutions to quadratic equations. As we find here, `1+\sqrt 2`
|
||||
is a root of the polynomial `x^2 - 2x - 1`::
|
||||
|
||||
>>> findpoly(1+sqrt(2), 2)
|
||||
[1, -2, -1]
|
||||
>>> findroot(lambda x: x**2 - 2*x - 1, 1)
|
||||
2.4142135623731
|
||||
|
||||
Despite only containing square roots, the following number results
|
||||
in a polynomial of degree 4::
|
||||
|
||||
>>> findpoly(sqrt(2)+sqrt(3), 4)
|
||||
[1, 0, -10, 0, 1]
|
||||
|
||||
In fact, `x^4 - 10x^2 + 1` is the *minimal polynomial* of
|
||||
`r = \sqrt 2 + \sqrt 3`, meaning that a rational polynomial of
|
||||
lower degree having `r` as a root does not exist. Given sufficient
|
||||
precision, :func:`~mpmath.findpoly` will usually find the correct
|
||||
minimal polynomial of a given algebraic number.
|
||||
|
||||
**Non-algebraic numbers**
|
||||
|
||||
If :func:`~mpmath.findpoly` fails to find a polynomial with given
|
||||
coefficient size and tolerance constraints, that means no such
|
||||
polynomial exists.
|
||||
|
||||
We can verify that `\pi` is not an algebraic number of degree 3 with
|
||||
coefficients less than 1000::
|
||||
|
||||
>>> mp.dps = 15
|
||||
>>> findpoly(pi, 3)
|
||||
>>>
|
||||
|
||||
It is always possible to find an algebraic approximation of a number
|
||||
using one (or several) of the following methods:
|
||||
|
||||
1. Increasing the permitted degree
|
||||
2. Allowing larger coefficients
|
||||
3. Reducing the tolerance
|
||||
|
||||
One example of each method is shown below::
|
||||
|
||||
>>> mp.dps = 15
|
||||
>>> findpoly(pi, 4)
|
||||
[95, -545, 863, -183, -298]
|
||||
>>> findpoly(pi, 3, maxcoeff=10000)
|
||||
[836, -1734, -2658, -457]
|
||||
>>> findpoly(pi, 3, tol=1e-7)
|
||||
[-4, 22, -29, -2]
|
||||
|
||||
It is unknown whether Euler's constant is transcendental (or even
|
||||
irrational). We can use :func:`~mpmath.findpoly` to check that if is
|
||||
an algebraic number, its minimal polynomial must have degree
|
||||
at least 7 and a coefficient of magnitude at least 1000000::
|
||||
|
||||
>>> mp.dps = 200
|
||||
>>> findpoly(euler, 6, maxcoeff=10**6, tol=1e-100, maxsteps=1000)
|
||||
>>>
|
||||
|
||||
Note that the high precision and strict tolerance is necessary
|
||||
for such high-degree runs, since otherwise unwanted low-accuracy
|
||||
approximations will be detected. It may also be necessary to set
|
||||
maxsteps high to prevent a premature exit (before the coefficient
|
||||
bound has been reached). Running with ``verbose=True`` to get an
|
||||
idea what is happening can be useful.
|
||||
"""
|
||||
x = ctx.mpf(x)
|
||||
if n < 1:
|
||||
raise ValueError("n cannot be less than 1")
|
||||
if x == 0:
|
||||
return [1, 0]
|
||||
xs = [ctx.mpf(1)]
|
||||
for i in range(1,n+1):
|
||||
xs.append(x**i)
|
||||
a = ctx.pslq(xs, **kwargs)
|
||||
if a is not None:
|
||||
return a[::-1]
|
||||
|
||||
def fracgcd(p, q):
|
||||
x, y = p, q
|
||||
while y:
|
||||
x, y = y, x % y
|
||||
if x != 1:
|
||||
p //= x
|
||||
q //= x
|
||||
if q == 1:
|
||||
return p
|
||||
return p, q
|
||||
|
||||
def pslqstring(r, constants):
|
||||
q = r[0]
|
||||
r = r[1:]
|
||||
s = []
|
||||
for i in range(len(r)):
|
||||
p = r[i]
|
||||
if p:
|
||||
z = fracgcd(-p,q)
|
||||
cs = constants[i][1]
|
||||
if cs == '1':
|
||||
cs = ''
|
||||
else:
|
||||
cs = '*' + cs
|
||||
if isinstance(z, int_types):
|
||||
if z > 0: term = str(z) + cs
|
||||
else: term = ("(%s)" % z) + cs
|
||||
else:
|
||||
term = ("(%s/%s)" % z) + cs
|
||||
s.append(term)
|
||||
s = ' + '.join(s)
|
||||
if '+' in s or '*' in s:
|
||||
s = '(' + s + ')'
|
||||
return s or '0'
|
||||
|
||||
def prodstring(r, constants):
|
||||
q = r[0]
|
||||
r = r[1:]
|
||||
num = []
|
||||
den = []
|
||||
for i in range(len(r)):
|
||||
p = r[i]
|
||||
if p:
|
||||
z = fracgcd(-p,q)
|
||||
cs = constants[i][1]
|
||||
if isinstance(z, int_types):
|
||||
if abs(z) == 1: t = cs
|
||||
else: t = '%s**%s' % (cs, abs(z))
|
||||
([num,den][z<0]).append(t)
|
||||
else:
|
||||
t = '%s**(%s/%s)' % (cs, abs(z[0]), z[1])
|
||||
([num,den][z[0]<0]).append(t)
|
||||
num = '*'.join(num)
|
||||
den = '*'.join(den)
|
||||
if num and den: return "(%s)/(%s)" % (num, den)
|
||||
if num: return num
|
||||
if den: return "1/(%s)" % den
|
||||
|
||||
def quadraticstring(ctx,t,a,b,c):
|
||||
if c < 0:
|
||||
a,b,c = -a,-b,-c
|
||||
u1 = (-b+ctx.sqrt(b**2-4*a*c))/(2*c)
|
||||
u2 = (-b-ctx.sqrt(b**2-4*a*c))/(2*c)
|
||||
if abs(u1-t) < abs(u2-t):
|
||||
if b: s = '((%s+sqrt(%s))/%s)' % (-b,b**2-4*a*c,2*c)
|
||||
else: s = '(sqrt(%s)/%s)' % (-4*a*c,2*c)
|
||||
else:
|
||||
if b: s = '((%s-sqrt(%s))/%s)' % (-b,b**2-4*a*c,2*c)
|
||||
else: s = '(-sqrt(%s)/%s)' % (-4*a*c,2*c)
|
||||
return s
|
||||
|
||||
# Transformation y = f(x,c), with inverse function x = f(y,c)
|
||||
# The third entry indicates whether the transformation is
|
||||
# redundant when c = 1
|
||||
transforms = [
|
||||
(lambda ctx,x,c: x*c, '$y/$c', 0),
|
||||
(lambda ctx,x,c: x/c, '$c*$y', 1),
|
||||
(lambda ctx,x,c: c/x, '$c/$y', 0),
|
||||
(lambda ctx,x,c: (x*c)**2, 'sqrt($y)/$c', 0),
|
||||
(lambda ctx,x,c: (x/c)**2, '$c*sqrt($y)', 1),
|
||||
(lambda ctx,x,c: (c/x)**2, '$c/sqrt($y)', 0),
|
||||
(lambda ctx,x,c: c*x**2, 'sqrt($y)/sqrt($c)', 1),
|
||||
(lambda ctx,x,c: x**2/c, 'sqrt($c)*sqrt($y)', 1),
|
||||
(lambda ctx,x,c: c/x**2, 'sqrt($c)/sqrt($y)', 1),
|
||||
(lambda ctx,x,c: ctx.sqrt(x*c), '$y**2/$c', 0),
|
||||
(lambda ctx,x,c: ctx.sqrt(x/c), '$c*$y**2', 1),
|
||||
(lambda ctx,x,c: ctx.sqrt(c/x), '$c/$y**2', 0),
|
||||
(lambda ctx,x,c: c*ctx.sqrt(x), '$y**2/$c**2', 1),
|
||||
(lambda ctx,x,c: ctx.sqrt(x)/c, '$c**2*$y**2', 1),
|
||||
(lambda ctx,x,c: c/ctx.sqrt(x), '$c**2/$y**2', 1),
|
||||
(lambda ctx,x,c: ctx.exp(x*c), 'log($y)/$c', 0),
|
||||
(lambda ctx,x,c: ctx.exp(x/c), '$c*log($y)', 1),
|
||||
(lambda ctx,x,c: ctx.exp(c/x), '$c/log($y)', 0),
|
||||
(lambda ctx,x,c: c*ctx.exp(x), 'log($y/$c)', 1),
|
||||
(lambda ctx,x,c: ctx.exp(x)/c, 'log($c*$y)', 1),
|
||||
(lambda ctx,x,c: c/ctx.exp(x), 'log($c/$y)', 0),
|
||||
(lambda ctx,x,c: ctx.ln(x*c), 'exp($y)/$c', 0),
|
||||
(lambda ctx,x,c: ctx.ln(x/c), '$c*exp($y)', 1),
|
||||
(lambda ctx,x,c: ctx.ln(c/x), '$c/exp($y)', 0),
|
||||
(lambda ctx,x,c: c*ctx.ln(x), 'exp($y/$c)', 1),
|
||||
(lambda ctx,x,c: ctx.ln(x)/c, 'exp($c*$y)', 1),
|
||||
(lambda ctx,x,c: c/ctx.ln(x), 'exp($c/$y)', 0),
|
||||
]
|
||||
|
||||
def identify(ctx, x, constants=[], tol=None, maxcoeff=1000, full=False,
|
||||
verbose=False):
|
||||
r"""
|
||||
Given a real number `x`, ``identify(x)`` attempts to find an exact
|
||||
formula for `x`. This formula is returned as a string. If no match
|
||||
is found, ``None`` is returned. With ``full=True``, a list of
|
||||
matching formulas is returned.
|
||||
|
||||
As a simple example, :func:`~mpmath.identify` will find an algebraic
|
||||
formula for the golden ratio::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 15; mp.pretty = True
|
||||
>>> identify(phi)
|
||||
'((1+sqrt(5))/2)'
|
||||
|
||||
:func:`~mpmath.identify` can identify simple algebraic numbers and simple
|
||||
combinations of given base constants, as well as certain basic
|
||||
transformations thereof. More specifically, :func:`~mpmath.identify`
|
||||
looks for the following:
|
||||
|
||||
1. Fractions
|
||||
2. Quadratic algebraic numbers
|
||||
3. Rational linear combinations of the base constants
|
||||
4. Any of the above after first transforming `x` into `f(x)` where
|
||||
`f(x)` is `1/x`, `\sqrt x`, `x^2`, `\log x` or `\exp x`, either
|
||||
directly or with `x` or `f(x)` multiplied or divided by one of
|
||||
the base constants
|
||||
5. Products of fractional powers of the base constants and
|
||||
small integers
|
||||
|
||||
Base constants can be given as a list of strings representing mpmath
|
||||
expressions (:func:`~mpmath.identify` will ``eval`` the strings to numerical
|
||||
values and use the original strings for the output), or as a dict of
|
||||
formula:value pairs.
|
||||
|
||||
In order not to produce spurious results, :func:`~mpmath.identify` should
|
||||
be used with high precision; preferably 50 digits or more.
|
||||
|
||||
**Examples**
|
||||
|
||||
Simple identifications can be performed safely at standard
|
||||
precision. Here the default recognition of rational, algebraic,
|
||||
and exp/log of algebraic numbers is demonstrated::
|
||||
|
||||
>>> mp.dps = 15
|
||||
>>> identify(0.22222222222222222)
|
||||
'(2/9)'
|
||||
>>> identify(1.9662210973805663)
|
||||
'sqrt(((24+sqrt(48))/8))'
|
||||
>>> identify(4.1132503787829275)
|
||||
'exp((sqrt(8)/2))'
|
||||
>>> identify(0.881373587019543)
|
||||
'log(((2+sqrt(8))/2))'
|
||||
|
||||
By default, :func:`~mpmath.identify` does not recognize `\pi`. At standard
|
||||
precision it finds a not too useful approximation. At slightly
|
||||
increased precision, this approximation is no longer accurate
|
||||
enough and :func:`~mpmath.identify` more correctly returns ``None``::
|
||||
|
||||
>>> identify(pi)
|
||||
'(2**(176/117)*3**(20/117)*5**(35/39))/(7**(92/117))'
|
||||
>>> mp.dps = 30
|
||||
>>> identify(pi)
|
||||
>>>
|
||||
|
||||
Numbers such as `\pi`, and simple combinations of user-defined
|
||||
constants, can be identified if they are provided explicitly::
|
||||
|
||||
>>> identify(3*pi-2*e, ['pi', 'e'])
|
||||
'(3*pi + (-2)*e)'
|
||||
|
||||
Here is an example using a dict of constants. Note that the
|
||||
constants need not be "atomic"; :func:`~mpmath.identify` can just
|
||||
as well express the given number in terms of expressions
|
||||
given by formulas::
|
||||
|
||||
>>> identify(pi+e, {'a':pi+2, 'b':2*e})
|
||||
'((-2) + 1*a + (1/2)*b)'
|
||||
|
||||
Next, we attempt some identifications with a set of base constants.
|
||||
It is necessary to increase the precision a bit.
|
||||
|
||||
>>> mp.dps = 50
|
||||
>>> base = ['sqrt(2)','pi','log(2)']
|
||||
>>> identify(0.25, base)
|
||||
'(1/4)'
|
||||
>>> identify(3*pi + 2*sqrt(2) + 5*log(2)/7, base)
|
||||
'(2*sqrt(2) + 3*pi + (5/7)*log(2))'
|
||||
>>> identify(exp(pi+2), base)
|
||||
'exp((2 + 1*pi))'
|
||||
>>> identify(1/(3+sqrt(2)), base)
|
||||
'((3/7) + (-1/7)*sqrt(2))'
|
||||
>>> identify(sqrt(2)/(3*pi+4), base)
|
||||
'sqrt(2)/(4 + 3*pi)'
|
||||
>>> identify(5**(mpf(1)/3)*pi*log(2)**2, base)
|
||||
'5**(1/3)*pi*log(2)**2'
|
||||
|
||||
An example of an erroneous solution being found when too low
|
||||
precision is used::
|
||||
|
||||
>>> mp.dps = 15
|
||||
>>> identify(1/(3*pi-4*e+sqrt(8)), ['pi', 'e', 'sqrt(2)'])
|
||||
'((11/25) + (-158/75)*pi + (76/75)*e + (44/15)*sqrt(2))'
|
||||
>>> mp.dps = 50
|
||||
>>> identify(1/(3*pi-4*e+sqrt(8)), ['pi', 'e', 'sqrt(2)'])
|
||||
'1/(3*pi + (-4)*e + 2*sqrt(2))'
|
||||
|
||||
**Finding approximate solutions**
|
||||
|
||||
The tolerance ``tol`` defaults to 3/4 of the working precision.
|
||||
Lowering the tolerance is useful for finding approximate matches.
|
||||
We can for example try to generate approximations for pi::
|
||||
|
||||
>>> mp.dps = 15
|
||||
>>> identify(pi, tol=1e-2)
|
||||
'(22/7)'
|
||||
>>> identify(pi, tol=1e-3)
|
||||
'(355/113)'
|
||||
>>> identify(pi, tol=1e-10)
|
||||
'(5**(339/269))/(2**(64/269)*3**(13/269)*7**(92/269))'
|
||||
|
||||
With ``full=True``, and by supplying a few base constants,
|
||||
``identify`` can generate almost endless lists of approximations
|
||||
for any number (the output below has been truncated to show only
|
||||
the first few)::
|
||||
|
||||
>>> for p in identify(pi, ['e', 'catalan'], tol=1e-5, full=True):
|
||||
... print(p)
|
||||
... # doctest: +ELLIPSIS
|
||||
e/log((6 + (-4/3)*e))
|
||||
(3**3*5*e*catalan**2)/(2*7**2)
|
||||
sqrt(((-13) + 1*e + 22*catalan))
|
||||
log(((-6) + 24*e + 4*catalan)/e)
|
||||
exp(catalan*((-1/5) + (8/15)*e))
|
||||
catalan*(6 + (-6)*e + 15*catalan)
|
||||
sqrt((5 + 26*e + (-3)*catalan))/e
|
||||
e*sqrt(((-27) + 2*e + 25*catalan))
|
||||
log(((-1) + (-11)*e + 59*catalan))
|
||||
((3/20) + (21/20)*e + (3/20)*catalan)
|
||||
...
|
||||
|
||||
The numerical values are roughly as close to `\pi` as permitted by the
|
||||
specified tolerance:
|
||||
|
||||
>>> e/log(6-4*e/3)
|
||||
3.14157719846001
|
||||
>>> 135*e*catalan**2/98
|
||||
3.14166950419369
|
||||
>>> sqrt(e-13+22*catalan)
|
||||
3.14158000062992
|
||||
>>> log(24*e-6+4*catalan)-1
|
||||
3.14158791577159
|
||||
|
||||
**Symbolic processing**
|
||||
|
||||
The output formula can be evaluated as a Python expression.
|
||||
Note however that if fractions (like '2/3') are present in
|
||||
the formula, Python's :func:`~mpmath.eval()` may erroneously perform
|
||||
integer division. Note also that the output is not necessarily
|
||||
in the algebraically simplest form::
|
||||
|
||||
>>> identify(sqrt(2))
|
||||
'(sqrt(8)/2)'
|
||||
|
||||
As a solution to both problems, consider using SymPy's
|
||||
:func:`~mpmath.sympify` to convert the formula into a symbolic expression.
|
||||
SymPy can be used to pretty-print or further simplify the formula
|
||||
symbolically::
|
||||
|
||||
>>> from sympy import sympify # doctest: +SKIP
|
||||
>>> sympify(identify(sqrt(2))) # doctest: +SKIP
|
||||
2**(1/2)
|
||||
|
||||
Sometimes :func:`~mpmath.identify` can simplify an expression further than
|
||||
a symbolic algorithm::
|
||||
|
||||
>>> from sympy import simplify # doctest: +SKIP
|
||||
>>> x = sympify('-1/(-3/2+(1/2)*5**(1/2))*(3/2-1/2*5**(1/2))**(1/2)') # doctest: +SKIP
|
||||
>>> x # doctest: +SKIP
|
||||
(3/2 - 5**(1/2)/2)**(-1/2)
|
||||
>>> x = simplify(x) # doctest: +SKIP
|
||||
>>> x # doctest: +SKIP
|
||||
2/(6 - 2*5**(1/2))**(1/2)
|
||||
>>> mp.dps = 30 # doctest: +SKIP
|
||||
>>> x = sympify(identify(x.evalf(30))) # doctest: +SKIP
|
||||
>>> x # doctest: +SKIP
|
||||
1/2 + 5**(1/2)/2
|
||||
|
||||
(In fact, this functionality is available directly in SymPy as the
|
||||
function :func:`~mpmath.nsimplify`, which is essentially a wrapper for
|
||||
:func:`~mpmath.identify`.)
|
||||
|
||||
**Miscellaneous issues and limitations**
|
||||
|
||||
The input `x` must be a real number. All base constants must be
|
||||
positive real numbers and must not be rationals or rational linear
|
||||
combinations of each other.
|
||||
|
||||
The worst-case computation time grows quickly with the number of
|
||||
base constants. Already with 3 or 4 base constants,
|
||||
:func:`~mpmath.identify` may require several seconds to finish. To search
|
||||
for relations among a large number of constants, you should
|
||||
consider using :func:`~mpmath.pslq` directly.
|
||||
|
||||
The extended transformations are applied to x, not the constants
|
||||
separately. As a result, ``identify`` will for example be able to
|
||||
recognize ``exp(2*pi+3)`` with ``pi`` given as a base constant, but
|
||||
not ``2*exp(pi)+3``. It will be able to recognize the latter if
|
||||
``exp(pi)`` is given explicitly as a base constant.
|
||||
|
||||
"""
|
||||
|
||||
solutions = []
|
||||
|
||||
def addsolution(s):
|
||||
if verbose: print("Found: ", s)
|
||||
solutions.append(s)
|
||||
|
||||
x = ctx.mpf(x)
|
||||
|
||||
# Further along, x will be assumed positive
|
||||
if x == 0:
|
||||
if full: return ['0']
|
||||
else: return '0'
|
||||
if x < 0:
|
||||
sol = ctx.identify(-x, constants, tol, maxcoeff, full, verbose)
|
||||
if sol is None:
|
||||
return sol
|
||||
if full:
|
||||
return ["-(%s)"%s for s in sol]
|
||||
else:
|
||||
return "-(%s)" % sol
|
||||
|
||||
if tol:
|
||||
tol = ctx.mpf(tol)
|
||||
else:
|
||||
tol = ctx.eps**0.7
|
||||
M = maxcoeff
|
||||
|
||||
if constants:
|
||||
if isinstance(constants, dict):
|
||||
constants = [(ctx.mpf(v), name) for (name, v) in sorted(constants.items())]
|
||||
else:
|
||||
namespace = dict((name, getattr(ctx,name)) for name in dir(ctx))
|
||||
constants = [(eval(p, namespace), p) for p in constants]
|
||||
else:
|
||||
constants = []
|
||||
|
||||
# We always want to find at least rational terms
|
||||
if 1 not in [value for (name, value) in constants]:
|
||||
constants = [(ctx.mpf(1), '1')] + constants
|
||||
|
||||
# PSLQ with simple algebraic and functional transformations
|
||||
for ft, ftn, red in transforms:
|
||||
for c, cn in constants:
|
||||
if red and cn == '1':
|
||||
continue
|
||||
t = ft(ctx,x,c)
|
||||
# Prevent exponential transforms from wreaking havoc
|
||||
if abs(t) > M**2 or abs(t) < tol:
|
||||
continue
|
||||
# Linear combination of base constants
|
||||
r = ctx.pslq([t] + [a[0] for a in constants], tol, M)
|
||||
s = None
|
||||
if r is not None and max(abs(uw) for uw in r) <= M and r[0]:
|
||||
s = pslqstring(r, constants)
|
||||
# Quadratic algebraic numbers
|
||||
else:
|
||||
q = ctx.pslq([ctx.one, t, t**2], tol, M)
|
||||
if q is not None and len(q) == 3 and q[2]:
|
||||
aa, bb, cc = q
|
||||
if max(abs(aa),abs(bb),abs(cc)) <= M:
|
||||
s = quadraticstring(ctx,t,aa,bb,cc)
|
||||
if s:
|
||||
if cn == '1' and ('/$c' in ftn):
|
||||
s = ftn.replace('$y', s).replace('/$c', '')
|
||||
else:
|
||||
s = ftn.replace('$y', s).replace('$c', cn)
|
||||
addsolution(s)
|
||||
if not full: return solutions[0]
|
||||
|
||||
if verbose:
|
||||
print(".")
|
||||
|
||||
# Check for a direct multiplicative formula
|
||||
if x != 1:
|
||||
# Allow fractional powers of fractions
|
||||
ilogs = [2,3,5,7]
|
||||
# Watch out for existing fractional powers of fractions
|
||||
logs = []
|
||||
for a, s in constants:
|
||||
if not sum(bool(ctx.findpoly(ctx.ln(a)/ctx.ln(i),1)) for i in ilogs):
|
||||
logs.append((ctx.ln(a), s))
|
||||
logs = [(ctx.ln(i),str(i)) for i in ilogs] + logs
|
||||
r = ctx.pslq([ctx.ln(x)] + [a[0] for a in logs], tol, M)
|
||||
if r is not None and max(abs(uw) for uw in r) <= M and r[0]:
|
||||
addsolution(prodstring(r, logs))
|
||||
if not full: return solutions[0]
|
||||
|
||||
if full:
|
||||
return sorted(solutions, key=len)
|
||||
else:
|
||||
return None
|
||||
|
||||
IdentificationMethods.pslq = pslq
|
||||
IdentificationMethods.findpoly = findpoly
|
||||
IdentificationMethods.identify = identify
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
@@ -0,0 +1,77 @@
|
||||
from .libmpf import (prec_to_dps, dps_to_prec, repr_dps,
|
||||
round_down, round_up, round_floor, round_ceiling, round_nearest,
|
||||
to_pickable, from_pickable, ComplexResult,
|
||||
fzero, fnzero, fone, fnone, ftwo, ften, fhalf, fnan, finf, fninf,
|
||||
math_float_inf, round_int, normalize, normalize1,
|
||||
from_man_exp, from_int, to_man_exp, to_int, mpf_ceil, mpf_floor,
|
||||
mpf_nint, mpf_frac,
|
||||
from_float, from_npfloat, from_Decimal, to_float, from_rational, to_rational, to_fixed,
|
||||
mpf_rand, mpf_eq, mpf_hash, mpf_cmp, mpf_lt, mpf_le, mpf_gt, mpf_ge,
|
||||
mpf_pos, mpf_neg, mpf_abs, mpf_sign, mpf_add, mpf_sub, mpf_sum,
|
||||
mpf_mul, mpf_mul_int, mpf_shift, mpf_frexp,
|
||||
mpf_div, mpf_rdiv_int, mpf_mod, mpf_pow_int,
|
||||
mpf_perturb,
|
||||
to_digits_exp, to_str, str_to_man_exp, from_str, from_bstr, to_bstr,
|
||||
mpf_sqrt, mpf_hypot)
|
||||
|
||||
from .libmpc import (mpc_one, mpc_zero, mpc_two, mpc_half,
|
||||
mpc_is_inf, mpc_is_infnan, mpc_to_str, mpc_to_complex, mpc_hash,
|
||||
mpc_conjugate, mpc_is_nonzero, mpc_add, mpc_add_mpf,
|
||||
mpc_sub, mpc_sub_mpf, mpc_pos, mpc_neg, mpc_shift, mpc_abs,
|
||||
mpc_arg, mpc_floor, mpc_ceil, mpc_nint, mpc_frac, mpc_mul, mpc_square,
|
||||
mpc_mul_mpf, mpc_mul_imag_mpf, mpc_mul_int,
|
||||
mpc_div, mpc_div_mpf, mpc_reciprocal, mpc_mpf_div,
|
||||
complex_int_pow, mpc_pow, mpc_pow_mpf, mpc_pow_int,
|
||||
mpc_sqrt, mpc_nthroot, mpc_cbrt, mpc_exp, mpc_log, mpc_cos, mpc_sin,
|
||||
mpc_tan, mpc_cos_pi, mpc_sin_pi, mpc_cosh, mpc_sinh, mpc_tanh,
|
||||
mpc_atan, mpc_acos, mpc_asin, mpc_asinh, mpc_acosh, mpc_atanh,
|
||||
mpc_fibonacci, mpf_expj, mpf_expjpi, mpc_expj, mpc_expjpi,
|
||||
mpc_cos_sin, mpc_cos_sin_pi)
|
||||
|
||||
from .libelefun import (ln2_fixed, mpf_ln2, ln10_fixed, mpf_ln10,
|
||||
pi_fixed, mpf_pi, e_fixed, mpf_e, phi_fixed, mpf_phi,
|
||||
degree_fixed, mpf_degree,
|
||||
mpf_pow, mpf_nthroot, mpf_cbrt, log_int_fixed, agm_fixed,
|
||||
mpf_log, mpf_log_hypot, mpf_exp, mpf_cos_sin, mpf_cos, mpf_sin, mpf_tan,
|
||||
mpf_cos_sin_pi, mpf_cos_pi, mpf_sin_pi, mpf_cosh_sinh,
|
||||
mpf_cosh, mpf_sinh, mpf_tanh, mpf_atan, mpf_atan2, mpf_asin,
|
||||
mpf_acos, mpf_asinh, mpf_acosh, mpf_atanh, mpf_fibonacci)
|
||||
|
||||
from .libhyper import (NoConvergence, make_hyp_summator,
|
||||
mpf_erf, mpf_erfc, mpf_ei, mpc_ei, mpf_e1, mpc_e1, mpf_expint,
|
||||
mpf_ci_si, mpf_ci, mpf_si, mpc_ci, mpc_si, mpf_besseljn,
|
||||
mpc_besseljn, mpf_agm, mpf_agm1, mpc_agm, mpc_agm1,
|
||||
mpf_ellipk, mpc_ellipk, mpf_ellipe, mpc_ellipe)
|
||||
|
||||
from .gammazeta import (catalan_fixed, mpf_catalan,
|
||||
khinchin_fixed, mpf_khinchin, glaisher_fixed, mpf_glaisher,
|
||||
apery_fixed, mpf_apery, euler_fixed, mpf_euler, mertens_fixed,
|
||||
mpf_mertens, twinprime_fixed, mpf_twinprime,
|
||||
mpf_bernoulli, bernfrac, mpf_gamma_int,
|
||||
mpf_factorial, mpc_factorial, mpf_gamma, mpc_gamma,
|
||||
mpf_loggamma, mpc_loggamma, mpf_rgamma, mpc_rgamma,
|
||||
mpf_harmonic, mpc_harmonic, mpf_psi0, mpc_psi0,
|
||||
mpf_psi, mpc_psi, mpf_zeta_int, mpf_zeta, mpc_zeta,
|
||||
mpf_altzeta, mpc_altzeta, mpf_zetasum, mpc_zetasum)
|
||||
|
||||
from .libmpi import (mpi_str,
|
||||
mpi_from_str, mpi_to_str,
|
||||
mpi_eq, mpi_ne,
|
||||
mpi_lt, mpi_le, mpi_gt, mpi_ge,
|
||||
mpi_add, mpi_sub, mpi_delta, mpi_mid,
|
||||
mpi_pos, mpi_neg, mpi_abs, mpi_mul, mpi_div, mpi_exp,
|
||||
mpi_log, mpi_sqrt, mpi_pow_int, mpi_pow, mpi_cos_sin,
|
||||
mpi_cos, mpi_sin, mpi_tan, mpi_cot,
|
||||
mpi_atan, mpi_atan2,
|
||||
mpci_pos, mpci_neg, mpci_add, mpci_sub, mpci_mul, mpci_div, mpci_pow,
|
||||
mpci_abs, mpci_pow, mpci_exp, mpci_log, mpci_cos, mpci_sin,
|
||||
mpi_gamma, mpci_gamma, mpi_loggamma, mpci_loggamma,
|
||||
mpi_rgamma, mpci_rgamma, mpi_factorial, mpci_factorial)
|
||||
|
||||
from .libintmath import (trailing, bitcount, numeral, bin_to_radix,
|
||||
isqrt, isqrt_small, isqrt_fast, sqrt_fixed, sqrtrem, ifib, ifac,
|
||||
list_primes, isprime, moebius, gcd, eulernum, stirling1, stirling2)
|
||||
|
||||
from .backend import (gmpy, sage, BACKEND, STRICT, MPZ, MPZ_TYPE,
|
||||
MPZ_ZERO, MPZ_ONE, MPZ_TWO, MPZ_THREE, MPZ_FIVE, int_types,
|
||||
HASH_MODULUS, HASH_BITS)
|
||||
@@ -0,0 +1,115 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
#----------------------------------------------------------------------------#
|
||||
# Support GMPY for high-speed large integer arithmetic. #
|
||||
# #
|
||||
# To allow an external module to handle arithmetic, we need to make sure #
|
||||
# that all high-precision variables are declared of the correct type. MPZ #
|
||||
# is the constructor for the high-precision type. It defaults to Python's #
|
||||
# long type but can be assinged another type, typically gmpy.mpz. #
|
||||
# #
|
||||
# MPZ must be used for the mantissa component of an mpf and must be used #
|
||||
# for internal fixed-point operations. #
|
||||
# #
|
||||
# Side-effects #
|
||||
# 1) "is" cannot be used to test for special values. Must use "==". #
|
||||
# 2) There are bugs in GMPY prior to v1.02 so we must use v1.03 or later. #
|
||||
#----------------------------------------------------------------------------#
|
||||
|
||||
# So we can import it from this module
|
||||
gmpy = None
|
||||
sage = None
|
||||
sage_utils = None
|
||||
|
||||
if sys.version_info[0] < 3:
|
||||
python3 = False
|
||||
else:
|
||||
python3 = True
|
||||
|
||||
BACKEND = 'python'
|
||||
|
||||
if not python3:
|
||||
MPZ = long
|
||||
xrange = xrange
|
||||
basestring = basestring
|
||||
|
||||
def exec_(_code_, _globs_=None, _locs_=None):
|
||||
"""Execute code in a namespace."""
|
||||
if _globs_ is None:
|
||||
frame = sys._getframe(1)
|
||||
_globs_ = frame.f_globals
|
||||
if _locs_ is None:
|
||||
_locs_ = frame.f_locals
|
||||
del frame
|
||||
elif _locs_ is None:
|
||||
_locs_ = _globs_
|
||||
exec("""exec _code_ in _globs_, _locs_""")
|
||||
else:
|
||||
MPZ = int
|
||||
xrange = range
|
||||
basestring = str
|
||||
|
||||
import builtins
|
||||
exec_ = getattr(builtins, "exec")
|
||||
|
||||
# Define constants for calculating hash on Python 3.2.
|
||||
if sys.version_info >= (3, 2):
|
||||
HASH_MODULUS = sys.hash_info.modulus
|
||||
if sys.hash_info.width == 32:
|
||||
HASH_BITS = 31
|
||||
else:
|
||||
HASH_BITS = 61
|
||||
else:
|
||||
HASH_MODULUS = None
|
||||
HASH_BITS = None
|
||||
|
||||
if 'MPMATH_NOGMPY' not in os.environ:
|
||||
try:
|
||||
try:
|
||||
import gmpy2 as gmpy
|
||||
except ImportError:
|
||||
try:
|
||||
import gmpy
|
||||
except ImportError:
|
||||
raise ImportError
|
||||
if gmpy.version() >= '1.03':
|
||||
BACKEND = 'gmpy'
|
||||
MPZ = gmpy.mpz
|
||||
except:
|
||||
pass
|
||||
|
||||
if ('MPMATH_NOSAGE' not in os.environ and 'SAGE_ROOT' in os.environ or
|
||||
'MPMATH_SAGE' in os.environ):
|
||||
try:
|
||||
import sage.all
|
||||
import sage.libs.mpmath.utils as _sage_utils
|
||||
sage = sage.all
|
||||
sage_utils = _sage_utils
|
||||
BACKEND = 'sage'
|
||||
MPZ = sage.Integer
|
||||
except:
|
||||
pass
|
||||
|
||||
if 'MPMATH_STRICT' in os.environ:
|
||||
STRICT = True
|
||||
else:
|
||||
STRICT = False
|
||||
|
||||
MPZ_TYPE = type(MPZ(0))
|
||||
MPZ_ZERO = MPZ(0)
|
||||
MPZ_ONE = MPZ(1)
|
||||
MPZ_TWO = MPZ(2)
|
||||
MPZ_THREE = MPZ(3)
|
||||
MPZ_FIVE = MPZ(5)
|
||||
|
||||
try:
|
||||
if BACKEND == 'python':
|
||||
int_types = (int, long)
|
||||
else:
|
||||
int_types = (int, long, MPZ_TYPE)
|
||||
except NameError:
|
||||
if BACKEND == 'python':
|
||||
int_types = (int,)
|
||||
else:
|
||||
int_types = (int, MPZ_TYPE)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,584 @@
|
||||
"""
|
||||
Utility functions for integer math.
|
||||
|
||||
TODO: rename, cleanup, perhaps move the gmpy wrapper code
|
||||
here from settings.py
|
||||
|
||||
"""
|
||||
|
||||
import math
|
||||
from bisect import bisect
|
||||
|
||||
from .backend import xrange
|
||||
from .backend import BACKEND, gmpy, sage, sage_utils, MPZ, MPZ_ONE, MPZ_ZERO
|
||||
|
||||
small_trailing = [0] * 256
|
||||
for j in range(1,8):
|
||||
small_trailing[1<<j::1<<(j+1)] = [j] * (1<<(7-j))
|
||||
|
||||
def giant_steps(start, target, n=2):
|
||||
"""
|
||||
Return a list of integers ~=
|
||||
|
||||
[start, n*start, ..., target/n^2, target/n, target]
|
||||
|
||||
but conservatively rounded so that the quotient between two
|
||||
successive elements is actually slightly less than n.
|
||||
|
||||
With n = 2, this describes suitable precision steps for a
|
||||
quadratically convergent algorithm such as Newton's method;
|
||||
with n = 3 steps for cubic convergence (Halley's method), etc.
|
||||
|
||||
>>> giant_steps(50,1000)
|
||||
[66, 128, 253, 502, 1000]
|
||||
>>> giant_steps(50,1000,4)
|
||||
[65, 252, 1000]
|
||||
|
||||
"""
|
||||
L = [target]
|
||||
while L[-1] > start*n:
|
||||
L = L + [L[-1]//n + 2]
|
||||
return L[::-1]
|
||||
|
||||
def rshift(x, n):
|
||||
"""For an integer x, calculate x >> n with the fastest (floor)
|
||||
rounding. Unlike the plain Python expression (x >> n), n is
|
||||
allowed to be negative, in which case a left shift is performed."""
|
||||
if n >= 0: return x >> n
|
||||
else: return x << (-n)
|
||||
|
||||
def lshift(x, n):
|
||||
"""For an integer x, calculate x << n. Unlike the plain Python
|
||||
expression (x << n), n is allowed to be negative, in which case a
|
||||
right shift with default (floor) rounding is performed."""
|
||||
if n >= 0: return x << n
|
||||
else: return x >> (-n)
|
||||
|
||||
if BACKEND == 'sage':
|
||||
import operator
|
||||
rshift = operator.rshift
|
||||
lshift = operator.lshift
|
||||
|
||||
def python_trailing(n):
|
||||
"""Count the number of trailing zero bits in abs(n)."""
|
||||
if not n:
|
||||
return 0
|
||||
low_byte = n & 0xff
|
||||
if low_byte:
|
||||
return small_trailing[low_byte]
|
||||
t = 8
|
||||
n >>= 8
|
||||
while not n & 0xff:
|
||||
n >>= 8
|
||||
t += 8
|
||||
return t + small_trailing[n & 0xff]
|
||||
|
||||
if BACKEND == 'gmpy':
|
||||
if gmpy.version() >= '2':
|
||||
def gmpy_trailing(n):
|
||||
"""Count the number of trailing zero bits in abs(n) using gmpy."""
|
||||
if n: return MPZ(n).bit_scan1()
|
||||
else: return 0
|
||||
else:
|
||||
def gmpy_trailing(n):
|
||||
"""Count the number of trailing zero bits in abs(n) using gmpy."""
|
||||
if n: return MPZ(n).scan1()
|
||||
else: return 0
|
||||
|
||||
# Small powers of 2
|
||||
powers = [1<<_ for _ in range(300)]
|
||||
|
||||
def python_bitcount(n):
|
||||
"""Calculate bit size of the nonnegative integer n."""
|
||||
bc = bisect(powers, n)
|
||||
if bc != 300:
|
||||
return bc
|
||||
bc = int(math.log(n, 2)) - 4
|
||||
return bc + bctable[n>>bc]
|
||||
|
||||
def gmpy_bitcount(n):
|
||||
"""Calculate bit size of the nonnegative integer n."""
|
||||
if n: return MPZ(n).numdigits(2)
|
||||
else: return 0
|
||||
|
||||
#def sage_bitcount(n):
|
||||
# if n: return MPZ(n).nbits()
|
||||
# else: return 0
|
||||
|
||||
def sage_trailing(n):
|
||||
return MPZ(n).trailing_zero_bits()
|
||||
|
||||
if BACKEND == 'gmpy':
|
||||
bitcount = gmpy_bitcount
|
||||
trailing = gmpy_trailing
|
||||
elif BACKEND == 'sage':
|
||||
sage_bitcount = sage_utils.bitcount
|
||||
bitcount = sage_bitcount
|
||||
trailing = sage_trailing
|
||||
else:
|
||||
bitcount = python_bitcount
|
||||
trailing = python_trailing
|
||||
|
||||
if BACKEND == 'gmpy' and 'bit_length' in dir(gmpy):
|
||||
bitcount = gmpy.bit_length
|
||||
|
||||
# Used to avoid slow function calls as far as possible
|
||||
trailtable = [trailing(n) for n in range(256)]
|
||||
bctable = [bitcount(n) for n in range(1024)]
|
||||
|
||||
# TODO: speed up for bases 2, 4, 8, 16, ...
|
||||
|
||||
def bin_to_radix(x, xbits, base, bdigits):
|
||||
"""Changes radix of a fixed-point number; i.e., converts
|
||||
x * 2**xbits to floor(x * 10**bdigits)."""
|
||||
return x * (MPZ(base)**bdigits) >> xbits
|
||||
|
||||
stddigits = '0123456789abcdefghijklmnopqrstuvwxyz'
|
||||
|
||||
def small_numeral(n, base=10, digits=stddigits):
|
||||
"""Return the string numeral of a positive integer in an arbitrary
|
||||
base. Most efficient for small input."""
|
||||
if base == 10:
|
||||
return str(n)
|
||||
digs = []
|
||||
while n:
|
||||
n, digit = divmod(n, base)
|
||||
digs.append(digits[digit])
|
||||
return "".join(digs[::-1])
|
||||
|
||||
def numeral_python(n, base=10, size=0, digits=stddigits):
|
||||
"""Represent the integer n as a string of digits in the given base.
|
||||
Recursive division is used to make this function about 3x faster
|
||||
than Python's str() for converting integers to decimal strings.
|
||||
|
||||
The 'size' parameters specifies the number of digits in n; this
|
||||
number is only used to determine splitting points and need not be
|
||||
exact."""
|
||||
if n <= 0:
|
||||
if not n:
|
||||
return "0"
|
||||
return "-" + numeral(-n, base, size, digits)
|
||||
# Fast enough to do directly
|
||||
if size < 250:
|
||||
return small_numeral(n, base, digits)
|
||||
# Divide in half
|
||||
half = (size // 2) + (size & 1)
|
||||
A, B = divmod(n, base**half)
|
||||
ad = numeral(A, base, half, digits)
|
||||
bd = numeral(B, base, half, digits).rjust(half, "0")
|
||||
return ad + bd
|
||||
|
||||
def numeral_gmpy(n, base=10, size=0, digits=stddigits):
|
||||
"""Represent the integer n as a string of digits in the given base.
|
||||
Recursive division is used to make this function about 3x faster
|
||||
than Python's str() for converting integers to decimal strings.
|
||||
|
||||
The 'size' parameters specifies the number of digits in n; this
|
||||
number is only used to determine splitting points and need not be
|
||||
exact."""
|
||||
if n < 0:
|
||||
return "-" + numeral(-n, base, size, digits)
|
||||
# gmpy.digits() may cause a segmentation fault when trying to convert
|
||||
# extremely large values to a string. The size limit may need to be
|
||||
# adjusted on some platforms, but 1500000 works on Windows and Linux.
|
||||
if size < 1500000:
|
||||
return gmpy.digits(n, base)
|
||||
# Divide in half
|
||||
half = (size // 2) + (size & 1)
|
||||
A, B = divmod(n, MPZ(base)**half)
|
||||
ad = numeral(A, base, half, digits)
|
||||
bd = numeral(B, base, half, digits).rjust(half, "0")
|
||||
return ad + bd
|
||||
|
||||
if BACKEND == "gmpy":
|
||||
numeral = numeral_gmpy
|
||||
else:
|
||||
numeral = numeral_python
|
||||
|
||||
_1_800 = 1<<800
|
||||
_1_600 = 1<<600
|
||||
_1_400 = 1<<400
|
||||
_1_200 = 1<<200
|
||||
_1_100 = 1<<100
|
||||
_1_50 = 1<<50
|
||||
|
||||
def isqrt_small_python(x):
|
||||
"""
|
||||
Correctly (floor) rounded integer square root, using
|
||||
division. Fast up to ~200 digits.
|
||||
"""
|
||||
if not x:
|
||||
return x
|
||||
if x < _1_800:
|
||||
# Exact with IEEE double precision arithmetic
|
||||
if x < _1_50:
|
||||
return int(x**0.5)
|
||||
# Initial estimate can be any integer >= the true root; round up
|
||||
r = int(x**0.5 * 1.00000000000001) + 1
|
||||
else:
|
||||
bc = bitcount(x)
|
||||
n = bc//2
|
||||
r = int((x>>(2*n-100))**0.5+2)<<(n-50) # +2 is to round up
|
||||
# The following iteration now precisely computes floor(sqrt(x))
|
||||
# See e.g. Crandall & Pomerance, "Prime Numbers: A Computational
|
||||
# Perspective"
|
||||
while 1:
|
||||
y = (r+x//r)>>1
|
||||
if y >= r:
|
||||
return r
|
||||
r = y
|
||||
|
||||
def isqrt_fast_python(x):
|
||||
"""
|
||||
Fast approximate integer square root, computed using division-free
|
||||
Newton iteration for large x. For random integers the result is almost
|
||||
always correct (floor(sqrt(x))), but is 1 ulp too small with a roughly
|
||||
0.1% probability. If x is very close to an exact square, the answer is
|
||||
1 ulp wrong with high probability.
|
||||
|
||||
With 0 guard bits, the largest error over a set of 10^5 random
|
||||
inputs of size 1-10^5 bits was 3 ulp. The use of 10 guard bits
|
||||
almost certainly guarantees a max 1 ulp error.
|
||||
"""
|
||||
# Use direct division-based iteration if sqrt(x) < 2^400
|
||||
# Assume floating-point square root accurate to within 1 ulp, then:
|
||||
# 0 Newton iterations good to 52 bits
|
||||
# 1 Newton iterations good to 104 bits
|
||||
# 2 Newton iterations good to 208 bits
|
||||
# 3 Newton iterations good to 416 bits
|
||||
if x < _1_800:
|
||||
y = int(x**0.5)
|
||||
if x >= _1_100:
|
||||
y = (y + x//y) >> 1
|
||||
if x >= _1_200:
|
||||
y = (y + x//y) >> 1
|
||||
if x >= _1_400:
|
||||
y = (y + x//y) >> 1
|
||||
return y
|
||||
bc = bitcount(x)
|
||||
guard_bits = 10
|
||||
x <<= 2*guard_bits
|
||||
bc += 2*guard_bits
|
||||
bc += (bc&1)
|
||||
hbc = bc//2
|
||||
startprec = min(50, hbc)
|
||||
# Newton iteration for 1/sqrt(x), with floating-point starting value
|
||||
r = int(2.0**(2*startprec) * (x >> (bc-2*startprec)) ** -0.5)
|
||||
pp = startprec
|
||||
for p in giant_steps(startprec, hbc):
|
||||
# r**2, scaled from real size 2**(-bc) to 2**p
|
||||
r2 = (r*r) >> (2*pp - p)
|
||||
# x*r**2, scaled from real size ~1.0 to 2**p
|
||||
xr2 = ((x >> (bc-p)) * r2) >> p
|
||||
# New value of r, scaled from real size 2**(-bc/2) to 2**p
|
||||
r = (r * ((3<<p) - xr2)) >> (pp+1)
|
||||
pp = p
|
||||
# (1/sqrt(x))*x = sqrt(x)
|
||||
return (r*(x>>hbc)) >> (p+guard_bits)
|
||||
|
||||
def sqrtrem_python(x):
|
||||
"""Correctly rounded integer (floor) square root with remainder."""
|
||||
# to check cutoff:
|
||||
# plot(lambda x: timing(isqrt, 2**int(x)), [0,2000])
|
||||
if x < _1_600:
|
||||
y = isqrt_small_python(x)
|
||||
return y, x - y*y
|
||||
y = isqrt_fast_python(x) + 1
|
||||
rem = x - y*y
|
||||
# Correct remainder
|
||||
while rem < 0:
|
||||
y -= 1
|
||||
rem += (1+2*y)
|
||||
else:
|
||||
if rem:
|
||||
while rem > 2*(1+y):
|
||||
y += 1
|
||||
rem -= (1+2*y)
|
||||
return y, rem
|
||||
|
||||
def isqrt_python(x):
|
||||
"""Integer square root with correct (floor) rounding."""
|
||||
return sqrtrem_python(x)[0]
|
||||
|
||||
def sqrt_fixed(x, prec):
|
||||
return isqrt_fast(x<<prec)
|
||||
|
||||
sqrt_fixed2 = sqrt_fixed
|
||||
|
||||
if BACKEND == 'gmpy':
|
||||
if gmpy.version() >= '2':
|
||||
isqrt_small = isqrt_fast = isqrt = gmpy.isqrt
|
||||
sqrtrem = gmpy.isqrt_rem
|
||||
else:
|
||||
isqrt_small = isqrt_fast = isqrt = gmpy.sqrt
|
||||
sqrtrem = gmpy.sqrtrem
|
||||
elif BACKEND == 'sage':
|
||||
isqrt_small = isqrt_fast = isqrt = \
|
||||
getattr(sage_utils, "isqrt", lambda n: MPZ(n).isqrt())
|
||||
sqrtrem = lambda n: MPZ(n).sqrtrem()
|
||||
else:
|
||||
isqrt_small = isqrt_small_python
|
||||
isqrt_fast = isqrt_fast_python
|
||||
isqrt = isqrt_python
|
||||
sqrtrem = sqrtrem_python
|
||||
|
||||
|
||||
def ifib(n, _cache={}):
|
||||
"""Computes the nth Fibonacci number as an integer, for
|
||||
integer n."""
|
||||
if n < 0:
|
||||
return (-1)**(-n+1) * ifib(-n)
|
||||
if n in _cache:
|
||||
return _cache[n]
|
||||
m = n
|
||||
# Use Dijkstra's logarithmic algorithm
|
||||
# The following implementation is basically equivalent to
|
||||
# http://en.literateprograms.org/Fibonacci_numbers_(Scheme)
|
||||
a, b, p, q = MPZ_ONE, MPZ_ZERO, MPZ_ZERO, MPZ_ONE
|
||||
while n:
|
||||
if n & 1:
|
||||
aq = a*q
|
||||
a, b = b*q+aq+a*p, b*p+aq
|
||||
n -= 1
|
||||
else:
|
||||
qq = q*q
|
||||
p, q = p*p+qq, qq+2*p*q
|
||||
n >>= 1
|
||||
if m < 250:
|
||||
_cache[m] = b
|
||||
return b
|
||||
|
||||
MAX_FACTORIAL_CACHE = 1000
|
||||
|
||||
def ifac(n, memo={0:1, 1:1}):
|
||||
"""Return n factorial (for integers n >= 0 only)."""
|
||||
f = memo.get(n)
|
||||
if f:
|
||||
return f
|
||||
k = len(memo)
|
||||
p = memo[k-1]
|
||||
MAX = MAX_FACTORIAL_CACHE
|
||||
while k <= n:
|
||||
p *= k
|
||||
if k <= MAX:
|
||||
memo[k] = p
|
||||
k += 1
|
||||
return p
|
||||
|
||||
def ifac2(n, memo_pair=[{0:1}, {1:1}]):
|
||||
"""Return n!! (double factorial), integers n >= 0 only."""
|
||||
memo = memo_pair[n&1]
|
||||
f = memo.get(n)
|
||||
if f:
|
||||
return f
|
||||
k = max(memo)
|
||||
p = memo[k]
|
||||
MAX = MAX_FACTORIAL_CACHE
|
||||
while k < n:
|
||||
k += 2
|
||||
p *= k
|
||||
if k <= MAX:
|
||||
memo[k] = p
|
||||
return p
|
||||
|
||||
if BACKEND == 'gmpy':
|
||||
ifac = gmpy.fac
|
||||
elif BACKEND == 'sage':
|
||||
ifac = lambda n: int(sage.factorial(n))
|
||||
ifib = sage.fibonacci
|
||||
|
||||
def list_primes(n):
|
||||
n = n + 1
|
||||
sieve = list(xrange(n))
|
||||
sieve[:2] = [0, 0]
|
||||
for i in xrange(2, int(n**0.5)+1):
|
||||
if sieve[i]:
|
||||
for j in xrange(i**2, n, i):
|
||||
sieve[j] = 0
|
||||
return [p for p in sieve if p]
|
||||
|
||||
if BACKEND == 'sage':
|
||||
# Note: it is *VERY* important for performance that we convert
|
||||
# the list to Python ints.
|
||||
def list_primes(n):
|
||||
return [int(_) for _ in sage.primes(n+1)]
|
||||
|
||||
small_odd_primes = (3,5,7,11,13,17,19,23,29,31,37,41,43,47)
|
||||
small_odd_primes_set = set(small_odd_primes)
|
||||
|
||||
def isprime(n):
|
||||
"""
|
||||
Determines whether n is a prime number. A probabilistic test is
|
||||
performed if n is very large. No special trick is used for detecting
|
||||
perfect powers.
|
||||
|
||||
>>> sum(list_primes(100000))
|
||||
454396537
|
||||
>>> sum(n*isprime(n) for n in range(100000))
|
||||
454396537
|
||||
|
||||
"""
|
||||
n = int(n)
|
||||
if not n & 1:
|
||||
return n == 2
|
||||
if n < 50:
|
||||
return n in small_odd_primes_set
|
||||
for p in small_odd_primes:
|
||||
if not n % p:
|
||||
return False
|
||||
m = n-1
|
||||
s = trailing(m)
|
||||
d = m >> s
|
||||
def test(a):
|
||||
x = pow(a,d,n)
|
||||
if x == 1 or x == m:
|
||||
return True
|
||||
for r in xrange(1,s):
|
||||
x = x**2 % n
|
||||
if x == m:
|
||||
return True
|
||||
return False
|
||||
# See http://primes.utm.edu/prove/prove2_3.html
|
||||
if n < 1373653:
|
||||
witnesses = [2,3]
|
||||
elif n < 341550071728321:
|
||||
witnesses = [2,3,5,7,11,13,17]
|
||||
else:
|
||||
witnesses = small_odd_primes
|
||||
for a in witnesses:
|
||||
if not test(a):
|
||||
return False
|
||||
return True
|
||||
|
||||
def moebius(n):
|
||||
"""
|
||||
Evaluates the Moebius function which is `mu(n) = (-1)^k` if `n`
|
||||
is a product of `k` distinct primes and `mu(n) = 0` otherwise.
|
||||
|
||||
TODO: speed up using factorization
|
||||
"""
|
||||
n = abs(int(n))
|
||||
if n < 2:
|
||||
return n
|
||||
factors = []
|
||||
for p in xrange(2, n+1):
|
||||
if not (n % p):
|
||||
if not (n % p**2):
|
||||
return 0
|
||||
if not sum(p % f for f in factors):
|
||||
factors.append(p)
|
||||
return (-1)**len(factors)
|
||||
|
||||
def gcd(*args):
|
||||
a = 0
|
||||
for b in args:
|
||||
if a:
|
||||
while b:
|
||||
a, b = b, a % b
|
||||
else:
|
||||
a = b
|
||||
return a
|
||||
|
||||
|
||||
# Comment by Juan Arias de Reyna:
|
||||
#
|
||||
# I learn this method to compute EulerE[2n] from van de Lune.
|
||||
#
|
||||
# We apply the formula EulerE[2n] = (-1)^n 2**(-2n) sum_{j=0}^n a(2n,2j+1)
|
||||
#
|
||||
# where the numbers a(n,j) vanish for j > n+1 or j <= -1 and satisfies
|
||||
#
|
||||
# a(0,-1) = a(0,0) = 0; a(0,1)= 1; a(0,2) = a(0,3) = 0
|
||||
#
|
||||
# a(n,j) = a(n-1,j) when n+j is even
|
||||
# a(n,j) = (j-1) a(n-1,j-1) + (j+1) a(n-1,j+1) when n+j is odd
|
||||
#
|
||||
#
|
||||
# But we can use only one array unidimensional a(j) since to compute
|
||||
# a(n,j) we only need to know a(n-1,k) where k and j are of different parity
|
||||
# and we have not to conserve the used values.
|
||||
#
|
||||
# We cached up the values of Euler numbers to sufficiently high order.
|
||||
#
|
||||
# Important Observation: If we pretend to use the numbers
|
||||
# EulerE[1], EulerE[2], ... , EulerE[n]
|
||||
# it is convenient to compute first EulerE[n], since the algorithm
|
||||
# computes first all
|
||||
# the previous ones, and keeps them in the CACHE
|
||||
|
||||
MAX_EULER_CACHE = 500
|
||||
|
||||
def eulernum(m, _cache={0:MPZ_ONE}):
|
||||
r"""
|
||||
Computes the Euler numbers `E(n)`, which can be defined as
|
||||
coefficients of the Taylor expansion of `1/cosh x`:
|
||||
|
||||
.. math ::
|
||||
|
||||
\frac{1}{\cosh x} = \sum_{n=0}^\infty \frac{E_n}{n!} x^n
|
||||
|
||||
Example::
|
||||
|
||||
>>> [int(eulernum(n)) for n in range(11)]
|
||||
[1, 0, -1, 0, 5, 0, -61, 0, 1385, 0, -50521]
|
||||
>>> [int(eulernum(n)) for n in range(11)] # test cache
|
||||
[1, 0, -1, 0, 5, 0, -61, 0, 1385, 0, -50521]
|
||||
|
||||
"""
|
||||
# for odd m > 1, the Euler numbers are zero
|
||||
if m & 1:
|
||||
return MPZ_ZERO
|
||||
f = _cache.get(m)
|
||||
if f:
|
||||
return f
|
||||
MAX = MAX_EULER_CACHE
|
||||
n = m
|
||||
a = [MPZ(_) for _ in [0,0,1,0,0,0]]
|
||||
for n in range(1, m+1):
|
||||
for j in range(n+1, -1, -2):
|
||||
a[j+1] = (j-1)*a[j] + (j+1)*a[j+2]
|
||||
a.append(0)
|
||||
suma = 0
|
||||
for k in range(n+1, -1, -2):
|
||||
suma += a[k+1]
|
||||
if n <= MAX:
|
||||
_cache[n] = ((-1)**(n//2))*(suma // 2**n)
|
||||
if n == m:
|
||||
return ((-1)**(n//2))*suma // 2**n
|
||||
|
||||
def stirling1(n, k):
|
||||
"""
|
||||
Stirling number of the first kind.
|
||||
"""
|
||||
if n < 0 or k < 0:
|
||||
raise ValueError
|
||||
if k >= n:
|
||||
return MPZ(n == k)
|
||||
if k < 1:
|
||||
return MPZ_ZERO
|
||||
L = [MPZ_ZERO] * (k+1)
|
||||
L[1] = MPZ_ONE
|
||||
for m in xrange(2, n+1):
|
||||
for j in xrange(min(k, m), 0, -1):
|
||||
L[j] = (m-1) * L[j] + L[j-1]
|
||||
return (-1)**(n+k) * L[k]
|
||||
|
||||
def stirling2(n, k):
|
||||
"""
|
||||
Stirling number of the second kind.
|
||||
"""
|
||||
if n < 0 or k < 0:
|
||||
raise ValueError
|
||||
if k >= n:
|
||||
return MPZ(n == k)
|
||||
if k <= 1:
|
||||
return MPZ(k == 1)
|
||||
s = MPZ_ZERO
|
||||
t = MPZ_ONE
|
||||
for j in xrange(k+1):
|
||||
if (k + j) & 1:
|
||||
s -= t * MPZ(j)**n
|
||||
else:
|
||||
s += t * MPZ(j)**n
|
||||
t = t * (k - j) // (j + 1)
|
||||
return s // ifac(k)
|
||||
@@ -0,0 +1,835 @@
|
||||
"""
|
||||
Low-level functions for complex arithmetic.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from .backend import MPZ, MPZ_ZERO, MPZ_ONE, MPZ_TWO, BACKEND
|
||||
|
||||
from .libmpf import (\
|
||||
round_floor, round_ceiling, round_down, round_up,
|
||||
round_nearest, round_fast, bitcount,
|
||||
bctable, normalize, normalize1, reciprocal_rnd, rshift, lshift, giant_steps,
|
||||
negative_rnd,
|
||||
to_str, to_fixed, from_man_exp, from_float, to_float, from_int, to_int,
|
||||
fzero, fone, ftwo, fhalf, finf, fninf, fnan, fnone,
|
||||
mpf_abs, mpf_pos, mpf_neg, mpf_add, mpf_sub, mpf_mul,
|
||||
mpf_div, mpf_mul_int, mpf_shift, mpf_sqrt, mpf_hypot,
|
||||
mpf_rdiv_int, mpf_floor, mpf_ceil, mpf_nint, mpf_frac,
|
||||
mpf_sign, mpf_hash,
|
||||
ComplexResult
|
||||
)
|
||||
|
||||
from .libelefun import (\
|
||||
mpf_pi, mpf_exp, mpf_log, mpf_cos_sin, mpf_cosh_sinh, mpf_tan, mpf_pow_int,
|
||||
mpf_log_hypot,
|
||||
mpf_cos_sin_pi, mpf_phi,
|
||||
mpf_cos, mpf_sin, mpf_cos_pi, mpf_sin_pi,
|
||||
mpf_atan, mpf_atan2, mpf_cosh, mpf_sinh, mpf_tanh,
|
||||
mpf_asin, mpf_acos, mpf_acosh, mpf_nthroot, mpf_fibonacci
|
||||
)
|
||||
|
||||
# An mpc value is a (real, imag) tuple
|
||||
mpc_one = fone, fzero
|
||||
mpc_zero = fzero, fzero
|
||||
mpc_two = ftwo, fzero
|
||||
mpc_half = (fhalf, fzero)
|
||||
|
||||
_infs = (finf, fninf)
|
||||
_infs_nan = (finf, fninf, fnan)
|
||||
|
||||
def mpc_is_inf(z):
|
||||
"""Check if either real or imaginary part is infinite"""
|
||||
re, im = z
|
||||
if re in _infs: return True
|
||||
if im in _infs: return True
|
||||
return False
|
||||
|
||||
def mpc_is_infnan(z):
|
||||
"""Check if either real or imaginary part is infinite or nan"""
|
||||
re, im = z
|
||||
if re in _infs_nan: return True
|
||||
if im in _infs_nan: return True
|
||||
return False
|
||||
|
||||
def mpc_to_str(z, dps, **kwargs):
|
||||
re, im = z
|
||||
rs = to_str(re, dps)
|
||||
if im[0]:
|
||||
return rs + " - " + to_str(mpf_neg(im), dps, **kwargs) + "j"
|
||||
else:
|
||||
return rs + " + " + to_str(im, dps, **kwargs) + "j"
|
||||
|
||||
def mpc_to_complex(z, strict=False, rnd=round_fast):
|
||||
re, im = z
|
||||
return complex(to_float(re, strict, rnd), to_float(im, strict, rnd))
|
||||
|
||||
def mpc_hash(z):
|
||||
if sys.version_info >= (3, 2):
|
||||
re, im = z
|
||||
h = mpf_hash(re) + sys.hash_info.imag * mpf_hash(im)
|
||||
# Need to reduce either module 2^32 or 2^64
|
||||
h = h % (2**sys.hash_info.width)
|
||||
return int(h)
|
||||
else:
|
||||
try:
|
||||
return hash(mpc_to_complex(z, strict=True))
|
||||
except OverflowError:
|
||||
return hash(z)
|
||||
|
||||
def mpc_conjugate(z, prec, rnd=round_fast):
|
||||
re, im = z
|
||||
return re, mpf_neg(im, prec, rnd)
|
||||
|
||||
def mpc_is_nonzero(z):
|
||||
return z != mpc_zero
|
||||
|
||||
def mpc_add(z, w, prec, rnd=round_fast):
|
||||
a, b = z
|
||||
c, d = w
|
||||
return mpf_add(a, c, prec, rnd), mpf_add(b, d, prec, rnd)
|
||||
|
||||
def mpc_add_mpf(z, x, prec, rnd=round_fast):
|
||||
a, b = z
|
||||
return mpf_add(a, x, prec, rnd), b
|
||||
|
||||
def mpc_sub(z, w, prec=0, rnd=round_fast):
|
||||
a, b = z
|
||||
c, d = w
|
||||
return mpf_sub(a, c, prec, rnd), mpf_sub(b, d, prec, rnd)
|
||||
|
||||
def mpc_sub_mpf(z, p, prec=0, rnd=round_fast):
|
||||
a, b = z
|
||||
return mpf_sub(a, p, prec, rnd), b
|
||||
|
||||
def mpc_pos(z, prec, rnd=round_fast):
|
||||
a, b = z
|
||||
return mpf_pos(a, prec, rnd), mpf_pos(b, prec, rnd)
|
||||
|
||||
def mpc_neg(z, prec=None, rnd=round_fast):
|
||||
a, b = z
|
||||
return mpf_neg(a, prec, rnd), mpf_neg(b, prec, rnd)
|
||||
|
||||
def mpc_shift(z, n):
|
||||
a, b = z
|
||||
return mpf_shift(a, n), mpf_shift(b, n)
|
||||
|
||||
def mpc_abs(z, prec, rnd=round_fast):
|
||||
"""Absolute value of a complex number, |a+bi|.
|
||||
Returns an mpf value."""
|
||||
a, b = z
|
||||
return mpf_hypot(a, b, prec, rnd)
|
||||
|
||||
def mpc_arg(z, prec, rnd=round_fast):
|
||||
"""Argument of a complex number. Returns an mpf value."""
|
||||
a, b = z
|
||||
return mpf_atan2(b, a, prec, rnd)
|
||||
|
||||
def mpc_floor(z, prec, rnd=round_fast):
|
||||
a, b = z
|
||||
return mpf_floor(a, prec, rnd), mpf_floor(b, prec, rnd)
|
||||
|
||||
def mpc_ceil(z, prec, rnd=round_fast):
|
||||
a, b = z
|
||||
return mpf_ceil(a, prec, rnd), mpf_ceil(b, prec, rnd)
|
||||
|
||||
def mpc_nint(z, prec, rnd=round_fast):
|
||||
a, b = z
|
||||
return mpf_nint(a, prec, rnd), mpf_nint(b, prec, rnd)
|
||||
|
||||
def mpc_frac(z, prec, rnd=round_fast):
|
||||
a, b = z
|
||||
return mpf_frac(a, prec, rnd), mpf_frac(b, prec, rnd)
|
||||
|
||||
|
||||
def mpc_mul(z, w, prec, rnd=round_fast):
|
||||
"""
|
||||
Complex multiplication.
|
||||
|
||||
Returns the real and imaginary part of (a+bi)*(c+di), rounded to
|
||||
the specified precision. The rounding mode applies to the real and
|
||||
imaginary parts separately.
|
||||
"""
|
||||
a, b = z
|
||||
c, d = w
|
||||
p = mpf_mul(a, c)
|
||||
q = mpf_mul(b, d)
|
||||
r = mpf_mul(a, d)
|
||||
s = mpf_mul(b, c)
|
||||
re = mpf_sub(p, q, prec, rnd)
|
||||
im = mpf_add(r, s, prec, rnd)
|
||||
return re, im
|
||||
|
||||
def mpc_square(z, prec, rnd=round_fast):
|
||||
# (a+b*I)**2 == a**2 - b**2 + 2*I*a*b
|
||||
a, b = z
|
||||
p = mpf_mul(a,a)
|
||||
q = mpf_mul(b,b)
|
||||
r = mpf_mul(a,b, prec, rnd)
|
||||
re = mpf_sub(p, q, prec, rnd)
|
||||
im = mpf_shift(r, 1)
|
||||
return re, im
|
||||
|
||||
def mpc_mul_mpf(z, p, prec, rnd=round_fast):
|
||||
a, b = z
|
||||
re = mpf_mul(a, p, prec, rnd)
|
||||
im = mpf_mul(b, p, prec, rnd)
|
||||
return re, im
|
||||
|
||||
def mpc_mul_imag_mpf(z, x, prec, rnd=round_fast):
|
||||
"""
|
||||
Multiply the mpc value z by I*x where x is an mpf value.
|
||||
"""
|
||||
a, b = z
|
||||
re = mpf_neg(mpf_mul(b, x, prec, rnd))
|
||||
im = mpf_mul(a, x, prec, rnd)
|
||||
return re, im
|
||||
|
||||
def mpc_mul_int(z, n, prec, rnd=round_fast):
|
||||
a, b = z
|
||||
re = mpf_mul_int(a, n, prec, rnd)
|
||||
im = mpf_mul_int(b, n, prec, rnd)
|
||||
return re, im
|
||||
|
||||
def mpc_div(z, w, prec, rnd=round_fast):
|
||||
a, b = z
|
||||
c, d = w
|
||||
wp = prec + 10
|
||||
# mag = c*c + d*d
|
||||
mag = mpf_add(mpf_mul(c, c), mpf_mul(d, d), wp)
|
||||
# (a*c+b*d)/mag, (b*c-a*d)/mag
|
||||
t = mpf_add(mpf_mul(a,c), mpf_mul(b,d), wp)
|
||||
u = mpf_sub(mpf_mul(b,c), mpf_mul(a,d), wp)
|
||||
return mpf_div(t,mag,prec,rnd), mpf_div(u,mag,prec,rnd)
|
||||
|
||||
def mpc_div_mpf(z, p, prec, rnd=round_fast):
|
||||
"""Calculate z/p where p is real"""
|
||||
a, b = z
|
||||
re = mpf_div(a, p, prec, rnd)
|
||||
im = mpf_div(b, p, prec, rnd)
|
||||
return re, im
|
||||
|
||||
def mpc_reciprocal(z, prec, rnd=round_fast):
|
||||
"""Calculate 1/z efficiently"""
|
||||
a, b = z
|
||||
m = mpf_add(mpf_mul(a,a),mpf_mul(b,b),prec+10)
|
||||
re = mpf_div(a, m, prec, rnd)
|
||||
im = mpf_neg(mpf_div(b, m, prec, rnd))
|
||||
return re, im
|
||||
|
||||
def mpc_mpf_div(p, z, prec, rnd=round_fast):
|
||||
"""Calculate p/z where p is real efficiently"""
|
||||
a, b = z
|
||||
m = mpf_add(mpf_mul(a,a),mpf_mul(b,b), prec+10)
|
||||
re = mpf_div(mpf_mul(a,p), m, prec, rnd)
|
||||
im = mpf_div(mpf_neg(mpf_mul(b,p)), m, prec, rnd)
|
||||
return re, im
|
||||
|
||||
def complex_int_pow(a, b, n):
|
||||
"""Complex integer power: computes (a+b*I)**n exactly for
|
||||
nonnegative n (a and b must be Python ints)."""
|
||||
wre = 1
|
||||
wim = 0
|
||||
while n:
|
||||
if n & 1:
|
||||
wre, wim = wre*a - wim*b, wim*a + wre*b
|
||||
n -= 1
|
||||
a, b = a*a - b*b, 2*a*b
|
||||
n //= 2
|
||||
return wre, wim
|
||||
|
||||
def mpc_pow(z, w, prec, rnd=round_fast):
|
||||
if w[1] == fzero:
|
||||
return mpc_pow_mpf(z, w[0], prec, rnd)
|
||||
return mpc_exp(mpc_mul(mpc_log(z, prec+10), w, prec+10), prec, rnd)
|
||||
|
||||
def mpc_pow_mpf(z, p, prec, rnd=round_fast):
|
||||
psign, pman, pexp, pbc = p
|
||||
if pexp >= 0:
|
||||
return mpc_pow_int(z, (-1)**psign * (pman<<pexp), prec, rnd)
|
||||
if pexp == -1:
|
||||
sqrtz = mpc_sqrt(z, prec+10)
|
||||
return mpc_pow_int(sqrtz, (-1)**psign * pman, prec, rnd)
|
||||
return mpc_exp(mpc_mul_mpf(mpc_log(z, prec+10), p, prec+10), prec, rnd)
|
||||
|
||||
def mpc_pow_int(z, n, prec, rnd=round_fast):
|
||||
a, b = z
|
||||
if b == fzero:
|
||||
return mpf_pow_int(a, n, prec, rnd), fzero
|
||||
if a == fzero:
|
||||
v = mpf_pow_int(b, n, prec, rnd)
|
||||
n %= 4
|
||||
if n == 0:
|
||||
return v, fzero
|
||||
elif n == 1:
|
||||
return fzero, v
|
||||
elif n == 2:
|
||||
return mpf_neg(v), fzero
|
||||
elif n == 3:
|
||||
return fzero, mpf_neg(v)
|
||||
if n == 0: return mpc_one
|
||||
if n == 1: return mpc_pos(z, prec, rnd)
|
||||
if n == 2: return mpc_square(z, prec, rnd)
|
||||
if n == -1: return mpc_reciprocal(z, prec, rnd)
|
||||
if n < 0: return mpc_reciprocal(mpc_pow_int(z, -n, prec+4), prec, rnd)
|
||||
asign, aman, aexp, abc = a
|
||||
bsign, bman, bexp, bbc = b
|
||||
if asign: aman = -aman
|
||||
if bsign: bman = -bman
|
||||
de = aexp - bexp
|
||||
abs_de = abs(de)
|
||||
exact_size = n*(abs_de + max(abc, bbc))
|
||||
if exact_size < 10000:
|
||||
if de > 0:
|
||||
aman <<= de
|
||||
aexp = bexp
|
||||
else:
|
||||
bman <<= (-de)
|
||||
bexp = aexp
|
||||
re, im = complex_int_pow(aman, bman, n)
|
||||
re = from_man_exp(re, int(n*aexp), prec, rnd)
|
||||
im = from_man_exp(im, int(n*bexp), prec, rnd)
|
||||
return re, im
|
||||
return mpc_exp(mpc_mul_int(mpc_log(z, prec+10), n, prec+10), prec, rnd)
|
||||
|
||||
def mpc_sqrt(z, prec, rnd=round_fast):
|
||||
"""Complex square root (principal branch).
|
||||
|
||||
We have sqrt(a+bi) = sqrt((r+a)/2) + b/sqrt(2*(r+a))*i where
|
||||
r = abs(a+bi), when a+bi is not a negative real number."""
|
||||
a, b = z
|
||||
if b == fzero:
|
||||
if a == fzero:
|
||||
return (a, b)
|
||||
# When a+bi is a negative real number, we get a real sqrt times i
|
||||
if a[0]:
|
||||
im = mpf_sqrt(mpf_neg(a), prec, rnd)
|
||||
return (fzero, im)
|
||||
else:
|
||||
re = mpf_sqrt(a, prec, rnd)
|
||||
return (re, fzero)
|
||||
wp = prec+20
|
||||
if not a[0]: # case a positive
|
||||
t = mpf_add(mpc_abs((a, b), wp), a, wp) # t = abs(a+bi) + a
|
||||
u = mpf_shift(t, -1) # u = t/2
|
||||
re = mpf_sqrt(u, prec, rnd) # re = sqrt(u)
|
||||
v = mpf_shift(t, 1) # v = 2*t
|
||||
w = mpf_sqrt(v, wp) # w = sqrt(v)
|
||||
im = mpf_div(b, w, prec, rnd) # im = b / w
|
||||
else: # case a negative
|
||||
t = mpf_sub(mpc_abs((a, b), wp), a, wp) # t = abs(a+bi) - a
|
||||
u = mpf_shift(t, -1) # u = t/2
|
||||
im = mpf_sqrt(u, prec, rnd) # im = sqrt(u)
|
||||
v = mpf_shift(t, 1) # v = 2*t
|
||||
w = mpf_sqrt(v, wp) # w = sqrt(v)
|
||||
re = mpf_div(b, w, prec, rnd) # re = b/w
|
||||
if b[0]:
|
||||
re = mpf_neg(re)
|
||||
im = mpf_neg(im)
|
||||
return re, im
|
||||
|
||||
def mpc_nthroot_fixed(a, b, n, prec):
|
||||
# a, b signed integers at fixed precision prec
|
||||
start = 50
|
||||
a1 = int(rshift(a, prec - n*start))
|
||||
b1 = int(rshift(b, prec - n*start))
|
||||
try:
|
||||
r = (a1 + 1j * b1)**(1.0/n)
|
||||
re = r.real
|
||||
im = r.imag
|
||||
re = MPZ(int(re))
|
||||
im = MPZ(int(im))
|
||||
except OverflowError:
|
||||
a1 = from_int(a1, start)
|
||||
b1 = from_int(b1, start)
|
||||
fn = from_int(n)
|
||||
nth = mpf_rdiv_int(1, fn, start)
|
||||
re, im = mpc_pow((a1, b1), (nth, fzero), start)
|
||||
re = to_int(re)
|
||||
im = to_int(im)
|
||||
extra = 10
|
||||
prevp = start
|
||||
extra1 = n
|
||||
for p in giant_steps(start, prec+extra):
|
||||
# this is slow for large n, unlike int_pow_fixed
|
||||
re2, im2 = complex_int_pow(re, im, n-1)
|
||||
re2 = rshift(re2, (n-1)*prevp - p - extra1)
|
||||
im2 = rshift(im2, (n-1)*prevp - p - extra1)
|
||||
r4 = (re2*re2 + im2*im2) >> (p + extra1)
|
||||
ap = rshift(a, prec - p)
|
||||
bp = rshift(b, prec - p)
|
||||
rec = (ap * re2 + bp * im2) >> p
|
||||
imc = (-ap * im2 + bp * re2) >> p
|
||||
reb = (rec << p) // r4
|
||||
imb = (imc << p) // r4
|
||||
re = (reb + (n-1)*lshift(re, p-prevp))//n
|
||||
im = (imb + (n-1)*lshift(im, p-prevp))//n
|
||||
prevp = p
|
||||
return re, im
|
||||
|
||||
def mpc_nthroot(z, n, prec, rnd=round_fast):
|
||||
"""
|
||||
Complex n-th root.
|
||||
|
||||
Use Newton method as in the real case when it is faster,
|
||||
otherwise use z**(1/n)
|
||||
"""
|
||||
a, b = z
|
||||
if a[0] == 0 and b == fzero:
|
||||
re = mpf_nthroot(a, n, prec, rnd)
|
||||
return (re, fzero)
|
||||
if n < 2:
|
||||
if n == 0:
|
||||
return mpc_one
|
||||
if n == 1:
|
||||
return mpc_pos((a, b), prec, rnd)
|
||||
if n == -1:
|
||||
return mpc_div(mpc_one, (a, b), prec, rnd)
|
||||
inverse = mpc_nthroot((a, b), -n, prec+5, reciprocal_rnd[rnd])
|
||||
return mpc_div(mpc_one, inverse, prec, rnd)
|
||||
if n <= 20:
|
||||
prec2 = int(1.2 * (prec + 10))
|
||||
asign, aman, aexp, abc = a
|
||||
bsign, bman, bexp, bbc = b
|
||||
pf = mpc_abs((a,b), prec)
|
||||
if pf[-2] + pf[-1] > -10 and pf[-2] + pf[-1] < prec:
|
||||
af = to_fixed(a, prec2)
|
||||
bf = to_fixed(b, prec2)
|
||||
re, im = mpc_nthroot_fixed(af, bf, n, prec2)
|
||||
extra = 10
|
||||
re = from_man_exp(re, -prec2-extra, prec2, rnd)
|
||||
im = from_man_exp(im, -prec2-extra, prec2, rnd)
|
||||
return re, im
|
||||
fn = from_int(n)
|
||||
prec2 = prec+10 + 10
|
||||
nth = mpf_rdiv_int(1, fn, prec2)
|
||||
re, im = mpc_pow((a, b), (nth, fzero), prec2, rnd)
|
||||
re = normalize(re[0], re[1], re[2], re[3], prec, rnd)
|
||||
im = normalize(im[0], im[1], im[2], im[3], prec, rnd)
|
||||
return re, im
|
||||
|
||||
def mpc_cbrt(z, prec, rnd=round_fast):
|
||||
"""
|
||||
Complex cubic root.
|
||||
"""
|
||||
return mpc_nthroot(z, 3, prec, rnd)
|
||||
|
||||
def mpc_exp(z, prec, rnd=round_fast):
|
||||
"""
|
||||
Complex exponential function.
|
||||
|
||||
We use the direct formula exp(a+bi) = exp(a) * (cos(b) + sin(b)*i)
|
||||
for the computation. This formula is very nice because it is
|
||||
pefectly stable; since we just do real multiplications, the only
|
||||
numerical errors that can creep in are single-ulp rounding errors.
|
||||
|
||||
The formula is efficient since mpmath's real exp is quite fast and
|
||||
since we can compute cos and sin simultaneously.
|
||||
|
||||
It is no problem if a and b are large; if the implementations of
|
||||
exp/cos/sin are accurate and efficient for all real numbers, then
|
||||
so is this function for all complex numbers.
|
||||
"""
|
||||
a, b = z
|
||||
if a == fzero:
|
||||
return mpf_cos_sin(b, prec, rnd)
|
||||
if b == fzero:
|
||||
return mpf_exp(a, prec, rnd), fzero
|
||||
mag = mpf_exp(a, prec+4, rnd)
|
||||
c, s = mpf_cos_sin(b, prec+4, rnd)
|
||||
re = mpf_mul(mag, c, prec, rnd)
|
||||
im = mpf_mul(mag, s, prec, rnd)
|
||||
return re, im
|
||||
|
||||
def mpc_log(z, prec, rnd=round_fast):
|
||||
re = mpf_log_hypot(z[0], z[1], prec, rnd)
|
||||
im = mpc_arg(z, prec, rnd)
|
||||
return re, im
|
||||
|
||||
def mpc_cos(z, prec, rnd=round_fast):
|
||||
"""Complex cosine. The formula used is cos(a+bi) = cos(a)*cosh(b) -
|
||||
sin(a)*sinh(b)*i.
|
||||
|
||||
The same comments apply as for the complex exp: only real
|
||||
multiplications are pewrormed, so no cancellation errors are
|
||||
possible. The formula is also efficient since we can compute both
|
||||
pairs (cos, sin) and (cosh, sinh) in single stwps."""
|
||||
a, b = z
|
||||
if b == fzero:
|
||||
return mpf_cos(a, prec, rnd), fzero
|
||||
if a == fzero:
|
||||
return mpf_cosh(b, prec, rnd), fzero
|
||||
wp = prec + 6
|
||||
c, s = mpf_cos_sin(a, wp)
|
||||
ch, sh = mpf_cosh_sinh(b, wp)
|
||||
re = mpf_mul(c, ch, prec, rnd)
|
||||
im = mpf_mul(s, sh, prec, rnd)
|
||||
return re, mpf_neg(im)
|
||||
|
||||
def mpc_sin(z, prec, rnd=round_fast):
|
||||
"""Complex sine. We have sin(a+bi) = sin(a)*cosh(b) +
|
||||
cos(a)*sinh(b)*i. See the docstring for mpc_cos for additional
|
||||
comments."""
|
||||
a, b = z
|
||||
if b == fzero:
|
||||
return mpf_sin(a, prec, rnd), fzero
|
||||
if a == fzero:
|
||||
return fzero, mpf_sinh(b, prec, rnd)
|
||||
wp = prec + 6
|
||||
c, s = mpf_cos_sin(a, wp)
|
||||
ch, sh = mpf_cosh_sinh(b, wp)
|
||||
re = mpf_mul(s, ch, prec, rnd)
|
||||
im = mpf_mul(c, sh, prec, rnd)
|
||||
return re, im
|
||||
|
||||
def mpc_tan(z, prec, rnd=round_fast):
|
||||
"""Complex tangent. Computed as tan(a+bi) = sin(2a)/M + sinh(2b)/M*i
|
||||
where M = cos(2a) + cosh(2b)."""
|
||||
a, b = z
|
||||
asign, aman, aexp, abc = a
|
||||
bsign, bman, bexp, bbc = b
|
||||
if b == fzero: return mpf_tan(a, prec, rnd), fzero
|
||||
if a == fzero: return fzero, mpf_tanh(b, prec, rnd)
|
||||
wp = prec + 15
|
||||
a = mpf_shift(a, 1)
|
||||
b = mpf_shift(b, 1)
|
||||
c, s = mpf_cos_sin(a, wp)
|
||||
ch, sh = mpf_cosh_sinh(b, wp)
|
||||
# TODO: handle cancellation when c ~= -1 and ch ~= 1
|
||||
mag = mpf_add(c, ch, wp)
|
||||
re = mpf_div(s, mag, prec, rnd)
|
||||
im = mpf_div(sh, mag, prec, rnd)
|
||||
return re, im
|
||||
|
||||
def mpc_cos_pi(z, prec, rnd=round_fast):
|
||||
a, b = z
|
||||
if b == fzero:
|
||||
return mpf_cos_pi(a, prec, rnd), fzero
|
||||
b = mpf_mul(b, mpf_pi(prec+5), prec+5)
|
||||
if a == fzero:
|
||||
return mpf_cosh(b, prec, rnd), fzero
|
||||
wp = prec + 6
|
||||
c, s = mpf_cos_sin_pi(a, wp)
|
||||
ch, sh = mpf_cosh_sinh(b, wp)
|
||||
re = mpf_mul(c, ch, prec, rnd)
|
||||
im = mpf_mul(s, sh, prec, rnd)
|
||||
return re, mpf_neg(im)
|
||||
|
||||
def mpc_sin_pi(z, prec, rnd=round_fast):
|
||||
a, b = z
|
||||
if b == fzero:
|
||||
return mpf_sin_pi(a, prec, rnd), fzero
|
||||
b = mpf_mul(b, mpf_pi(prec+5), prec+5)
|
||||
if a == fzero:
|
||||
return fzero, mpf_sinh(b, prec, rnd)
|
||||
wp = prec + 6
|
||||
c, s = mpf_cos_sin_pi(a, wp)
|
||||
ch, sh = mpf_cosh_sinh(b, wp)
|
||||
re = mpf_mul(s, ch, prec, rnd)
|
||||
im = mpf_mul(c, sh, prec, rnd)
|
||||
return re, im
|
||||
|
||||
def mpc_cos_sin(z, prec, rnd=round_fast):
|
||||
a, b = z
|
||||
if a == fzero:
|
||||
ch, sh = mpf_cosh_sinh(b, prec, rnd)
|
||||
return (ch, fzero), (fzero, sh)
|
||||
if b == fzero:
|
||||
c, s = mpf_cos_sin(a, prec, rnd)
|
||||
return (c, fzero), (s, fzero)
|
||||
wp = prec + 6
|
||||
c, s = mpf_cos_sin(a, wp)
|
||||
ch, sh = mpf_cosh_sinh(b, wp)
|
||||
cre = mpf_mul(c, ch, prec, rnd)
|
||||
cim = mpf_mul(s, sh, prec, rnd)
|
||||
sre = mpf_mul(s, ch, prec, rnd)
|
||||
sim = mpf_mul(c, sh, prec, rnd)
|
||||
return (cre, mpf_neg(cim)), (sre, sim)
|
||||
|
||||
def mpc_cos_sin_pi(z, prec, rnd=round_fast):
|
||||
a, b = z
|
||||
if b == fzero:
|
||||
c, s = mpf_cos_sin_pi(a, prec, rnd)
|
||||
return (c, fzero), (s, fzero)
|
||||
b = mpf_mul(b, mpf_pi(prec+5), prec+5)
|
||||
if a == fzero:
|
||||
ch, sh = mpf_cosh_sinh(b, prec, rnd)
|
||||
return (ch, fzero), (fzero, sh)
|
||||
wp = prec + 6
|
||||
c, s = mpf_cos_sin_pi(a, wp)
|
||||
ch, sh = mpf_cosh_sinh(b, wp)
|
||||
cre = mpf_mul(c, ch, prec, rnd)
|
||||
cim = mpf_mul(s, sh, prec, rnd)
|
||||
sre = mpf_mul(s, ch, prec, rnd)
|
||||
sim = mpf_mul(c, sh, prec, rnd)
|
||||
return (cre, mpf_neg(cim)), (sre, sim)
|
||||
|
||||
def mpc_cosh(z, prec, rnd=round_fast):
|
||||
"""Complex hyperbolic cosine. Computed as cosh(z) = cos(z*i)."""
|
||||
a, b = z
|
||||
return mpc_cos((b, mpf_neg(a)), prec, rnd)
|
||||
|
||||
def mpc_sinh(z, prec, rnd=round_fast):
|
||||
"""Complex hyperbolic sine. Computed as sinh(z) = -i*sin(z*i)."""
|
||||
a, b = z
|
||||
b, a = mpc_sin((b, a), prec, rnd)
|
||||
return a, b
|
||||
|
||||
def mpc_tanh(z, prec, rnd=round_fast):
|
||||
"""Complex hyperbolic tangent. Computed as tanh(z) = -i*tan(z*i)."""
|
||||
a, b = z
|
||||
b, a = mpc_tan((b, a), prec, rnd)
|
||||
return a, b
|
||||
|
||||
# TODO: avoid loss of accuracy
|
||||
def mpc_atan(z, prec, rnd=round_fast):
|
||||
a, b = z
|
||||
# atan(z) = (I/2)*(log(1-I*z) - log(1+I*z))
|
||||
# x = 1-I*z = 1 + b - I*a
|
||||
# y = 1+I*z = 1 - b + I*a
|
||||
wp = prec + 15
|
||||
x = mpf_add(fone, b, wp), mpf_neg(a)
|
||||
y = mpf_sub(fone, b, wp), a
|
||||
l1 = mpc_log(x, wp)
|
||||
l2 = mpc_log(y, wp)
|
||||
a, b = mpc_sub(l1, l2, prec, rnd)
|
||||
# (I/2) * (a+b*I) = (-b/2 + a/2*I)
|
||||
v = mpf_neg(mpf_shift(b,-1)), mpf_shift(a,-1)
|
||||
# Subtraction at infinity gives correct real part but
|
||||
# wrong imaginary part (should be zero)
|
||||
if v[1] == fnan and mpc_is_inf(z):
|
||||
v = (v[0], fzero)
|
||||
return v
|
||||
|
||||
beta_crossover = from_float(0.6417)
|
||||
alpha_crossover = from_float(1.5)
|
||||
|
||||
def acos_asin(z, prec, rnd, n):
|
||||
""" complex acos for n = 0, asin for n = 1
|
||||
The algorithm is described in
|
||||
T.E. Hull, T.F. Fairgrieve and P.T.P. Tang
|
||||
'Implementing the Complex Arcsine and Arcosine Functions
|
||||
using Exception Handling',
|
||||
ACM Trans. on Math. Software Vol. 23 (1997), p299
|
||||
The complex acos and asin can be defined as
|
||||
acos(z) = acos(beta) - I*sign(a)* log(alpha + sqrt(alpha**2 -1))
|
||||
asin(z) = asin(beta) + I*sign(a)* log(alpha + sqrt(alpha**2 -1))
|
||||
where z = a + I*b
|
||||
alpha = (1/2)*(r + s); beta = (1/2)*(r - s) = a/alpha
|
||||
r = sqrt((a+1)**2 + y**2); s = sqrt((a-1)**2 + y**2)
|
||||
These expressions are rewritten in different ways in different
|
||||
regions, delimited by two crossovers alpha_crossover and beta_crossover,
|
||||
and by abs(a) <= 1, in order to improve the numerical accuracy.
|
||||
"""
|
||||
a, b = z
|
||||
wp = prec + 10
|
||||
# special cases with real argument
|
||||
if b == fzero:
|
||||
am = mpf_sub(fone, mpf_abs(a), wp)
|
||||
# case abs(a) <= 1
|
||||
if not am[0]:
|
||||
if n == 0:
|
||||
return mpf_acos(a, prec, rnd), fzero
|
||||
else:
|
||||
return mpf_asin(a, prec, rnd), fzero
|
||||
# cases abs(a) > 1
|
||||
else:
|
||||
# case a < -1
|
||||
if a[0]:
|
||||
pi = mpf_pi(prec, rnd)
|
||||
c = mpf_acosh(mpf_neg(a), prec, rnd)
|
||||
if n == 0:
|
||||
return pi, mpf_neg(c)
|
||||
else:
|
||||
return mpf_neg(mpf_shift(pi, -1)), c
|
||||
# case a > 1
|
||||
else:
|
||||
c = mpf_acosh(a, prec, rnd)
|
||||
if n == 0:
|
||||
return fzero, c
|
||||
else:
|
||||
pi = mpf_pi(prec, rnd)
|
||||
return mpf_shift(pi, -1), mpf_neg(c)
|
||||
asign = bsign = 0
|
||||
if a[0]:
|
||||
a = mpf_neg(a)
|
||||
asign = 1
|
||||
if b[0]:
|
||||
b = mpf_neg(b)
|
||||
bsign = 1
|
||||
am = mpf_sub(fone, a, wp)
|
||||
ap = mpf_add(fone, a, wp)
|
||||
r = mpf_hypot(ap, b, wp)
|
||||
s = mpf_hypot(am, b, wp)
|
||||
alpha = mpf_shift(mpf_add(r, s, wp), -1)
|
||||
beta = mpf_div(a, alpha, wp)
|
||||
b2 = mpf_mul(b,b, wp)
|
||||
# case beta <= beta_crossover
|
||||
if not mpf_sub(beta_crossover, beta, wp)[0]:
|
||||
if n == 0:
|
||||
re = mpf_acos(beta, wp)
|
||||
else:
|
||||
re = mpf_asin(beta, wp)
|
||||
else:
|
||||
# to compute the real part in this region use the identity
|
||||
# asin(beta) = atan(beta/sqrt(1-beta**2))
|
||||
# beta/sqrt(1-beta**2) = (alpha + a) * (alpha - a)
|
||||
# alpha + a is numerically accurate; alpha - a can have
|
||||
# cancellations leading to numerical inaccuracies, so rewrite
|
||||
# it in differente ways according to the region
|
||||
Ax = mpf_add(alpha, a, wp)
|
||||
# case a <= 1
|
||||
if not am[0]:
|
||||
# c = b*b/(r + (a+1)); d = (s + (1-a))
|
||||
# alpha - a = (1/2)*(c + d)
|
||||
# case n=0: re = atan(sqrt((1/2) * Ax * (c + d))/a)
|
||||
# case n=1: re = atan(a/sqrt((1/2) * Ax * (c + d)))
|
||||
c = mpf_div(b2, mpf_add(r, ap, wp), wp)
|
||||
d = mpf_add(s, am, wp)
|
||||
re = mpf_shift(mpf_mul(Ax, mpf_add(c, d, wp), wp), -1)
|
||||
if n == 0:
|
||||
re = mpf_atan(mpf_div(mpf_sqrt(re, wp), a, wp), wp)
|
||||
else:
|
||||
re = mpf_atan(mpf_div(a, mpf_sqrt(re, wp), wp), wp)
|
||||
else:
|
||||
# c = Ax/(r + (a+1)); d = Ax/(s - (1-a))
|
||||
# alpha - a = (1/2)*(c + d)
|
||||
# case n = 0: re = atan(b*sqrt(c + d)/2/a)
|
||||
# case n = 1: re = atan(a/(b*sqrt(c + d)/2)
|
||||
c = mpf_div(Ax, mpf_add(r, ap, wp), wp)
|
||||
d = mpf_div(Ax, mpf_sub(s, am, wp), wp)
|
||||
re = mpf_shift(mpf_add(c, d, wp), -1)
|
||||
re = mpf_mul(b, mpf_sqrt(re, wp), wp)
|
||||
if n == 0:
|
||||
re = mpf_atan(mpf_div(re, a, wp), wp)
|
||||
else:
|
||||
re = mpf_atan(mpf_div(a, re, wp), wp)
|
||||
# to compute alpha + sqrt(alpha**2 - 1), if alpha <= alpha_crossover
|
||||
# replace it with 1 + Am1 + sqrt(Am1*(alpha+1)))
|
||||
# where Am1 = alpha -1
|
||||
# if alpha <= alpha_crossover:
|
||||
if not mpf_sub(alpha_crossover, alpha, wp)[0]:
|
||||
c1 = mpf_div(b2, mpf_add(r, ap, wp), wp)
|
||||
# case a < 1
|
||||
if mpf_neg(am)[0]:
|
||||
# Am1 = (1/2) * (b*b/(r + (a+1)) + b*b/(s + (1-a))
|
||||
c2 = mpf_add(s, am, wp)
|
||||
c2 = mpf_div(b2, c2, wp)
|
||||
Am1 = mpf_shift(mpf_add(c1, c2, wp), -1)
|
||||
else:
|
||||
# Am1 = (1/2) * (b*b/(r + (a+1)) + (s - (1-a)))
|
||||
c2 = mpf_sub(s, am, wp)
|
||||
Am1 = mpf_shift(mpf_add(c1, c2, wp), -1)
|
||||
# im = log(1 + Am1 + sqrt(Am1*(alpha+1)))
|
||||
im = mpf_mul(Am1, mpf_add(alpha, fone, wp), wp)
|
||||
im = mpf_log(mpf_add(fone, mpf_add(Am1, mpf_sqrt(im, wp), wp), wp), wp)
|
||||
else:
|
||||
# im = log(alpha + sqrt(alpha*alpha - 1))
|
||||
im = mpf_sqrt(mpf_sub(mpf_mul(alpha, alpha, wp), fone, wp), wp)
|
||||
im = mpf_log(mpf_add(alpha, im, wp), wp)
|
||||
if asign:
|
||||
if n == 0:
|
||||
re = mpf_sub(mpf_pi(wp), re, wp)
|
||||
else:
|
||||
re = mpf_neg(re)
|
||||
if not bsign and n == 0:
|
||||
im = mpf_neg(im)
|
||||
if bsign and n == 1:
|
||||
im = mpf_neg(im)
|
||||
re = normalize(re[0], re[1], re[2], re[3], prec, rnd)
|
||||
im = normalize(im[0], im[1], im[2], im[3], prec, rnd)
|
||||
return re, im
|
||||
|
||||
def mpc_acos(z, prec, rnd=round_fast):
|
||||
return acos_asin(z, prec, rnd, 0)
|
||||
|
||||
def mpc_asin(z, prec, rnd=round_fast):
|
||||
return acos_asin(z, prec, rnd, 1)
|
||||
|
||||
def mpc_asinh(z, prec, rnd=round_fast):
|
||||
# asinh(z) = I * asin(-I z)
|
||||
a, b = z
|
||||
a, b = mpc_asin((b, mpf_neg(a)), prec, rnd)
|
||||
return mpf_neg(b), a
|
||||
|
||||
def mpc_acosh(z, prec, rnd=round_fast):
|
||||
# acosh(z) = -I * acos(z) for Im(acos(z)) <= 0
|
||||
# +I * acos(z) otherwise
|
||||
a, b = mpc_acos(z, prec, rnd)
|
||||
if b[0] or b == fzero:
|
||||
return mpf_neg(b), a
|
||||
else:
|
||||
return b, mpf_neg(a)
|
||||
|
||||
def mpc_atanh(z, prec, rnd=round_fast):
|
||||
# atanh(z) = (log(1+z)-log(1-z))/2
|
||||
wp = prec + 15
|
||||
a = mpc_add(z, mpc_one, wp)
|
||||
b = mpc_sub(mpc_one, z, wp)
|
||||
a = mpc_log(a, wp)
|
||||
b = mpc_log(b, wp)
|
||||
v = mpc_shift(mpc_sub(a, b, wp), -1)
|
||||
# Subtraction at infinity gives correct imaginary part but
|
||||
# wrong real part (should be zero)
|
||||
if v[0] == fnan and mpc_is_inf(z):
|
||||
v = (fzero, v[1])
|
||||
return v
|
||||
|
||||
def mpc_fibonacci(z, prec, rnd=round_fast):
|
||||
re, im = z
|
||||
if im == fzero:
|
||||
return (mpf_fibonacci(re, prec, rnd), fzero)
|
||||
size = max(abs(re[2]+re[3]), abs(re[2]+re[3]))
|
||||
wp = prec + size + 20
|
||||
a = mpf_phi(wp)
|
||||
b = mpf_add(mpf_shift(a, 1), fnone, wp)
|
||||
u = mpc_pow((a, fzero), z, wp)
|
||||
v = mpc_cos_pi(z, wp)
|
||||
v = mpc_div(v, u, wp)
|
||||
u = mpc_sub(u, v, wp)
|
||||
u = mpc_div_mpf(u, b, prec, rnd)
|
||||
return u
|
||||
|
||||
def mpf_expj(x, prec, rnd='f'):
|
||||
raise ComplexResult
|
||||
|
||||
def mpc_expj(z, prec, rnd='f'):
|
||||
re, im = z
|
||||
if im == fzero:
|
||||
return mpf_cos_sin(re, prec, rnd)
|
||||
if re == fzero:
|
||||
return mpf_exp(mpf_neg(im), prec, rnd), fzero
|
||||
ey = mpf_exp(mpf_neg(im), prec+10)
|
||||
c, s = mpf_cos_sin(re, prec+10)
|
||||
re = mpf_mul(ey, c, prec, rnd)
|
||||
im = mpf_mul(ey, s, prec, rnd)
|
||||
return re, im
|
||||
|
||||
def mpf_expjpi(x, prec, rnd='f'):
|
||||
raise ComplexResult
|
||||
|
||||
def mpc_expjpi(z, prec, rnd='f'):
|
||||
re, im = z
|
||||
if im == fzero:
|
||||
return mpf_cos_sin_pi(re, prec, rnd)
|
||||
sign, man, exp, bc = im
|
||||
wp = prec+10
|
||||
if man:
|
||||
wp += max(0, exp+bc)
|
||||
im = mpf_neg(mpf_mul(mpf_pi(wp), im, wp))
|
||||
if re == fzero:
|
||||
return mpf_exp(im, prec, rnd), fzero
|
||||
ey = mpf_exp(im, prec+10)
|
||||
c, s = mpf_cos_sin_pi(re, prec+10)
|
||||
re = mpf_mul(ey, c, prec, rnd)
|
||||
im = mpf_mul(ey, s, prec, rnd)
|
||||
return re, im
|
||||
|
||||
|
||||
if BACKEND == 'sage':
|
||||
try:
|
||||
import sage.libs.mpmath.ext_libmp as _lbmp
|
||||
mpc_exp = _lbmp.mpc_exp
|
||||
mpc_sqrt = _lbmp.mpc_sqrt
|
||||
except (ImportError, AttributeError):
|
||||
print("Warning: Sage imports in libmpc failed")
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,935 @@
|
||||
"""
|
||||
Computational functions for interval arithmetic.
|
||||
|
||||
"""
|
||||
|
||||
from .backend import xrange
|
||||
|
||||
from .libmpf import (
|
||||
ComplexResult,
|
||||
round_down, round_up, round_floor, round_ceiling, round_nearest,
|
||||
prec_to_dps, repr_dps, dps_to_prec,
|
||||
bitcount,
|
||||
from_float,
|
||||
fnan, finf, fninf, fzero, fhalf, fone, fnone,
|
||||
mpf_sign, mpf_lt, mpf_le, mpf_gt, mpf_ge, mpf_eq, mpf_cmp,
|
||||
mpf_min_max,
|
||||
mpf_floor, from_int, to_int, to_str, from_str,
|
||||
mpf_abs, mpf_neg, mpf_pos, mpf_add, mpf_sub, mpf_mul, mpf_mul_int,
|
||||
mpf_div, mpf_shift, mpf_pow_int,
|
||||
from_man_exp, MPZ_ONE)
|
||||
|
||||
from .libelefun import (
|
||||
mpf_log, mpf_exp, mpf_sqrt, mpf_atan, mpf_atan2,
|
||||
mpf_pi, mod_pi2, mpf_cos_sin
|
||||
)
|
||||
|
||||
from .gammazeta import mpf_gamma, mpf_rgamma, mpf_loggamma, mpc_loggamma
|
||||
|
||||
def mpi_str(s, prec):
|
||||
sa, sb = s
|
||||
dps = prec_to_dps(prec) + 5
|
||||
return "[%s, %s]" % (to_str(sa, dps), to_str(sb, dps))
|
||||
#dps = prec_to_dps(prec)
|
||||
#m = mpi_mid(s, prec)
|
||||
#d = mpf_shift(mpi_delta(s, 20), -1)
|
||||
#return "%s +/- %s" % (to_str(m, dps), to_str(d, 3))
|
||||
|
||||
mpi_zero = (fzero, fzero)
|
||||
mpi_one = (fone, fone)
|
||||
|
||||
def mpi_eq(s, t):
|
||||
return s == t
|
||||
|
||||
def mpi_ne(s, t):
|
||||
return s != t
|
||||
|
||||
def mpi_lt(s, t):
|
||||
sa, sb = s
|
||||
ta, tb = t
|
||||
if mpf_lt(sb, ta): return True
|
||||
if mpf_ge(sa, tb): return False
|
||||
return None
|
||||
|
||||
def mpi_le(s, t):
|
||||
sa, sb = s
|
||||
ta, tb = t
|
||||
if mpf_le(sb, ta): return True
|
||||
if mpf_gt(sa, tb): return False
|
||||
return None
|
||||
|
||||
def mpi_gt(s, t): return mpi_lt(t, s)
|
||||
def mpi_ge(s, t): return mpi_le(t, s)
|
||||
|
||||
def mpi_add(s, t, prec=0):
|
||||
sa, sb = s
|
||||
ta, tb = t
|
||||
a = mpf_add(sa, ta, prec, round_floor)
|
||||
b = mpf_add(sb, tb, prec, round_ceiling)
|
||||
if a == fnan: a = fninf
|
||||
if b == fnan: b = finf
|
||||
return a, b
|
||||
|
||||
def mpi_sub(s, t, prec=0):
|
||||
sa, sb = s
|
||||
ta, tb = t
|
||||
a = mpf_sub(sa, tb, prec, round_floor)
|
||||
b = mpf_sub(sb, ta, prec, round_ceiling)
|
||||
if a == fnan: a = fninf
|
||||
if b == fnan: b = finf
|
||||
return a, b
|
||||
|
||||
def mpi_delta(s, prec):
|
||||
sa, sb = s
|
||||
return mpf_sub(sb, sa, prec, round_up)
|
||||
|
||||
def mpi_mid(s, prec):
|
||||
sa, sb = s
|
||||
return mpf_shift(mpf_add(sa, sb, prec, round_nearest), -1)
|
||||
|
||||
def mpi_pos(s, prec):
|
||||
sa, sb = s
|
||||
a = mpf_pos(sa, prec, round_floor)
|
||||
b = mpf_pos(sb, prec, round_ceiling)
|
||||
return a, b
|
||||
|
||||
def mpi_neg(s, prec=0):
|
||||
sa, sb = s
|
||||
a = mpf_neg(sb, prec, round_floor)
|
||||
b = mpf_neg(sa, prec, round_ceiling)
|
||||
return a, b
|
||||
|
||||
def mpi_abs(s, prec=0):
|
||||
sa, sb = s
|
||||
sas = mpf_sign(sa)
|
||||
sbs = mpf_sign(sb)
|
||||
# Both points nonnegative?
|
||||
if sas >= 0:
|
||||
a = mpf_pos(sa, prec, round_floor)
|
||||
b = mpf_pos(sb, prec, round_ceiling)
|
||||
# Upper point nonnegative?
|
||||
elif sbs >= 0:
|
||||
a = fzero
|
||||
negsa = mpf_neg(sa)
|
||||
if mpf_lt(negsa, sb):
|
||||
b = mpf_pos(sb, prec, round_ceiling)
|
||||
else:
|
||||
b = mpf_pos(negsa, prec, round_ceiling)
|
||||
# Both negative?
|
||||
else:
|
||||
a = mpf_neg(sb, prec, round_floor)
|
||||
b = mpf_neg(sa, prec, round_ceiling)
|
||||
return a, b
|
||||
|
||||
# TODO: optimize
|
||||
def mpi_mul_mpf(s, t, prec):
|
||||
return mpi_mul(s, (t, t), prec)
|
||||
|
||||
def mpi_div_mpf(s, t, prec):
|
||||
return mpi_div(s, (t, t), prec)
|
||||
|
||||
def mpi_mul(s, t, prec=0):
|
||||
sa, sb = s
|
||||
ta, tb = t
|
||||
sas = mpf_sign(sa)
|
||||
sbs = mpf_sign(sb)
|
||||
tas = mpf_sign(ta)
|
||||
tbs = mpf_sign(tb)
|
||||
if sas == sbs == 0:
|
||||
# Should maybe be undefined
|
||||
if ta == fninf or tb == finf:
|
||||
return fninf, finf
|
||||
return fzero, fzero
|
||||
if tas == tbs == 0:
|
||||
# Should maybe be undefined
|
||||
if sa == fninf or sb == finf:
|
||||
return fninf, finf
|
||||
return fzero, fzero
|
||||
if sas >= 0:
|
||||
# positive * positive
|
||||
if tas >= 0:
|
||||
a = mpf_mul(sa, ta, prec, round_floor)
|
||||
b = mpf_mul(sb, tb, prec, round_ceiling)
|
||||
if a == fnan: a = fzero
|
||||
if b == fnan: b = finf
|
||||
# positive * negative
|
||||
elif tbs <= 0:
|
||||
a = mpf_mul(sb, ta, prec, round_floor)
|
||||
b = mpf_mul(sa, tb, prec, round_ceiling)
|
||||
if a == fnan: a = fninf
|
||||
if b == fnan: b = fzero
|
||||
# positive * both signs
|
||||
else:
|
||||
a = mpf_mul(sb, ta, prec, round_floor)
|
||||
b = mpf_mul(sb, tb, prec, round_ceiling)
|
||||
if a == fnan: a = fninf
|
||||
if b == fnan: b = finf
|
||||
elif sbs <= 0:
|
||||
# negative * positive
|
||||
if tas >= 0:
|
||||
a = mpf_mul(sa, tb, prec, round_floor)
|
||||
b = mpf_mul(sb, ta, prec, round_ceiling)
|
||||
if a == fnan: a = fninf
|
||||
if b == fnan: b = fzero
|
||||
# negative * negative
|
||||
elif tbs <= 0:
|
||||
a = mpf_mul(sb, tb, prec, round_floor)
|
||||
b = mpf_mul(sa, ta, prec, round_ceiling)
|
||||
if a == fnan: a = fzero
|
||||
if b == fnan: b = finf
|
||||
# negative * both signs
|
||||
else:
|
||||
a = mpf_mul(sa, tb, prec, round_floor)
|
||||
b = mpf_mul(sa, ta, prec, round_ceiling)
|
||||
if a == fnan: a = fninf
|
||||
if b == fnan: b = finf
|
||||
else:
|
||||
# General case: perform all cross-multiplications and compare
|
||||
# Since the multiplications can be done exactly, we need only
|
||||
# do 4 (instead of 8: two for each rounding mode)
|
||||
cases = [mpf_mul(sa, ta), mpf_mul(sa, tb), mpf_mul(sb, ta), mpf_mul(sb, tb)]
|
||||
if fnan in cases:
|
||||
a, b = (fninf, finf)
|
||||
else:
|
||||
a, b = mpf_min_max(cases)
|
||||
a = mpf_pos(a, prec, round_floor)
|
||||
b = mpf_pos(b, prec, round_ceiling)
|
||||
return a, b
|
||||
|
||||
def mpi_square(s, prec=0):
|
||||
sa, sb = s
|
||||
if mpf_ge(sa, fzero):
|
||||
a = mpf_mul(sa, sa, prec, round_floor)
|
||||
b = mpf_mul(sb, sb, prec, round_ceiling)
|
||||
elif mpf_le(sb, fzero):
|
||||
a = mpf_mul(sb, sb, prec, round_floor)
|
||||
b = mpf_mul(sa, sa, prec, round_ceiling)
|
||||
else:
|
||||
sa = mpf_neg(sa)
|
||||
sa, sb = mpf_min_max([sa, sb])
|
||||
a = fzero
|
||||
b = mpf_mul(sb, sb, prec, round_ceiling)
|
||||
return a, b
|
||||
|
||||
def mpi_div(s, t, prec):
|
||||
sa, sb = s
|
||||
ta, tb = t
|
||||
sas = mpf_sign(sa)
|
||||
sbs = mpf_sign(sb)
|
||||
tas = mpf_sign(ta)
|
||||
tbs = mpf_sign(tb)
|
||||
# 0 / X
|
||||
if sas == sbs == 0:
|
||||
# 0 / <interval containing 0>
|
||||
if (tas < 0 and tbs > 0) or (tas == 0 or tbs == 0):
|
||||
return fninf, finf
|
||||
return fzero, fzero
|
||||
# Denominator contains both negative and positive numbers;
|
||||
# this should properly be a multi-interval, but the closest
|
||||
# match is the entire (extended) real line
|
||||
if tas < 0 and tbs > 0:
|
||||
return fninf, finf
|
||||
# Assume denominator to be nonnegative
|
||||
if tas < 0:
|
||||
return mpi_div(mpi_neg(s), mpi_neg(t), prec)
|
||||
# Division by zero
|
||||
# XXX: make sure all results make sense
|
||||
if tas == 0:
|
||||
# Numerator contains both signs?
|
||||
if sas < 0 and sbs > 0:
|
||||
return fninf, finf
|
||||
if tas == tbs:
|
||||
return fninf, finf
|
||||
# Numerator positive?
|
||||
if sas >= 0:
|
||||
a = mpf_div(sa, tb, prec, round_floor)
|
||||
b = finf
|
||||
if sbs <= 0:
|
||||
a = fninf
|
||||
b = mpf_div(sb, tb, prec, round_ceiling)
|
||||
# Division with positive denominator
|
||||
# We still have to handle nans resulting from inf/0 or inf/inf
|
||||
else:
|
||||
# Nonnegative numerator
|
||||
if sas >= 0:
|
||||
a = mpf_div(sa, tb, prec, round_floor)
|
||||
b = mpf_div(sb, ta, prec, round_ceiling)
|
||||
if a == fnan: a = fzero
|
||||
if b == fnan: b = finf
|
||||
# Nonpositive numerator
|
||||
elif sbs <= 0:
|
||||
a = mpf_div(sa, ta, prec, round_floor)
|
||||
b = mpf_div(sb, tb, prec, round_ceiling)
|
||||
if a == fnan: a = fninf
|
||||
if b == fnan: b = fzero
|
||||
# Numerator contains both signs?
|
||||
else:
|
||||
a = mpf_div(sa, ta, prec, round_floor)
|
||||
b = mpf_div(sb, ta, prec, round_ceiling)
|
||||
if a == fnan: a = fninf
|
||||
if b == fnan: b = finf
|
||||
return a, b
|
||||
|
||||
def mpi_pi(prec):
|
||||
a = mpf_pi(prec, round_floor)
|
||||
b = mpf_pi(prec, round_ceiling)
|
||||
return a, b
|
||||
|
||||
def mpi_exp(s, prec):
|
||||
sa, sb = s
|
||||
# exp is monotonic
|
||||
a = mpf_exp(sa, prec, round_floor)
|
||||
b = mpf_exp(sb, prec, round_ceiling)
|
||||
return a, b
|
||||
|
||||
def mpi_log(s, prec):
|
||||
sa, sb = s
|
||||
# log is monotonic
|
||||
a = mpf_log(sa, prec, round_floor)
|
||||
b = mpf_log(sb, prec, round_ceiling)
|
||||
return a, b
|
||||
|
||||
def mpi_sqrt(s, prec):
|
||||
sa, sb = s
|
||||
# sqrt is monotonic
|
||||
a = mpf_sqrt(sa, prec, round_floor)
|
||||
b = mpf_sqrt(sb, prec, round_ceiling)
|
||||
return a, b
|
||||
|
||||
def mpi_atan(s, prec):
|
||||
sa, sb = s
|
||||
a = mpf_atan(sa, prec, round_floor)
|
||||
b = mpf_atan(sb, prec, round_ceiling)
|
||||
return a, b
|
||||
|
||||
def mpi_pow_int(s, n, prec):
|
||||
sa, sb = s
|
||||
if n < 0:
|
||||
return mpi_div((fone, fone), mpi_pow_int(s, -n, prec+20), prec)
|
||||
if n == 0:
|
||||
return (fone, fone)
|
||||
if n == 1:
|
||||
return s
|
||||
if n == 2:
|
||||
return mpi_square(s, prec)
|
||||
# Odd -- signs are preserved
|
||||
if n & 1:
|
||||
a = mpf_pow_int(sa, n, prec, round_floor)
|
||||
b = mpf_pow_int(sb, n, prec, round_ceiling)
|
||||
# Even -- important to ensure positivity
|
||||
else:
|
||||
sas = mpf_sign(sa)
|
||||
sbs = mpf_sign(sb)
|
||||
# Nonnegative?
|
||||
if sas >= 0:
|
||||
a = mpf_pow_int(sa, n, prec, round_floor)
|
||||
b = mpf_pow_int(sb, n, prec, round_ceiling)
|
||||
# Nonpositive?
|
||||
elif sbs <= 0:
|
||||
a = mpf_pow_int(sb, n, prec, round_floor)
|
||||
b = mpf_pow_int(sa, n, prec, round_ceiling)
|
||||
# Mixed signs?
|
||||
else:
|
||||
a = fzero
|
||||
# max(-a,b)**n
|
||||
sa = mpf_neg(sa)
|
||||
if mpf_ge(sa, sb):
|
||||
b = mpf_pow_int(sa, n, prec, round_ceiling)
|
||||
else:
|
||||
b = mpf_pow_int(sb, n, prec, round_ceiling)
|
||||
return a, b
|
||||
|
||||
def mpi_pow(s, t, prec):
|
||||
ta, tb = t
|
||||
if ta == tb and ta not in (finf, fninf):
|
||||
if ta == from_int(to_int(ta)):
|
||||
return mpi_pow_int(s, to_int(ta), prec)
|
||||
if ta == fhalf:
|
||||
return mpi_sqrt(s, prec)
|
||||
u = mpi_log(s, prec + 20)
|
||||
v = mpi_mul(u, t, prec + 20)
|
||||
return mpi_exp(v, prec)
|
||||
|
||||
def MIN(x, y):
|
||||
if mpf_le(x, y):
|
||||
return x
|
||||
return y
|
||||
|
||||
def MAX(x, y):
|
||||
if mpf_ge(x, y):
|
||||
return x
|
||||
return y
|
||||
|
||||
def cos_sin_quadrant(x, wp):
|
||||
sign, man, exp, bc = x
|
||||
if x == fzero:
|
||||
return fone, fzero, 0
|
||||
# TODO: combine evaluation code to avoid duplicate modulo
|
||||
c, s = mpf_cos_sin(x, wp)
|
||||
t, n, wp_ = mod_pi2(man, exp, exp+bc, 15)
|
||||
if sign:
|
||||
n = -1-n
|
||||
return c, s, n
|
||||
|
||||
def mpi_cos_sin(x, prec):
|
||||
a, b = x
|
||||
if a == b == fzero:
|
||||
return (fone, fone), (fzero, fzero)
|
||||
# Guaranteed to contain both -1 and 1
|
||||
if (finf in x) or (fninf in x):
|
||||
return (fnone, fone), (fnone, fone)
|
||||
wp = prec + 20
|
||||
ca, sa, na = cos_sin_quadrant(a, wp)
|
||||
cb, sb, nb = cos_sin_quadrant(b, wp)
|
||||
ca, cb = mpf_min_max([ca, cb])
|
||||
sa, sb = mpf_min_max([sa, sb])
|
||||
# Both functions are monotonic within one quadrant
|
||||
if na == nb:
|
||||
pass
|
||||
# Guaranteed to contain both -1 and 1
|
||||
elif nb - na >= 4:
|
||||
return (fnone, fone), (fnone, fone)
|
||||
else:
|
||||
# cos has maximum between a and b
|
||||
if na//4 != nb//4:
|
||||
cb = fone
|
||||
# cos has minimum
|
||||
if (na-2)//4 != (nb-2)//4:
|
||||
ca = fnone
|
||||
# sin has maximum
|
||||
if (na-1)//4 != (nb-1)//4:
|
||||
sb = fone
|
||||
# sin has minimum
|
||||
if (na-3)//4 != (nb-3)//4:
|
||||
sa = fnone
|
||||
# Perturb to force interval rounding
|
||||
more = from_man_exp((MPZ_ONE<<wp) + (MPZ_ONE<<10), -wp)
|
||||
less = from_man_exp((MPZ_ONE<<wp) - (MPZ_ONE<<10), -wp)
|
||||
def finalize(v, rounding):
|
||||
if bool(v[0]) == (rounding == round_floor):
|
||||
p = more
|
||||
else:
|
||||
p = less
|
||||
v = mpf_mul(v, p, prec, rounding)
|
||||
sign, man, exp, bc = v
|
||||
if exp+bc >= 1:
|
||||
if sign:
|
||||
return fnone
|
||||
return fone
|
||||
return v
|
||||
ca = finalize(ca, round_floor)
|
||||
cb = finalize(cb, round_ceiling)
|
||||
sa = finalize(sa, round_floor)
|
||||
sb = finalize(sb, round_ceiling)
|
||||
return (ca,cb), (sa,sb)
|
||||
|
||||
def mpi_cos(x, prec):
|
||||
return mpi_cos_sin(x, prec)[0]
|
||||
|
||||
def mpi_sin(x, prec):
|
||||
return mpi_cos_sin(x, prec)[1]
|
||||
|
||||
def mpi_tan(x, prec):
|
||||
cos, sin = mpi_cos_sin(x, prec+20)
|
||||
return mpi_div(sin, cos, prec)
|
||||
|
||||
def mpi_cot(x, prec):
|
||||
cos, sin = mpi_cos_sin(x, prec+20)
|
||||
return mpi_div(cos, sin, prec)
|
||||
|
||||
def mpi_from_str_a_b(x, y, percent, prec):
|
||||
wp = prec + 20
|
||||
xa = from_str(x, wp, round_floor)
|
||||
xb = from_str(x, wp, round_ceiling)
|
||||
#ya = from_str(y, wp, round_floor)
|
||||
y = from_str(y, wp, round_ceiling)
|
||||
assert mpf_ge(y, fzero)
|
||||
if percent:
|
||||
y = mpf_mul(MAX(mpf_abs(xa), mpf_abs(xb)), y, wp, round_ceiling)
|
||||
y = mpf_div(y, from_int(100), wp, round_ceiling)
|
||||
a = mpf_sub(xa, y, prec, round_floor)
|
||||
b = mpf_add(xb, y, prec, round_ceiling)
|
||||
return a, b
|
||||
|
||||
def mpi_from_str(s, prec):
|
||||
"""
|
||||
Parse an interval number given as a string.
|
||||
|
||||
Allowed forms are
|
||||
|
||||
"-1.23e-27"
|
||||
Any single decimal floating-point literal.
|
||||
"a +- b" or "a (b)"
|
||||
a is the midpoint of the interval and b is the half-width
|
||||
"a +- b%" or "a (b%)"
|
||||
a is the midpoint of the interval and the half-width
|
||||
is b percent of a (`a \times b / 100`).
|
||||
"[a, b]"
|
||||
The interval indicated directly.
|
||||
"x[y,z]e"
|
||||
x are shared digits, y and z are unequal digits, e is the exponent.
|
||||
|
||||
"""
|
||||
e = ValueError("Improperly formed interval number '%s'" % s)
|
||||
s = s.replace(" ", "")
|
||||
wp = prec + 20
|
||||
if "+-" in s:
|
||||
x, y = s.split("+-")
|
||||
return mpi_from_str_a_b(x, y, False, prec)
|
||||
# case 2
|
||||
elif "(" in s:
|
||||
# Don't confuse with a complex number (x,y)
|
||||
if s[0] == "(" or ")" not in s:
|
||||
raise e
|
||||
s = s.replace(")", "")
|
||||
percent = False
|
||||
if "%" in s:
|
||||
if s[-1] != "%":
|
||||
raise e
|
||||
percent = True
|
||||
s = s.replace("%", "")
|
||||
x, y = s.split("(")
|
||||
return mpi_from_str_a_b(x, y, percent, prec)
|
||||
elif "," in s:
|
||||
if ('[' not in s) or (']' not in s):
|
||||
raise e
|
||||
if s[0] == '[':
|
||||
# case 3
|
||||
s = s.replace("[", "")
|
||||
s = s.replace("]", "")
|
||||
a, b = s.split(",")
|
||||
a = from_str(a, prec, round_floor)
|
||||
b = from_str(b, prec, round_ceiling)
|
||||
return a, b
|
||||
else:
|
||||
# case 4
|
||||
x, y = s.split('[')
|
||||
y, z = y.split(',')
|
||||
if 'e' in s:
|
||||
z, e = z.split(']')
|
||||
else:
|
||||
z, e = z.rstrip(']'), ''
|
||||
a = from_str(x+y+e, prec, round_floor)
|
||||
b = from_str(x+z+e, prec, round_ceiling)
|
||||
return a, b
|
||||
else:
|
||||
a = from_str(s, prec, round_floor)
|
||||
b = from_str(s, prec, round_ceiling)
|
||||
return a, b
|
||||
|
||||
def mpi_to_str(x, dps, use_spaces=True, brackets='[]', mode='brackets', error_dps=4, **kwargs):
|
||||
"""
|
||||
Convert a mpi interval to a string.
|
||||
|
||||
**Arguments**
|
||||
|
||||
*dps*
|
||||
decimal places to use for printing
|
||||
*use_spaces*
|
||||
use spaces for more readable output, defaults to true
|
||||
*brackets*
|
||||
pair of strings (or two-character string) giving left and right brackets
|
||||
*mode*
|
||||
mode of display: 'plusminus', 'percent', 'brackets' (default) or 'diff'
|
||||
*error_dps*
|
||||
limit the error to *error_dps* digits (mode 'plusminus and 'percent')
|
||||
|
||||
Additional keyword arguments are forwarded to the mpf-to-string conversion
|
||||
for the components of the output.
|
||||
|
||||
**Examples**
|
||||
|
||||
>>> from mpmath import mpi, mp
|
||||
>>> mp.dps = 30
|
||||
>>> x = mpi(1, 2)._mpi_
|
||||
>>> mpi_to_str(x, 2, mode='plusminus')
|
||||
'1.5 +- 0.5'
|
||||
>>> mpi_to_str(x, 2, mode='percent')
|
||||
'1.5 (33.33%)'
|
||||
>>> mpi_to_str(x, 2, mode='brackets')
|
||||
'[1.0, 2.0]'
|
||||
>>> mpi_to_str(x, 2, mode='brackets' , brackets=('<', '>'))
|
||||
'<1.0, 2.0>'
|
||||
>>> x = mpi('5.2582327113062393041', '5.2582327113062749951')._mpi_
|
||||
>>> mpi_to_str(x, 15, mode='diff')
|
||||
'5.2582327113062[4, 7]'
|
||||
>>> mpi_to_str(mpi(0)._mpi_, 2, mode='percent')
|
||||
'0.0 (0.0%)'
|
||||
|
||||
"""
|
||||
prec = dps_to_prec(dps)
|
||||
wp = prec + 20
|
||||
a, b = x
|
||||
mid = mpi_mid(x, prec)
|
||||
delta = mpi_delta(x, prec)
|
||||
a_str = to_str(a, dps, **kwargs)
|
||||
b_str = to_str(b, dps, **kwargs)
|
||||
mid_str = to_str(mid, dps, **kwargs)
|
||||
sp = ""
|
||||
if use_spaces:
|
||||
sp = " "
|
||||
br1, br2 = brackets
|
||||
if mode == 'plusminus':
|
||||
delta_str = to_str(mpf_shift(delta,-1), dps, **kwargs)
|
||||
s = mid_str + sp + "+-" + sp + delta_str
|
||||
elif mode == 'percent':
|
||||
if mid == fzero:
|
||||
p = fzero
|
||||
else:
|
||||
# p = 100 * delta(x) / (2*mid(x))
|
||||
p = mpf_mul(delta, from_int(100))
|
||||
p = mpf_div(p, mpf_mul(mid, from_int(2)), wp)
|
||||
s = mid_str + sp + "(" + to_str(p, error_dps) + "%)"
|
||||
elif mode == 'brackets':
|
||||
s = br1 + a_str + "," + sp + b_str + br2
|
||||
elif mode == 'diff':
|
||||
# use more digits if str(x.a) and str(x.b) are equal
|
||||
if a_str == b_str:
|
||||
a_str = to_str(a, dps+3, **kwargs)
|
||||
b_str = to_str(b, dps+3, **kwargs)
|
||||
# separate mantissa and exponent
|
||||
a = a_str.split('e')
|
||||
if len(a) == 1:
|
||||
a.append('')
|
||||
b = b_str.split('e')
|
||||
if len(b) == 1:
|
||||
b.append('')
|
||||
if a[1] == b[1]:
|
||||
if a[0] != b[0]:
|
||||
for i in xrange(len(a[0]) + 1):
|
||||
if a[0][i] != b[0][i]:
|
||||
break
|
||||
s = (a[0][:i] + br1 + a[0][i:] + ',' + sp + b[0][i:] + br2
|
||||
+ 'e'*min(len(a[1]), 1) + a[1])
|
||||
else: # no difference
|
||||
s = a[0] + br1 + br2 + 'e'*min(len(a[1]), 1) + a[1]
|
||||
else:
|
||||
s = br1 + 'e'.join(a) + ',' + sp + 'e'.join(b) + br2
|
||||
else:
|
||||
raise ValueError("'%s' is unknown mode for printing mpi" % mode)
|
||||
return s
|
||||
|
||||
def mpci_add(x, y, prec):
|
||||
a, b = x
|
||||
c, d = y
|
||||
return mpi_add(a, c, prec), mpi_add(b, d, prec)
|
||||
|
||||
def mpci_sub(x, y, prec):
|
||||
a, b = x
|
||||
c, d = y
|
||||
return mpi_sub(a, c, prec), mpi_sub(b, d, prec)
|
||||
|
||||
def mpci_neg(x, prec=0):
|
||||
a, b = x
|
||||
return mpi_neg(a, prec), mpi_neg(b, prec)
|
||||
|
||||
def mpci_pos(x, prec):
|
||||
a, b = x
|
||||
return mpi_pos(a, prec), mpi_pos(b, prec)
|
||||
|
||||
def mpci_mul(x, y, prec):
|
||||
# TODO: optimize for real/imag cases
|
||||
a, b = x
|
||||
c, d = y
|
||||
r1 = mpi_mul(a,c)
|
||||
r2 = mpi_mul(b,d)
|
||||
re = mpi_sub(r1,r2,prec)
|
||||
i1 = mpi_mul(a,d)
|
||||
i2 = mpi_mul(b,c)
|
||||
im = mpi_add(i1,i2,prec)
|
||||
return re, im
|
||||
|
||||
def mpci_div(x, y, prec):
|
||||
# TODO: optimize for real/imag cases
|
||||
a, b = x
|
||||
c, d = y
|
||||
wp = prec+20
|
||||
m1 = mpi_square(c)
|
||||
m2 = mpi_square(d)
|
||||
m = mpi_add(m1,m2,wp)
|
||||
re = mpi_add(mpi_mul(a,c), mpi_mul(b,d), wp)
|
||||
im = mpi_sub(mpi_mul(b,c), mpi_mul(a,d), wp)
|
||||
re = mpi_div(re, m, prec)
|
||||
im = mpi_div(im, m, prec)
|
||||
return re, im
|
||||
|
||||
def mpci_exp(x, prec):
|
||||
a, b = x
|
||||
wp = prec+20
|
||||
r = mpi_exp(a, wp)
|
||||
c, s = mpi_cos_sin(b, wp)
|
||||
a = mpi_mul(r, c, prec)
|
||||
b = mpi_mul(r, s, prec)
|
||||
return a, b
|
||||
|
||||
def mpi_shift(x, n):
|
||||
a, b = x
|
||||
return mpf_shift(a,n), mpf_shift(b,n)
|
||||
|
||||
def mpi_cosh_sinh(x, prec):
|
||||
# TODO: accuracy for small x
|
||||
wp = prec+20
|
||||
e1 = mpi_exp(x, wp)
|
||||
e2 = mpi_div(mpi_one, e1, wp)
|
||||
c = mpi_add(e1, e2, prec)
|
||||
s = mpi_sub(e1, e2, prec)
|
||||
c = mpi_shift(c, -1)
|
||||
s = mpi_shift(s, -1)
|
||||
return c, s
|
||||
|
||||
def mpci_cos(x, prec):
|
||||
a, b = x
|
||||
wp = prec+10
|
||||
c, s = mpi_cos_sin(a, wp)
|
||||
ch, sh = mpi_cosh_sinh(b, wp)
|
||||
re = mpi_mul(c, ch, prec)
|
||||
im = mpi_mul(s, sh, prec)
|
||||
return re, mpi_neg(im)
|
||||
|
||||
def mpci_sin(x, prec):
|
||||
a, b = x
|
||||
wp = prec+10
|
||||
c, s = mpi_cos_sin(a, wp)
|
||||
ch, sh = mpi_cosh_sinh(b, wp)
|
||||
re = mpi_mul(s, ch, prec)
|
||||
im = mpi_mul(c, sh, prec)
|
||||
return re, im
|
||||
|
||||
def mpci_abs(x, prec):
|
||||
a, b = x
|
||||
if a == mpi_zero:
|
||||
return mpi_abs(b)
|
||||
if b == mpi_zero:
|
||||
return mpi_abs(a)
|
||||
# Important: nonnegative
|
||||
a = mpi_square(a)
|
||||
b = mpi_square(b)
|
||||
t = mpi_add(a, b, prec+20)
|
||||
return mpi_sqrt(t, prec)
|
||||
|
||||
def mpi_atan2(y, x, prec):
|
||||
ya, yb = y
|
||||
xa, xb = x
|
||||
# Constrained to the real line
|
||||
if ya == yb == fzero:
|
||||
if mpf_ge(xa, fzero):
|
||||
return mpi_zero
|
||||
return mpi_pi(prec)
|
||||
# Right half-plane
|
||||
if mpf_ge(xa, fzero):
|
||||
if mpf_ge(ya, fzero):
|
||||
a = mpf_atan2(ya, xb, prec, round_floor)
|
||||
else:
|
||||
a = mpf_atan2(ya, xa, prec, round_floor)
|
||||
if mpf_ge(yb, fzero):
|
||||
b = mpf_atan2(yb, xa, prec, round_ceiling)
|
||||
else:
|
||||
b = mpf_atan2(yb, xb, prec, round_ceiling)
|
||||
# Upper half-plane
|
||||
elif mpf_ge(ya, fzero):
|
||||
b = mpf_atan2(ya, xa, prec, round_ceiling)
|
||||
if mpf_le(xb, fzero):
|
||||
a = mpf_atan2(yb, xb, prec, round_floor)
|
||||
else:
|
||||
a = mpf_atan2(ya, xb, prec, round_floor)
|
||||
# Lower half-plane
|
||||
elif mpf_le(yb, fzero):
|
||||
a = mpf_atan2(yb, xa, prec, round_floor)
|
||||
if mpf_le(xb, fzero):
|
||||
b = mpf_atan2(ya, xb, prec, round_ceiling)
|
||||
else:
|
||||
b = mpf_atan2(yb, xb, prec, round_ceiling)
|
||||
# Covering the origin
|
||||
else:
|
||||
b = mpf_pi(prec, round_ceiling)
|
||||
a = mpf_neg(b)
|
||||
return a, b
|
||||
|
||||
def mpci_arg(z, prec):
|
||||
x, y = z
|
||||
return mpi_atan2(y, x, prec)
|
||||
|
||||
def mpci_log(z, prec):
|
||||
x, y = z
|
||||
re = mpi_log(mpci_abs(z, prec+20), prec)
|
||||
im = mpci_arg(z, prec)
|
||||
return re, im
|
||||
|
||||
def mpci_pow(x, y, prec):
|
||||
# TODO: recognize/speed up real cases, integer y
|
||||
yre, yim = y
|
||||
if yim == mpi_zero:
|
||||
ya, yb = yre
|
||||
if ya == yb:
|
||||
sign, man, exp, bc = yb
|
||||
if man and exp >= 0:
|
||||
return mpci_pow_int(x, (-1)**sign * int(man<<exp), prec)
|
||||
# x^0
|
||||
if yb == fzero:
|
||||
return mpci_pow_int(x, 0, prec)
|
||||
wp = prec+20
|
||||
return mpci_exp(mpci_mul(y, mpci_log(x, wp), wp), prec)
|
||||
|
||||
def mpci_square(x, prec):
|
||||
a, b = x
|
||||
# (a+bi)^2 = (a^2-b^2) + 2abi
|
||||
re = mpi_sub(mpi_square(a), mpi_square(b), prec)
|
||||
im = mpi_mul(a, b, prec)
|
||||
im = mpi_shift(im, 1)
|
||||
return re, im
|
||||
|
||||
def mpci_pow_int(x, n, prec):
|
||||
if n < 0:
|
||||
return mpci_div((mpi_one,mpi_zero), mpci_pow_int(x, -n, prec+20), prec)
|
||||
if n == 0:
|
||||
return mpi_one, mpi_zero
|
||||
if n == 1:
|
||||
return mpci_pos(x, prec)
|
||||
if n == 2:
|
||||
return mpci_square(x, prec)
|
||||
wp = prec + 20
|
||||
result = (mpi_one, mpi_zero)
|
||||
while n:
|
||||
if n & 1:
|
||||
result = mpci_mul(result, x, wp)
|
||||
n -= 1
|
||||
x = mpci_square(x, wp)
|
||||
n >>= 1
|
||||
return mpci_pos(result, prec)
|
||||
|
||||
gamma_min_a = from_float(1.46163214496)
|
||||
gamma_min_b = from_float(1.46163214497)
|
||||
gamma_min = (gamma_min_a, gamma_min_b)
|
||||
gamma_mono_imag_a = from_float(-1.1)
|
||||
gamma_mono_imag_b = from_float(1.1)
|
||||
|
||||
def mpi_overlap(x, y):
|
||||
a, b = x
|
||||
c, d = y
|
||||
if mpf_lt(d, a): return False
|
||||
if mpf_gt(c, b): return False
|
||||
return True
|
||||
|
||||
# type = 0 -- gamma
|
||||
# type = 1 -- factorial
|
||||
# type = 2 -- 1/gamma
|
||||
# type = 3 -- log-gamma
|
||||
|
||||
def mpi_gamma(z, prec, type=0):
|
||||
a, b = z
|
||||
wp = prec+20
|
||||
|
||||
if type == 1:
|
||||
return mpi_gamma(mpi_add(z, mpi_one, wp), prec, 0)
|
||||
|
||||
# increasing
|
||||
if mpf_gt(a, gamma_min_b):
|
||||
if type == 0:
|
||||
c = mpf_gamma(a, prec, round_floor)
|
||||
d = mpf_gamma(b, prec, round_ceiling)
|
||||
elif type == 2:
|
||||
c = mpf_rgamma(b, prec, round_floor)
|
||||
d = mpf_rgamma(a, prec, round_ceiling)
|
||||
elif type == 3:
|
||||
c = mpf_loggamma(a, prec, round_floor)
|
||||
d = mpf_loggamma(b, prec, round_ceiling)
|
||||
# decreasing
|
||||
elif mpf_gt(a, fzero) and mpf_lt(b, gamma_min_a):
|
||||
if type == 0:
|
||||
c = mpf_gamma(b, prec, round_floor)
|
||||
d = mpf_gamma(a, prec, round_ceiling)
|
||||
elif type == 2:
|
||||
c = mpf_rgamma(a, prec, round_floor)
|
||||
d = mpf_rgamma(b, prec, round_ceiling)
|
||||
elif type == 3:
|
||||
c = mpf_loggamma(b, prec, round_floor)
|
||||
d = mpf_loggamma(a, prec, round_ceiling)
|
||||
else:
|
||||
# TODO: reflection formula
|
||||
znew = mpi_add(z, mpi_one, wp)
|
||||
if type == 0: return mpi_div(mpi_gamma(znew, prec+2, 0), z, prec)
|
||||
if type == 2: return mpi_mul(mpi_gamma(znew, prec+2, 2), z, prec)
|
||||
if type == 3: return mpi_sub(mpi_gamma(znew, prec+2, 3), mpi_log(z, prec+2), prec)
|
||||
return c, d
|
||||
|
||||
def mpci_gamma(z, prec, type=0):
|
||||
(a1,a2), (b1,b2) = z
|
||||
|
||||
# Real case
|
||||
if b1 == b2 == fzero and (type != 3 or mpf_gt(a1,fzero)):
|
||||
return mpi_gamma(z, prec, type), mpi_zero
|
||||
|
||||
# Estimate precision
|
||||
wp = prec+20
|
||||
if type != 3:
|
||||
amag = a2[2]+a2[3]
|
||||
bmag = b2[2]+b2[3]
|
||||
if a2 != fzero:
|
||||
mag = max(amag, bmag)
|
||||
else:
|
||||
mag = bmag
|
||||
an = abs(to_int(a2))
|
||||
bn = abs(to_int(b2))
|
||||
absn = max(an, bn)
|
||||
gamma_size = max(0,absn*mag)
|
||||
wp += bitcount(gamma_size)
|
||||
|
||||
# Assume type != 1
|
||||
if type == 1:
|
||||
(a1,a2) = mpi_add((a1,a2), mpi_one, wp); z = (a1,a2), (b1,b2)
|
||||
type = 0
|
||||
|
||||
# Avoid non-monotonic region near the negative real axis
|
||||
if mpf_lt(a1, gamma_min_b):
|
||||
if mpi_overlap((b1,b2), (gamma_mono_imag_a, gamma_mono_imag_b)):
|
||||
# TODO: reflection formula
|
||||
#if mpf_lt(a2, mpf_shift(fone,-1)):
|
||||
# znew = mpci_sub((mpi_one,mpi_zero),z,wp)
|
||||
# ...
|
||||
# Recurrence:
|
||||
# gamma(z) = gamma(z+1)/z
|
||||
znew = mpi_add((a1,a2), mpi_one, wp), (b1,b2)
|
||||
if type == 0: return mpci_div(mpci_gamma(znew, prec+2, 0), z, prec)
|
||||
if type == 2: return mpci_mul(mpci_gamma(znew, prec+2, 2), z, prec)
|
||||
if type == 3: return mpci_sub(mpci_gamma(znew, prec+2, 3), mpci_log(z,prec+2), prec)
|
||||
|
||||
# Use monotonicity (except for a small region close to the
|
||||
# origin and near poles)
|
||||
# upper half-plane
|
||||
if mpf_ge(b1, fzero):
|
||||
minre = mpc_loggamma((a1,b2), wp, round_floor)
|
||||
maxre = mpc_loggamma((a2,b1), wp, round_ceiling)
|
||||
minim = mpc_loggamma((a1,b1), wp, round_floor)
|
||||
maxim = mpc_loggamma((a2,b2), wp, round_ceiling)
|
||||
# lower half-plane
|
||||
elif mpf_le(b2, fzero):
|
||||
minre = mpc_loggamma((a1,b1), wp, round_floor)
|
||||
maxre = mpc_loggamma((a2,b2), wp, round_ceiling)
|
||||
minim = mpc_loggamma((a2,b1), wp, round_floor)
|
||||
maxim = mpc_loggamma((a1,b2), wp, round_ceiling)
|
||||
# crosses real axis
|
||||
else:
|
||||
maxre = mpc_loggamma((a2,fzero), wp, round_ceiling)
|
||||
# stretches more into the lower half-plane
|
||||
if mpf_gt(mpf_neg(b1), b2):
|
||||
minre = mpc_loggamma((a1,b1), wp, round_ceiling)
|
||||
else:
|
||||
minre = mpc_loggamma((a1,b2), wp, round_ceiling)
|
||||
minim = mpc_loggamma((a2,b1), wp, round_floor)
|
||||
maxim = mpc_loggamma((a2,b2), wp, round_floor)
|
||||
|
||||
w = (minre[0], maxre[0]), (minim[1], maxim[1])
|
||||
if type == 3:
|
||||
return mpi_pos(w[0], prec), mpi_pos(w[1], prec)
|
||||
if type == 2:
|
||||
w = mpci_neg(w)
|
||||
return mpci_exp(w, prec)
|
||||
|
||||
def mpi_loggamma(z, prec): return mpi_gamma(z, prec, type=3)
|
||||
def mpci_loggamma(z, prec): return mpci_gamma(z, prec, type=3)
|
||||
|
||||
def mpi_rgamma(z, prec): return mpi_gamma(z, prec, type=2)
|
||||
def mpci_rgamma(z, prec): return mpci_gamma(z, prec, type=2)
|
||||
|
||||
def mpi_factorial(z, prec): return mpi_gamma(z, prec, type=1)
|
||||
def mpci_factorial(z, prec): return mpci_gamma(z, prec, type=1)
|
||||
@@ -0,0 +1,672 @@
|
||||
"""
|
||||
This module complements the math and cmath builtin modules by providing
|
||||
fast machine precision versions of some additional functions (gamma, ...)
|
||||
and wrapping math/cmath functions so that they can be called with either
|
||||
real or complex arguments.
|
||||
"""
|
||||
|
||||
import operator
|
||||
import math
|
||||
import cmath
|
||||
|
||||
# Irrational (?) constants
|
||||
pi = 3.1415926535897932385
|
||||
e = 2.7182818284590452354
|
||||
sqrt2 = 1.4142135623730950488
|
||||
sqrt5 = 2.2360679774997896964
|
||||
phi = 1.6180339887498948482
|
||||
ln2 = 0.69314718055994530942
|
||||
ln10 = 2.302585092994045684
|
||||
euler = 0.57721566490153286061
|
||||
catalan = 0.91596559417721901505
|
||||
khinchin = 2.6854520010653064453
|
||||
apery = 1.2020569031595942854
|
||||
|
||||
logpi = 1.1447298858494001741
|
||||
|
||||
def _mathfun_real(f_real, f_complex):
|
||||
def f(x, **kwargs):
|
||||
if type(x) is float:
|
||||
return f_real(x)
|
||||
if type(x) is complex:
|
||||
return f_complex(x)
|
||||
try:
|
||||
x = float(x)
|
||||
return f_real(x)
|
||||
except (TypeError, ValueError):
|
||||
x = complex(x)
|
||||
return f_complex(x)
|
||||
f.__name__ = f_real.__name__
|
||||
return f
|
||||
|
||||
def _mathfun(f_real, f_complex):
|
||||
def f(x, **kwargs):
|
||||
if type(x) is complex:
|
||||
return f_complex(x)
|
||||
try:
|
||||
return f_real(float(x))
|
||||
except (TypeError, ValueError):
|
||||
return f_complex(complex(x))
|
||||
f.__name__ = f_real.__name__
|
||||
return f
|
||||
|
||||
def _mathfun_n(f_real, f_complex):
|
||||
def f(*args, **kwargs):
|
||||
try:
|
||||
return f_real(*(float(x) for x in args))
|
||||
except (TypeError, ValueError):
|
||||
return f_complex(*(complex(x) for x in args))
|
||||
f.__name__ = f_real.__name__
|
||||
return f
|
||||
|
||||
# Workaround for non-raising log and sqrt in Python 2.5 and 2.4
|
||||
# on Unix system
|
||||
try:
|
||||
math.log(-2.0)
|
||||
def math_log(x):
|
||||
if x <= 0.0:
|
||||
raise ValueError("math domain error")
|
||||
return math.log(x)
|
||||
def math_sqrt(x):
|
||||
if x < 0.0:
|
||||
raise ValueError("math domain error")
|
||||
return math.sqrt(x)
|
||||
except (ValueError, TypeError):
|
||||
math_log = math.log
|
||||
math_sqrt = math.sqrt
|
||||
|
||||
pow = _mathfun_n(operator.pow, lambda x, y: complex(x)**y)
|
||||
log = _mathfun_n(math_log, cmath.log)
|
||||
sqrt = _mathfun(math_sqrt, cmath.sqrt)
|
||||
exp = _mathfun_real(math.exp, cmath.exp)
|
||||
|
||||
cos = _mathfun_real(math.cos, cmath.cos)
|
||||
sin = _mathfun_real(math.sin, cmath.sin)
|
||||
tan = _mathfun_real(math.tan, cmath.tan)
|
||||
|
||||
acos = _mathfun(math.acos, cmath.acos)
|
||||
asin = _mathfun(math.asin, cmath.asin)
|
||||
atan = _mathfun_real(math.atan, cmath.atan)
|
||||
|
||||
cosh = _mathfun_real(math.cosh, cmath.cosh)
|
||||
sinh = _mathfun_real(math.sinh, cmath.sinh)
|
||||
tanh = _mathfun_real(math.tanh, cmath.tanh)
|
||||
|
||||
floor = _mathfun_real(math.floor,
|
||||
lambda z: complex(math.floor(z.real), math.floor(z.imag)))
|
||||
ceil = _mathfun_real(math.ceil,
|
||||
lambda z: complex(math.ceil(z.real), math.ceil(z.imag)))
|
||||
|
||||
|
||||
cos_sin = _mathfun_real(lambda x: (math.cos(x), math.sin(x)),
|
||||
lambda z: (cmath.cos(z), cmath.sin(z)))
|
||||
|
||||
cbrt = _mathfun(lambda x: x**(1./3), lambda z: z**(1./3))
|
||||
|
||||
def nthroot(x, n):
|
||||
r = 1./n
|
||||
try:
|
||||
return float(x) ** r
|
||||
except (ValueError, TypeError):
|
||||
return complex(x) ** r
|
||||
|
||||
def _sinpi_real(x):
|
||||
if x < 0:
|
||||
return -_sinpi_real(-x)
|
||||
n, r = divmod(x, 0.5)
|
||||
r *= pi
|
||||
n %= 4
|
||||
if n == 0: return math.sin(r)
|
||||
if n == 1: return math.cos(r)
|
||||
if n == 2: return -math.sin(r)
|
||||
if n == 3: return -math.cos(r)
|
||||
|
||||
def _cospi_real(x):
|
||||
if x < 0:
|
||||
x = -x
|
||||
n, r = divmod(x, 0.5)
|
||||
r *= pi
|
||||
n %= 4
|
||||
if n == 0: return math.cos(r)
|
||||
if n == 1: return -math.sin(r)
|
||||
if n == 2: return -math.cos(r)
|
||||
if n == 3: return math.sin(r)
|
||||
|
||||
def _sinpi_complex(z):
|
||||
if z.real < 0:
|
||||
return -_sinpi_complex(-z)
|
||||
n, r = divmod(z.real, 0.5)
|
||||
z = pi*complex(r, z.imag)
|
||||
n %= 4
|
||||
if n == 0: return cmath.sin(z)
|
||||
if n == 1: return cmath.cos(z)
|
||||
if n == 2: return -cmath.sin(z)
|
||||
if n == 3: return -cmath.cos(z)
|
||||
|
||||
def _cospi_complex(z):
|
||||
if z.real < 0:
|
||||
z = -z
|
||||
n, r = divmod(z.real, 0.5)
|
||||
z = pi*complex(r, z.imag)
|
||||
n %= 4
|
||||
if n == 0: return cmath.cos(z)
|
||||
if n == 1: return -cmath.sin(z)
|
||||
if n == 2: return -cmath.cos(z)
|
||||
if n == 3: return cmath.sin(z)
|
||||
|
||||
cospi = _mathfun_real(_cospi_real, _cospi_complex)
|
||||
sinpi = _mathfun_real(_sinpi_real, _sinpi_complex)
|
||||
|
||||
def tanpi(x):
|
||||
try:
|
||||
return sinpi(x) / cospi(x)
|
||||
except OverflowError:
|
||||
if complex(x).imag > 10:
|
||||
return 1j
|
||||
if complex(x).imag < 10:
|
||||
return -1j
|
||||
raise
|
||||
|
||||
def cotpi(x):
|
||||
try:
|
||||
return cospi(x) / sinpi(x)
|
||||
except OverflowError:
|
||||
if complex(x).imag > 10:
|
||||
return -1j
|
||||
if complex(x).imag < 10:
|
||||
return 1j
|
||||
raise
|
||||
|
||||
INF = 1e300*1e300
|
||||
NINF = -INF
|
||||
NAN = INF-INF
|
||||
EPS = 2.2204460492503131e-16
|
||||
|
||||
_exact_gamma = (INF, 1.0, 1.0, 2.0, 6.0, 24.0, 120.0, 720.0, 5040.0, 40320.0,
|
||||
362880.0, 3628800.0, 39916800.0, 479001600.0, 6227020800.0, 87178291200.0,
|
||||
1307674368000.0, 20922789888000.0, 355687428096000.0, 6402373705728000.0,
|
||||
121645100408832000.0, 2432902008176640000.0)
|
||||
|
||||
_max_exact_gamma = len(_exact_gamma)-1
|
||||
|
||||
# Lanczos coefficients used by the GNU Scientific Library
|
||||
_lanczos_g = 7
|
||||
_lanczos_p = (0.99999999999980993, 676.5203681218851, -1259.1392167224028,
|
||||
771.32342877765313, -176.61502916214059, 12.507343278686905,
|
||||
-0.13857109526572012, 9.9843695780195716e-6, 1.5056327351493116e-7)
|
||||
|
||||
def _gamma_real(x):
|
||||
_intx = int(x)
|
||||
if _intx == x:
|
||||
if _intx <= 0:
|
||||
#return (-1)**_intx * INF
|
||||
raise ZeroDivisionError("gamma function pole")
|
||||
if _intx <= _max_exact_gamma:
|
||||
return _exact_gamma[_intx]
|
||||
if x < 0.5:
|
||||
# TODO: sinpi
|
||||
return pi / (_sinpi_real(x)*_gamma_real(1-x))
|
||||
else:
|
||||
x -= 1.0
|
||||
r = _lanczos_p[0]
|
||||
for i in range(1, _lanczos_g+2):
|
||||
r += _lanczos_p[i]/(x+i)
|
||||
t = x + _lanczos_g + 0.5
|
||||
return 2.506628274631000502417 * t**(x+0.5) * math.exp(-t) * r
|
||||
|
||||
def _gamma_complex(x):
|
||||
if not x.imag:
|
||||
return complex(_gamma_real(x.real))
|
||||
if x.real < 0.5:
|
||||
# TODO: sinpi
|
||||
return pi / (_sinpi_complex(x)*_gamma_complex(1-x))
|
||||
else:
|
||||
x -= 1.0
|
||||
r = _lanczos_p[0]
|
||||
for i in range(1, _lanczos_g+2):
|
||||
r += _lanczos_p[i]/(x+i)
|
||||
t = x + _lanczos_g + 0.5
|
||||
return 2.506628274631000502417 * t**(x+0.5) * cmath.exp(-t) * r
|
||||
|
||||
gamma = _mathfun_real(_gamma_real, _gamma_complex)
|
||||
|
||||
def rgamma(x):
|
||||
try:
|
||||
return 1./gamma(x)
|
||||
except ZeroDivisionError:
|
||||
return x*0.0
|
||||
|
||||
def factorial(x):
|
||||
return gamma(x+1.0)
|
||||
|
||||
def arg(x):
|
||||
if type(x) is float:
|
||||
return math.atan2(0.0,x)
|
||||
return math.atan2(x.imag,x.real)
|
||||
|
||||
# XXX: broken for negatives
|
||||
def loggamma(x):
|
||||
if type(x) not in (float, complex):
|
||||
try:
|
||||
x = float(x)
|
||||
except (ValueError, TypeError):
|
||||
x = complex(x)
|
||||
try:
|
||||
xreal = x.real
|
||||
ximag = x.imag
|
||||
except AttributeError: # py2.5
|
||||
xreal = x
|
||||
ximag = 0.0
|
||||
# Reflection formula
|
||||
# http://functions.wolfram.com/GammaBetaErf/LogGamma/16/01/01/0003/
|
||||
if xreal < 0.0:
|
||||
if abs(x) < 0.5:
|
||||
v = log(gamma(x))
|
||||
if ximag == 0:
|
||||
v = v.conjugate()
|
||||
return v
|
||||
z = 1-x
|
||||
try:
|
||||
re = z.real
|
||||
im = z.imag
|
||||
except AttributeError: # py2.5
|
||||
re = z
|
||||
im = 0.0
|
||||
refloor = floor(re)
|
||||
if im == 0.0:
|
||||
imsign = 0
|
||||
elif im < 0.0:
|
||||
imsign = -1
|
||||
else:
|
||||
imsign = 1
|
||||
return (-pi*1j)*abs(refloor)*(1-abs(imsign)) + logpi - \
|
||||
log(sinpi(z-refloor)) - loggamma(z) + 1j*pi*refloor*imsign
|
||||
if x == 1.0 or x == 2.0:
|
||||
return x*0
|
||||
p = 0.
|
||||
while abs(x) < 11:
|
||||
p -= log(x)
|
||||
x += 1.0
|
||||
s = 0.918938533204672742 + (x-0.5)*log(x) - x
|
||||
r = 1./x
|
||||
r2 = r*r
|
||||
s += 0.083333333333333333333*r; r *= r2
|
||||
s += -0.0027777777777777777778*r; r *= r2
|
||||
s += 0.00079365079365079365079*r; r *= r2
|
||||
s += -0.0005952380952380952381*r; r *= r2
|
||||
s += 0.00084175084175084175084*r; r *= r2
|
||||
s += -0.0019175269175269175269*r; r *= r2
|
||||
s += 0.0064102564102564102564*r; r *= r2
|
||||
s += -0.02955065359477124183*r
|
||||
return s + p
|
||||
|
||||
_psi_coeff = [
|
||||
0.083333333333333333333,
|
||||
-0.0083333333333333333333,
|
||||
0.003968253968253968254,
|
||||
-0.0041666666666666666667,
|
||||
0.0075757575757575757576,
|
||||
-0.021092796092796092796,
|
||||
0.083333333333333333333,
|
||||
-0.44325980392156862745,
|
||||
3.0539543302701197438,
|
||||
-26.456212121212121212]
|
||||
|
||||
def _digamma_real(x):
|
||||
_intx = int(x)
|
||||
if _intx == x:
|
||||
if _intx <= 0:
|
||||
raise ZeroDivisionError("polygamma pole")
|
||||
if x < 0.5:
|
||||
x = 1.0-x
|
||||
s = pi*cotpi(x)
|
||||
else:
|
||||
s = 0.0
|
||||
while x < 10.0:
|
||||
s -= 1.0/x
|
||||
x += 1.0
|
||||
x2 = x**-2
|
||||
t = x2
|
||||
for c in _psi_coeff:
|
||||
s -= c*t
|
||||
if t < 1e-20:
|
||||
break
|
||||
t *= x2
|
||||
return s + math_log(x) - 0.5/x
|
||||
|
||||
def _digamma_complex(x):
|
||||
if not x.imag:
|
||||
return complex(_digamma_real(x.real))
|
||||
if x.real < 0.5:
|
||||
x = 1.0-x
|
||||
s = pi*cotpi(x)
|
||||
else:
|
||||
s = 0.0
|
||||
while abs(x) < 10.0:
|
||||
s -= 1.0/x
|
||||
x += 1.0
|
||||
x2 = x**-2
|
||||
t = x2
|
||||
for c in _psi_coeff:
|
||||
s -= c*t
|
||||
if abs(t) < 1e-20:
|
||||
break
|
||||
t *= x2
|
||||
return s + cmath.log(x) - 0.5/x
|
||||
|
||||
digamma = _mathfun_real(_digamma_real, _digamma_complex)
|
||||
|
||||
# TODO: could implement complex erf and erfc here. Need
|
||||
# to find an accurate method (avoiding cancellation)
|
||||
# for approx. 1 < abs(x) < 9.
|
||||
|
||||
_erfc_coeff_P = [
|
||||
1.0000000161203922312,
|
||||
2.1275306946297962644,
|
||||
2.2280433377390253297,
|
||||
1.4695509105618423961,
|
||||
0.66275911699770787537,
|
||||
0.20924776504163751585,
|
||||
0.045459713768411264339,
|
||||
0.0063065951710717791934,
|
||||
0.00044560259661560421715][::-1]
|
||||
|
||||
_erfc_coeff_Q = [
|
||||
1.0000000000000000000,
|
||||
3.2559100272784894318,
|
||||
4.9019435608903239131,
|
||||
4.4971472894498014205,
|
||||
2.7845640601891186528,
|
||||
1.2146026030046904138,
|
||||
0.37647108453729465912,
|
||||
0.080970149639040548613,
|
||||
0.011178148899483545902,
|
||||
0.00078981003831980423513][::-1]
|
||||
|
||||
def _polyval(coeffs, x):
|
||||
p = coeffs[0]
|
||||
for c in coeffs[1:]:
|
||||
p = c + x*p
|
||||
return p
|
||||
|
||||
def _erf_taylor(x):
|
||||
# Taylor series assuming 0 <= x <= 1
|
||||
x2 = x*x
|
||||
s = t = x
|
||||
n = 1
|
||||
while abs(t) > 1e-17:
|
||||
t *= x2/n
|
||||
s -= t/(n+n+1)
|
||||
n += 1
|
||||
t *= x2/n
|
||||
s += t/(n+n+1)
|
||||
n += 1
|
||||
return 1.1283791670955125739*s
|
||||
|
||||
def _erfc_mid(x):
|
||||
# Rational approximation assuming 0 <= x <= 9
|
||||
return exp(-x*x)*_polyval(_erfc_coeff_P,x)/_polyval(_erfc_coeff_Q,x)
|
||||
|
||||
def _erfc_asymp(x):
|
||||
# Asymptotic expansion assuming x >= 9
|
||||
x2 = x*x
|
||||
v = exp(-x2)/x*0.56418958354775628695
|
||||
r = t = 0.5 / x2
|
||||
s = 1.0
|
||||
for n in range(1,22,4):
|
||||
s -= t
|
||||
t *= r * (n+2)
|
||||
s += t
|
||||
t *= r * (n+4)
|
||||
if abs(t) < 1e-17:
|
||||
break
|
||||
return s * v
|
||||
|
||||
def erf(x):
|
||||
"""
|
||||
erf of a real number.
|
||||
"""
|
||||
x = float(x)
|
||||
if x != x:
|
||||
return x
|
||||
if x < 0.0:
|
||||
return -erf(-x)
|
||||
if x >= 1.0:
|
||||
if x >= 6.0:
|
||||
return 1.0
|
||||
return 1.0 - _erfc_mid(x)
|
||||
return _erf_taylor(x)
|
||||
|
||||
def erfc(x):
|
||||
"""
|
||||
erfc of a real number.
|
||||
"""
|
||||
x = float(x)
|
||||
if x != x:
|
||||
return x
|
||||
if x < 0.0:
|
||||
if x < -6.0:
|
||||
return 2.0
|
||||
return 2.0-erfc(-x)
|
||||
if x > 9.0:
|
||||
return _erfc_asymp(x)
|
||||
if x >= 1.0:
|
||||
return _erfc_mid(x)
|
||||
return 1.0 - _erf_taylor(x)
|
||||
|
||||
gauss42 = [\
|
||||
(0.99839961899006235, 0.0041059986046490839),
|
||||
(-0.99839961899006235, 0.0041059986046490839),
|
||||
(0.9915772883408609, 0.009536220301748501),
|
||||
(-0.9915772883408609,0.009536220301748501),
|
||||
(0.97934250806374812, 0.014922443697357493),
|
||||
(-0.97934250806374812, 0.014922443697357493),
|
||||
(0.96175936533820439,0.020227869569052644),
|
||||
(-0.96175936533820439, 0.020227869569052644),
|
||||
(0.93892355735498811, 0.025422959526113047),
|
||||
(-0.93892355735498811,0.025422959526113047),
|
||||
(0.91095972490412735, 0.030479240699603467),
|
||||
(-0.91095972490412735, 0.030479240699603467),
|
||||
(0.87802056981217269,0.03536907109759211),
|
||||
(-0.87802056981217269, 0.03536907109759211),
|
||||
(0.8402859832618168, 0.040065735180692258),
|
||||
(-0.8402859832618168,0.040065735180692258),
|
||||
(0.7979620532554873, 0.044543577771965874),
|
||||
(-0.7979620532554873, 0.044543577771965874),
|
||||
(0.75127993568948048,0.048778140792803244),
|
||||
(-0.75127993568948048, 0.048778140792803244),
|
||||
(0.70049459055617114, 0.052746295699174064),
|
||||
(-0.70049459055617114,0.052746295699174064),
|
||||
(0.64588338886924779, 0.056426369358018376),
|
||||
(-0.64588338886924779, 0.056426369358018376),
|
||||
(0.58774459748510932, 0.059798262227586649),
|
||||
(-0.58774459748510932, 0.059798262227586649),
|
||||
(0.5263957499311922, 0.062843558045002565),
|
||||
(-0.5263957499311922, 0.062843558045002565),
|
||||
(0.46217191207042191, 0.065545624364908975),
|
||||
(-0.46217191207042191, 0.065545624364908975),
|
||||
(0.39542385204297503, 0.067889703376521934),
|
||||
(-0.39542385204297503, 0.067889703376521934),
|
||||
(0.32651612446541151, 0.069862992492594159),
|
||||
(-0.32651612446541151, 0.069862992492594159),
|
||||
(0.25582507934287907, 0.071454714265170971),
|
||||
(-0.25582507934287907, 0.071454714265170971),
|
||||
(0.18373680656485453, 0.072656175243804091),
|
||||
(-0.18373680656485453, 0.072656175243804091),
|
||||
(0.11064502720851986, 0.073460813453467527),
|
||||
(-0.11064502720851986, 0.073460813453467527),
|
||||
(0.036948943165351772, 0.073864234232172879),
|
||||
(-0.036948943165351772, 0.073864234232172879)]
|
||||
|
||||
EI_ASYMP_CONVERGENCE_RADIUS = 40.0
|
||||
|
||||
def ei_asymp(z, _e1=False):
|
||||
r = 1./z
|
||||
s = t = 1.0
|
||||
k = 1
|
||||
while 1:
|
||||
t *= k*r
|
||||
s += t
|
||||
if abs(t) < 1e-16:
|
||||
break
|
||||
k += 1
|
||||
v = s*exp(z)/z
|
||||
if _e1:
|
||||
if type(z) is complex:
|
||||
zreal = z.real
|
||||
zimag = z.imag
|
||||
else:
|
||||
zreal = z
|
||||
zimag = 0.0
|
||||
if zimag == 0.0 and zreal > 0.0:
|
||||
v += pi*1j
|
||||
else:
|
||||
if type(z) is complex:
|
||||
if z.imag > 0:
|
||||
v += pi*1j
|
||||
if z.imag < 0:
|
||||
v -= pi*1j
|
||||
return v
|
||||
|
||||
def ei_taylor(z, _e1=False):
|
||||
s = t = z
|
||||
k = 2
|
||||
while 1:
|
||||
t = t*z/k
|
||||
term = t/k
|
||||
if abs(term) < 1e-17:
|
||||
break
|
||||
s += term
|
||||
k += 1
|
||||
s += euler
|
||||
if _e1:
|
||||
s += log(-z)
|
||||
else:
|
||||
if type(z) is float or z.imag == 0.0:
|
||||
s += math_log(abs(z))
|
||||
else:
|
||||
s += cmath.log(z)
|
||||
return s
|
||||
|
||||
def ei(z, _e1=False):
|
||||
typez = type(z)
|
||||
if typez not in (float, complex):
|
||||
try:
|
||||
z = float(z)
|
||||
typez = float
|
||||
except (TypeError, ValueError):
|
||||
z = complex(z)
|
||||
typez = complex
|
||||
if not z:
|
||||
return -INF
|
||||
absz = abs(z)
|
||||
if absz > EI_ASYMP_CONVERGENCE_RADIUS:
|
||||
return ei_asymp(z, _e1)
|
||||
elif absz <= 2.0 or (typez is float and z > 0.0):
|
||||
return ei_taylor(z, _e1)
|
||||
# Integrate, starting from whichever is smaller of a Taylor
|
||||
# series value or an asymptotic series value
|
||||
if typez is complex and z.real > 0.0:
|
||||
zref = z / absz
|
||||
ref = ei_taylor(zref, _e1)
|
||||
else:
|
||||
zref = EI_ASYMP_CONVERGENCE_RADIUS * z / absz
|
||||
ref = ei_asymp(zref, _e1)
|
||||
C = (zref-z)*0.5
|
||||
D = (zref+z)*0.5
|
||||
s = 0.0
|
||||
if type(z) is complex:
|
||||
_exp = cmath.exp
|
||||
else:
|
||||
_exp = math.exp
|
||||
for x,w in gauss42:
|
||||
t = C*x+D
|
||||
s += w*_exp(t)/t
|
||||
ref -= C*s
|
||||
return ref
|
||||
|
||||
def e1(z):
|
||||
# hack to get consistent signs if the imaginary part if 0
|
||||
# and signed
|
||||
typez = type(z)
|
||||
if type(z) not in (float, complex):
|
||||
try:
|
||||
z = float(z)
|
||||
typez = float
|
||||
except (TypeError, ValueError):
|
||||
z = complex(z)
|
||||
typez = complex
|
||||
if typez is complex and not z.imag:
|
||||
z = complex(z.real, 0.0)
|
||||
# end hack
|
||||
return -ei(-z, _e1=True)
|
||||
|
||||
_zeta_int = [\
|
||||
-0.5,
|
||||
0.0,
|
||||
1.6449340668482264365,1.2020569031595942854,1.0823232337111381915,
|
||||
1.0369277551433699263,1.0173430619844491397,1.0083492773819228268,
|
||||
1.0040773561979443394,1.0020083928260822144,1.0009945751278180853,
|
||||
1.0004941886041194646,1.0002460865533080483,1.0001227133475784891,
|
||||
1.0000612481350587048,1.0000305882363070205,1.0000152822594086519,
|
||||
1.0000076371976378998,1.0000038172932649998,1.0000019082127165539,
|
||||
1.0000009539620338728,1.0000004769329867878,1.0000002384505027277,
|
||||
1.0000001192199259653,1.0000000596081890513,1.0000000298035035147,
|
||||
1.0000000149015548284]
|
||||
|
||||
_zeta_P = [-3.50000000087575873, -0.701274355654678147,
|
||||
-0.0672313458590012612, -0.00398731457954257841,
|
||||
-0.000160948723019303141, -4.67633010038383371e-6,
|
||||
-1.02078104417700585e-7, -1.68030037095896287e-9,
|
||||
-1.85231868742346722e-11][::-1]
|
||||
|
||||
_zeta_Q = [1.00000000000000000, -0.936552848762465319,
|
||||
-0.0588835413263763741, -0.00441498861482948666,
|
||||
-0.000143416758067432622, -5.10691659585090782e-6,
|
||||
-9.58813053268913799e-8, -1.72963791443181972e-9,
|
||||
-1.83527919681474132e-11][::-1]
|
||||
|
||||
_zeta_1 = [3.03768838606128127e-10, -1.21924525236601262e-8,
|
||||
2.01201845887608893e-7, -1.53917240683468381e-6,
|
||||
-5.09890411005967954e-7, 0.000122464707271619326,
|
||||
-0.000905721539353130232, -0.00239315326074843037,
|
||||
0.084239750013159168, 0.418938517907442414, 0.500000001921884009]
|
||||
|
||||
_zeta_0 = [-3.46092485016748794e-10, -6.42610089468292485e-9,
|
||||
1.76409071536679773e-7, -1.47141263991560698e-6, -6.38880222546167613e-7,
|
||||
0.000122641099800668209, -0.000905894913516772796, -0.00239303348507992713,
|
||||
0.0842396947501199816, 0.418938533204660256, 0.500000000000000052]
|
||||
|
||||
def zeta(s):
|
||||
"""
|
||||
Riemann zeta function, real argument
|
||||
"""
|
||||
if not isinstance(s, (float, int)):
|
||||
try:
|
||||
s = float(s)
|
||||
except (ValueError, TypeError):
|
||||
try:
|
||||
s = complex(s)
|
||||
if not s.imag:
|
||||
return complex(zeta(s.real))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
raise NotImplementedError
|
||||
if s == 1:
|
||||
raise ValueError("zeta(1) pole")
|
||||
if s >= 27:
|
||||
return 1.0 + 2.0**(-s) + 3.0**(-s)
|
||||
n = int(s)
|
||||
if n == s:
|
||||
if n >= 0:
|
||||
return _zeta_int[n]
|
||||
if not (n % 2):
|
||||
return 0.0
|
||||
if s <= 0.0:
|
||||
return 2.**s*pi**(s-1)*_sinpi_real(0.5*s)*_gamma_real(1-s)*zeta(1-s)
|
||||
if s <= 2.0:
|
||||
if s <= 1.0:
|
||||
return _polyval(_zeta_0,s)/(s-1)
|
||||
return _polyval(_zeta_1,s)/(s-1)
|
||||
z = _polyval(_zeta_P,s) / _polyval(_zeta_Q,s)
|
||||
return 1.0 + 2.0**(-s) + 3.0**(-s) + 4.0**(-s)*z
|
||||
@@ -0,0 +1,2 @@
|
||||
from . import eigen # to set methods
|
||||
from . import eigen_symmetric # to set methods
|
||||
@@ -0,0 +1,531 @@
|
||||
from ..libmp.backend import xrange
|
||||
|
||||
# TODO: should use diagonalization-based algorithms
|
||||
|
||||
class MatrixCalculusMethods(object):
|
||||
|
||||
def _exp_pade(ctx, a):
|
||||
"""
|
||||
Exponential of a matrix using Pade approximants.
|
||||
|
||||
See G. H. Golub, C. F. van Loan 'Matrix Computations',
|
||||
third Ed., page 572
|
||||
|
||||
TODO:
|
||||
- find a good estimate for q
|
||||
- reduce the number of matrix multiplications to improve
|
||||
performance
|
||||
"""
|
||||
def eps_pade(p):
|
||||
return ctx.mpf(2)**(3-2*p) * \
|
||||
ctx.factorial(p)**2/(ctx.factorial(2*p)**2 * (2*p + 1))
|
||||
q = 4
|
||||
extraq = 8
|
||||
while 1:
|
||||
if eps_pade(q) < ctx.eps:
|
||||
break
|
||||
q += 1
|
||||
q += extraq
|
||||
j = int(max(1, ctx.mag(ctx.mnorm(a,'inf'))))
|
||||
extra = q
|
||||
prec = ctx.prec
|
||||
ctx.dps += extra + 3
|
||||
try:
|
||||
a = a/2**j
|
||||
na = a.rows
|
||||
den = ctx.eye(na)
|
||||
num = ctx.eye(na)
|
||||
x = ctx.eye(na)
|
||||
c = ctx.mpf(1)
|
||||
for k in range(1, q+1):
|
||||
c *= ctx.mpf(q - k + 1)/((2*q - k + 1) * k)
|
||||
x = a*x
|
||||
cx = c*x
|
||||
num += cx
|
||||
den += (-1)**k * cx
|
||||
f = ctx.lu_solve_mat(den, num)
|
||||
for k in range(j):
|
||||
f = f*f
|
||||
finally:
|
||||
ctx.prec = prec
|
||||
return f*1
|
||||
|
||||
def expm(ctx, A, method='taylor'):
|
||||
r"""
|
||||
Computes the matrix exponential of a square matrix `A`, which is defined
|
||||
by the power series
|
||||
|
||||
.. math ::
|
||||
|
||||
\exp(A) = I + A + \frac{A^2}{2!} + \frac{A^3}{3!} + \ldots
|
||||
|
||||
With method='taylor', the matrix exponential is computed
|
||||
using the Taylor series. With method='pade', Pade approximants
|
||||
are used instead.
|
||||
|
||||
**Examples**
|
||||
|
||||
Basic examples::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 15; mp.pretty = True
|
||||
>>> expm(zeros(3))
|
||||
[1.0 0.0 0.0]
|
||||
[0.0 1.0 0.0]
|
||||
[0.0 0.0 1.0]
|
||||
>>> expm(eye(3))
|
||||
[2.71828182845905 0.0 0.0]
|
||||
[ 0.0 2.71828182845905 0.0]
|
||||
[ 0.0 0.0 2.71828182845905]
|
||||
>>> expm([[1,1,0],[1,0,1],[0,1,0]])
|
||||
[ 3.86814500615414 2.26812870852145 0.841130841230196]
|
||||
[ 2.26812870852145 2.44114713886289 1.42699786729125]
|
||||
[0.841130841230196 1.42699786729125 1.6000162976327]
|
||||
>>> expm([[1,1,0],[1,0,1],[0,1,0]], method='pade')
|
||||
[ 3.86814500615414 2.26812870852145 0.841130841230196]
|
||||
[ 2.26812870852145 2.44114713886289 1.42699786729125]
|
||||
[0.841130841230196 1.42699786729125 1.6000162976327]
|
||||
>>> expm([[1+j, 0], [1+j,1]])
|
||||
[(1.46869393991589 + 2.28735528717884j) 0.0]
|
||||
[ (1.03776739863568 + 3.536943175722j) (2.71828182845905 + 0.0j)]
|
||||
|
||||
Matrices with large entries are allowed::
|
||||
|
||||
>>> expm(matrix([[1,2],[2,3]])**25)
|
||||
[5.65024064048415e+2050488462815550 9.14228140091932e+2050488462815550]
|
||||
[9.14228140091932e+2050488462815550 1.47925220414035e+2050488462815551]
|
||||
|
||||
The identity `\exp(A+B) = \exp(A) \exp(B)` does not hold for
|
||||
noncommuting matrices::
|
||||
|
||||
>>> A = hilbert(3)
|
||||
>>> B = A + eye(3)
|
||||
>>> chop(mnorm(A*B - B*A))
|
||||
0.0
|
||||
>>> chop(mnorm(expm(A+B) - expm(A)*expm(B)))
|
||||
0.0
|
||||
>>> B = A + ones(3)
|
||||
>>> mnorm(A*B - B*A)
|
||||
1.8
|
||||
>>> mnorm(expm(A+B) - expm(A)*expm(B))
|
||||
42.0927851137247
|
||||
|
||||
"""
|
||||
if method == 'pade':
|
||||
prec = ctx.prec
|
||||
try:
|
||||
A = ctx.matrix(A)
|
||||
ctx.prec += 2*A.rows
|
||||
res = ctx._exp_pade(A)
|
||||
finally:
|
||||
ctx.prec = prec
|
||||
return res
|
||||
A = ctx.matrix(A)
|
||||
prec = ctx.prec
|
||||
j = int(max(1, ctx.mag(ctx.mnorm(A,'inf'))))
|
||||
j += int(0.5*prec**0.5)
|
||||
try:
|
||||
ctx.prec += 10 + 2*j
|
||||
tol = +ctx.eps
|
||||
A = A/2**j
|
||||
T = A
|
||||
Y = A**0 + A
|
||||
k = 2
|
||||
while 1:
|
||||
T *= A * (1/ctx.mpf(k))
|
||||
if ctx.mnorm(T, 'inf') < tol:
|
||||
break
|
||||
Y += T
|
||||
k += 1
|
||||
for k in xrange(j):
|
||||
Y = Y*Y
|
||||
finally:
|
||||
ctx.prec = prec
|
||||
Y *= 1
|
||||
return Y
|
||||
|
||||
def cosm(ctx, A):
|
||||
r"""
|
||||
Gives the cosine of a square matrix `A`, defined in analogy
|
||||
with the matrix exponential.
|
||||
|
||||
Examples::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 15; mp.pretty = True
|
||||
>>> X = eye(3)
|
||||
>>> cosm(X)
|
||||
[0.54030230586814 0.0 0.0]
|
||||
[ 0.0 0.54030230586814 0.0]
|
||||
[ 0.0 0.0 0.54030230586814]
|
||||
>>> X = hilbert(3)
|
||||
>>> cosm(X)
|
||||
[ 0.424403834569555 -0.316643413047167 -0.221474945949293]
|
||||
[-0.316643413047167 0.820646708837824 -0.127183694770039]
|
||||
[-0.221474945949293 -0.127183694770039 0.909236687217541]
|
||||
>>> X = matrix([[1+j,-2],[0,-j]])
|
||||
>>> cosm(X)
|
||||
[(0.833730025131149 - 0.988897705762865j) (1.07485840848393 - 0.17192140544213j)]
|
||||
[ 0.0 (1.54308063481524 + 0.0j)]
|
||||
"""
|
||||
B = 0.5 * (ctx.expm(A*ctx.j) + ctx.expm(A*(-ctx.j)))
|
||||
if not sum(A.apply(ctx.im).apply(abs)):
|
||||
B = B.apply(ctx.re)
|
||||
return B
|
||||
|
||||
def sinm(ctx, A):
|
||||
r"""
|
||||
Gives the sine of a square matrix `A`, defined in analogy
|
||||
with the matrix exponential.
|
||||
|
||||
Examples::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 15; mp.pretty = True
|
||||
>>> X = eye(3)
|
||||
>>> sinm(X)
|
||||
[0.841470984807897 0.0 0.0]
|
||||
[ 0.0 0.841470984807897 0.0]
|
||||
[ 0.0 0.0 0.841470984807897]
|
||||
>>> X = hilbert(3)
|
||||
>>> sinm(X)
|
||||
[0.711608512150994 0.339783913247439 0.220742837314741]
|
||||
[0.339783913247439 0.244113865695532 0.187231271174372]
|
||||
[0.220742837314741 0.187231271174372 0.155816730769635]
|
||||
>>> X = matrix([[1+j,-2],[0,-j]])
|
||||
>>> sinm(X)
|
||||
[(1.29845758141598 + 0.634963914784736j) (-1.96751511930922 + 0.314700021761367j)]
|
||||
[ 0.0 (0.0 - 1.1752011936438j)]
|
||||
"""
|
||||
B = (-0.5j) * (ctx.expm(A*ctx.j) - ctx.expm(A*(-ctx.j)))
|
||||
if not sum(A.apply(ctx.im).apply(abs)):
|
||||
B = B.apply(ctx.re)
|
||||
return B
|
||||
|
||||
def _sqrtm_rot(ctx, A, _may_rotate):
|
||||
# If the iteration fails to converge, cheat by performing
|
||||
# a rotation by a complex number
|
||||
u = ctx.j**0.3
|
||||
return ctx.sqrtm(u*A, _may_rotate) / ctx.sqrt(u)
|
||||
|
||||
def sqrtm(ctx, A, _may_rotate=2):
|
||||
r"""
|
||||
Computes a square root of the square matrix `A`, i.e. returns
|
||||
a matrix `B = A^{1/2}` such that `B^2 = A`. The square root
|
||||
of a matrix, if it exists, is not unique.
|
||||
|
||||
**Examples**
|
||||
|
||||
Square roots of some simple matrices::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 15; mp.pretty = True
|
||||
>>> sqrtm([[1,0], [0,1]])
|
||||
[1.0 0.0]
|
||||
[0.0 1.0]
|
||||
>>> sqrtm([[0,0], [0,0]])
|
||||
[0.0 0.0]
|
||||
[0.0 0.0]
|
||||
>>> sqrtm([[2,0],[0,1]])
|
||||
[1.4142135623731 0.0]
|
||||
[ 0.0 1.0]
|
||||
>>> sqrtm([[1,1],[1,0]])
|
||||
[ (0.920442065259926 - 0.21728689675164j) (0.568864481005783 + 0.351577584254143j)]
|
||||
[(0.568864481005783 + 0.351577584254143j) (0.351577584254143 - 0.568864481005783j)]
|
||||
>>> sqrtm([[1,0],[0,1]])
|
||||
[1.0 0.0]
|
||||
[0.0 1.0]
|
||||
>>> sqrtm([[-1,0],[0,1]])
|
||||
[(0.0 - 1.0j) 0.0]
|
||||
[ 0.0 (1.0 + 0.0j)]
|
||||
>>> sqrtm([[j,0],[0,j]])
|
||||
[(0.707106781186547 + 0.707106781186547j) 0.0]
|
||||
[ 0.0 (0.707106781186547 + 0.707106781186547j)]
|
||||
|
||||
A square root of a rotation matrix, giving the corresponding
|
||||
half-angle rotation matrix::
|
||||
|
||||
>>> t1 = 0.75
|
||||
>>> t2 = t1 * 0.5
|
||||
>>> A1 = matrix([[cos(t1), -sin(t1)], [sin(t1), cos(t1)]])
|
||||
>>> A2 = matrix([[cos(t2), -sin(t2)], [sin(t2), cos(t2)]])
|
||||
>>> sqrtm(A1)
|
||||
[0.930507621912314 -0.366272529086048]
|
||||
[0.366272529086048 0.930507621912314]
|
||||
>>> A2
|
||||
[0.930507621912314 -0.366272529086048]
|
||||
[0.366272529086048 0.930507621912314]
|
||||
|
||||
The identity `(A^2)^{1/2} = A` does not necessarily hold::
|
||||
|
||||
>>> A = matrix([[4,1,4],[7,8,9],[10,2,11]])
|
||||
>>> sqrtm(A**2)
|
||||
[ 4.0 1.0 4.0]
|
||||
[ 7.0 8.0 9.0]
|
||||
[10.0 2.0 11.0]
|
||||
>>> sqrtm(A)**2
|
||||
[ 4.0 1.0 4.0]
|
||||
[ 7.0 8.0 9.0]
|
||||
[10.0 2.0 11.0]
|
||||
>>> A = matrix([[-4,1,4],[7,-8,9],[10,2,11]])
|
||||
>>> sqrtm(A**2)
|
||||
[ 7.43715112194995 -0.324127569985474 1.8481718827526]
|
||||
[-0.251549715716942 9.32699765900402 2.48221180985147]
|
||||
[ 4.11609388833616 0.775751877098258 13.017955697342]
|
||||
>>> chop(sqrtm(A)**2)
|
||||
[-4.0 1.0 4.0]
|
||||
[ 7.0 -8.0 9.0]
|
||||
[10.0 2.0 11.0]
|
||||
|
||||
For some matrices, a square root does not exist::
|
||||
|
||||
>>> sqrtm([[0,1], [0,0]])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ZeroDivisionError: matrix is numerically singular
|
||||
|
||||
Two examples from the documentation for Matlab's ``sqrtm``::
|
||||
|
||||
>>> mp.dps = 15; mp.pretty = True
|
||||
>>> sqrtm([[7,10],[15,22]])
|
||||
[1.56669890360128 1.74077655955698]
|
||||
[2.61116483933547 4.17786374293675]
|
||||
>>>
|
||||
>>> X = matrix(\
|
||||
... [[5,-4,1,0,0],
|
||||
... [-4,6,-4,1,0],
|
||||
... [1,-4,6,-4,1],
|
||||
... [0,1,-4,6,-4],
|
||||
... [0,0,1,-4,5]])
|
||||
>>> Y = matrix(\
|
||||
... [[2,-1,-0,-0,-0],
|
||||
... [-1,2,-1,0,-0],
|
||||
... [0,-1,2,-1,0],
|
||||
... [-0,0,-1,2,-1],
|
||||
... [-0,-0,-0,-1,2]])
|
||||
>>> mnorm(sqrtm(X) - Y)
|
||||
4.53155328326114e-19
|
||||
|
||||
"""
|
||||
A = ctx.matrix(A)
|
||||
# Trivial
|
||||
if A*0 == A:
|
||||
return A
|
||||
prec = ctx.prec
|
||||
if _may_rotate:
|
||||
d = ctx.det(A)
|
||||
if abs(ctx.im(d)) < 16*ctx.eps and ctx.re(d) < 0:
|
||||
return ctx._sqrtm_rot(A, _may_rotate-1)
|
||||
try:
|
||||
ctx.prec += 10
|
||||
tol = ctx.eps * 128
|
||||
Y = A
|
||||
Z = I = A**0
|
||||
k = 0
|
||||
# Denman-Beavers iteration
|
||||
while 1:
|
||||
Yprev = Y
|
||||
try:
|
||||
Y, Z = 0.5*(Y+ctx.inverse(Z)), 0.5*(Z+ctx.inverse(Y))
|
||||
except ZeroDivisionError:
|
||||
if _may_rotate:
|
||||
Y = ctx._sqrtm_rot(A, _may_rotate-1)
|
||||
break
|
||||
else:
|
||||
raise
|
||||
mag1 = ctx.mnorm(Y-Yprev, 'inf')
|
||||
mag2 = ctx.mnorm(Y, 'inf')
|
||||
if mag1 <= mag2*tol:
|
||||
break
|
||||
if _may_rotate and k > 6 and not mag1 < mag2 * 0.001:
|
||||
return ctx._sqrtm_rot(A, _may_rotate-1)
|
||||
k += 1
|
||||
if k > ctx.prec:
|
||||
raise ctx.NoConvergence
|
||||
finally:
|
||||
ctx.prec = prec
|
||||
Y *= 1
|
||||
return Y
|
||||
|
||||
def logm(ctx, A):
|
||||
r"""
|
||||
Computes a logarithm of the square matrix `A`, i.e. returns
|
||||
a matrix `B = \log(A)` such that `\exp(B) = A`. The logarithm
|
||||
of a matrix, if it exists, is not unique.
|
||||
|
||||
**Examples**
|
||||
|
||||
Logarithms of some simple matrices::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 15; mp.pretty = True
|
||||
>>> X = eye(3)
|
||||
>>> logm(X)
|
||||
[0.0 0.0 0.0]
|
||||
[0.0 0.0 0.0]
|
||||
[0.0 0.0 0.0]
|
||||
>>> logm(2*X)
|
||||
[0.693147180559945 0.0 0.0]
|
||||
[ 0.0 0.693147180559945 0.0]
|
||||
[ 0.0 0.0 0.693147180559945]
|
||||
>>> logm(expm(X))
|
||||
[1.0 0.0 0.0]
|
||||
[0.0 1.0 0.0]
|
||||
[0.0 0.0 1.0]
|
||||
|
||||
A logarithm of a complex matrix::
|
||||
|
||||
>>> X = matrix([[2+j, 1, 3], [1-j, 1-2*j, 1], [-4, -5, j]])
|
||||
>>> B = logm(X)
|
||||
>>> nprint(B)
|
||||
[ (0.808757 + 0.107759j) (2.20752 + 0.202762j) (1.07376 - 0.773874j)]
|
||||
[ (0.905709 - 0.107795j) (0.0287395 - 0.824993j) (0.111619 + 0.514272j)]
|
||||
[(-0.930151 + 0.399512j) (-2.06266 - 0.674397j) (0.791552 + 0.519839j)]
|
||||
>>> chop(expm(B))
|
||||
[(2.0 + 1.0j) 1.0 3.0]
|
||||
[(1.0 - 1.0j) (1.0 - 2.0j) 1.0]
|
||||
[ -4.0 -5.0 (0.0 + 1.0j)]
|
||||
|
||||
A matrix `X` close to the identity matrix, for which
|
||||
`\log(\exp(X)) = \exp(\log(X)) = X` holds::
|
||||
|
||||
>>> X = eye(3) + hilbert(3)/4
|
||||
>>> X
|
||||
[ 1.25 0.125 0.0833333333333333]
|
||||
[ 0.125 1.08333333333333 0.0625]
|
||||
[0.0833333333333333 0.0625 1.05]
|
||||
>>> logm(expm(X))
|
||||
[ 1.25 0.125 0.0833333333333333]
|
||||
[ 0.125 1.08333333333333 0.0625]
|
||||
[0.0833333333333333 0.0625 1.05]
|
||||
>>> expm(logm(X))
|
||||
[ 1.25 0.125 0.0833333333333333]
|
||||
[ 0.125 1.08333333333333 0.0625]
|
||||
[0.0833333333333333 0.0625 1.05]
|
||||
|
||||
A logarithm of a rotation matrix, giving back the angle of
|
||||
the rotation::
|
||||
|
||||
>>> t = 3.7
|
||||
>>> A = matrix([[cos(t),sin(t)],[-sin(t),cos(t)]])
|
||||
>>> chop(logm(A))
|
||||
[ 0.0 -2.58318530717959]
|
||||
[2.58318530717959 0.0]
|
||||
>>> (2*pi-t)
|
||||
2.58318530717959
|
||||
|
||||
For some matrices, a logarithm does not exist::
|
||||
|
||||
>>> logm([[1,0], [0,0]])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ZeroDivisionError: matrix is numerically singular
|
||||
|
||||
Logarithm of a matrix with large entries::
|
||||
|
||||
>>> logm(hilbert(3) * 10**20).apply(re)
|
||||
[ 45.5597513593433 1.27721006042799 0.317662687717978]
|
||||
[ 1.27721006042799 42.5222778973542 2.24003708791604]
|
||||
[0.317662687717978 2.24003708791604 42.395212822267]
|
||||
|
||||
"""
|
||||
A = ctx.matrix(A)
|
||||
prec = ctx.prec
|
||||
try:
|
||||
ctx.prec += 10
|
||||
tol = ctx.eps * 128
|
||||
I = A**0
|
||||
B = A
|
||||
n = 0
|
||||
while 1:
|
||||
B = ctx.sqrtm(B)
|
||||
n += 1
|
||||
if ctx.mnorm(B-I, 'inf') < 0.125:
|
||||
break
|
||||
T = X = B-I
|
||||
L = X*0
|
||||
k = 1
|
||||
while 1:
|
||||
if k & 1:
|
||||
L += T / k
|
||||
else:
|
||||
L -= T / k
|
||||
T *= X
|
||||
if ctx.mnorm(T, 'inf') < tol:
|
||||
break
|
||||
k += 1
|
||||
if k > ctx.prec:
|
||||
raise ctx.NoConvergence
|
||||
finally:
|
||||
ctx.prec = prec
|
||||
L *= 2**n
|
||||
return L
|
||||
|
||||
def powm(ctx, A, r):
|
||||
r"""
|
||||
Computes `A^r = \exp(A \log r)` for a matrix `A` and complex
|
||||
number `r`.
|
||||
|
||||
**Examples**
|
||||
|
||||
Powers and inverse powers of a matrix::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 15; mp.pretty = True
|
||||
>>> A = matrix([[4,1,4],[7,8,9],[10,2,11]])
|
||||
>>> powm(A, 2)
|
||||
[ 63.0 20.0 69.0]
|
||||
[174.0 89.0 199.0]
|
||||
[164.0 48.0 179.0]
|
||||
>>> chop(powm(powm(A, 4), 1/4.))
|
||||
[ 4.0 1.0 4.0]
|
||||
[ 7.0 8.0 9.0]
|
||||
[10.0 2.0 11.0]
|
||||
>>> powm(extraprec(20)(powm)(A, -4), -1/4.)
|
||||
[ 4.0 1.0 4.0]
|
||||
[ 7.0 8.0 9.0]
|
||||
[10.0 2.0 11.0]
|
||||
>>> chop(powm(powm(A, 1+0.5j), 1/(1+0.5j)))
|
||||
[ 4.0 1.0 4.0]
|
||||
[ 7.0 8.0 9.0]
|
||||
[10.0 2.0 11.0]
|
||||
>>> powm(extraprec(5)(powm)(A, -1.5), -1/(1.5))
|
||||
[ 4.0 1.0 4.0]
|
||||
[ 7.0 8.0 9.0]
|
||||
[10.0 2.0 11.0]
|
||||
|
||||
A Fibonacci-generating matrix::
|
||||
|
||||
>>> powm([[1,1],[1,0]], 10)
|
||||
[89.0 55.0]
|
||||
[55.0 34.0]
|
||||
>>> fib(10)
|
||||
55.0
|
||||
>>> powm([[1,1],[1,0]], 6.5)
|
||||
[(16.5166626964253 - 0.0121089837381789j) (10.2078589271083 + 0.0195927472575932j)]
|
||||
[(10.2078589271083 + 0.0195927472575932j) (6.30880376931698 - 0.0317017309957721j)]
|
||||
>>> (phi**6.5 - (1-phi)**6.5)/sqrt(5)
|
||||
(10.2078589271083 - 0.0195927472575932j)
|
||||
>>> powm([[1,1],[1,0]], 6.2)
|
||||
[ (14.3076953002666 - 0.008222855781077j) (8.81733464837593 + 0.0133048601383712j)]
|
||||
[(8.81733464837593 + 0.0133048601383712j) (5.49036065189071 - 0.0215277159194482j)]
|
||||
>>> (phi**6.2 - (1-phi)**6.2)/sqrt(5)
|
||||
(8.81733464837593 - 0.0133048601383712j)
|
||||
|
||||
"""
|
||||
A = ctx.matrix(A)
|
||||
r = ctx.convert(r)
|
||||
prec = ctx.prec
|
||||
try:
|
||||
ctx.prec += 10
|
||||
if ctx.isint(r):
|
||||
v = A ** int(r)
|
||||
elif ctx.isint(r*2):
|
||||
y = int(r*2)
|
||||
v = ctx.sqrtm(A) ** y
|
||||
else:
|
||||
v = ctx.expm(r*ctx.logm(A))
|
||||
finally:
|
||||
ctx.prec = prec
|
||||
v *= 1
|
||||
return v
|
||||
@@ -0,0 +1,877 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
##################################################################################################
|
||||
# module for the eigenvalue problem
|
||||
# Copyright 2013 Timo Hartmann (thartmann15 at gmail.com)
|
||||
#
|
||||
# todo:
|
||||
# - implement balancing
|
||||
# - agressive early deflation
|
||||
#
|
||||
##################################################################################################
|
||||
|
||||
"""
|
||||
The eigenvalue problem
|
||||
----------------------
|
||||
|
||||
This file contains routines for the eigenvalue problem.
|
||||
|
||||
high level routines:
|
||||
|
||||
hessenberg : reduction of a real or complex square matrix to upper Hessenberg form
|
||||
schur : reduction of a real or complex square matrix to upper Schur form
|
||||
eig : eigenvalues and eigenvectors of a real or complex square matrix
|
||||
|
||||
low level routines:
|
||||
|
||||
hessenberg_reduce_0 : reduction of a real or complex square matrix to upper Hessenberg form
|
||||
hessenberg_reduce_1 : auxiliary routine to hessenberg_reduce_0
|
||||
qr_step : a single implicitly shifted QR step for an upper Hessenberg matrix
|
||||
hessenberg_qr : Schur decomposition of an upper Hessenberg matrix
|
||||
eig_tr_r : right eigenvectors of an upper triangular matrix
|
||||
eig_tr_l : left eigenvectors of an upper triangular matrix
|
||||
"""
|
||||
|
||||
from ..libmp.backend import xrange
|
||||
|
||||
class Eigen(object):
|
||||
pass
|
||||
|
||||
def defun(f):
|
||||
setattr(Eigen, f.__name__, f)
|
||||
return f
|
||||
|
||||
def hessenberg_reduce_0(ctx, A, T):
|
||||
"""
|
||||
This routine computes the (upper) Hessenberg decomposition of a square matrix A.
|
||||
Given A, an unitary matrix Q is calculated such that
|
||||
|
||||
Q' A Q = H and Q' Q = Q Q' = 1
|
||||
|
||||
where H is an upper Hessenberg matrix, meaning that it only contains zeros
|
||||
below the first subdiagonal. Here ' denotes the hermitian transpose (i.e.
|
||||
transposition and conjugation).
|
||||
|
||||
parameters:
|
||||
A (input/output) On input, A contains the square matrix A of
|
||||
dimension (n,n). On output, A contains a compressed representation
|
||||
of Q and H.
|
||||
T (output) An array of length n containing the first elements of
|
||||
the Householder reflectors.
|
||||
"""
|
||||
|
||||
# internally we work with householder reflections from the right.
|
||||
# let u be a row vector (i.e. u[i]=A[i,:i]). then
|
||||
# Q is build up by reflectors of the type (1-v'v) where v is a suitable
|
||||
# modification of u. these reflectors are applyed to A from the right.
|
||||
# because we work with reflectors from the right we have to start with
|
||||
# the bottom row of A and work then upwards (this corresponds to
|
||||
# some kind of RQ decomposition).
|
||||
# the first part of the vectors v (i.e. A[i,:(i-1)]) are stored as row vectors
|
||||
# in the lower left part of A (excluding the diagonal and subdiagonal).
|
||||
# the last entry of v is stored in T.
|
||||
# the upper right part of A (including diagonal and subdiagonal) becomes H.
|
||||
|
||||
|
||||
n = A.rows
|
||||
if n <= 2: return
|
||||
|
||||
for i in xrange(n-1, 1, -1):
|
||||
|
||||
# scale the vector
|
||||
|
||||
scale = 0
|
||||
for k in xrange(0, i):
|
||||
scale += abs(ctx.re(A[i,k])) + abs(ctx.im(A[i,k]))
|
||||
|
||||
scale_inv = 0
|
||||
if scale != 0:
|
||||
scale_inv = 1 / scale
|
||||
|
||||
if scale == 0 or ctx.isinf(scale_inv):
|
||||
# sadly there are floating point numbers not equal to zero whose reciprocal is infinity
|
||||
T[i] = 0
|
||||
A[i,i-1] = 0
|
||||
continue
|
||||
|
||||
# calculate parameters for housholder transformation
|
||||
|
||||
H = 0
|
||||
for k in xrange(0, i):
|
||||
A[i,k] *= scale_inv
|
||||
rr = ctx.re(A[i,k])
|
||||
ii = ctx.im(A[i,k])
|
||||
H += rr * rr + ii * ii
|
||||
|
||||
F = A[i,i-1]
|
||||
f = abs(F)
|
||||
G = ctx.sqrt(H)
|
||||
A[i,i-1] = - G * scale
|
||||
|
||||
if f == 0:
|
||||
T[i] = G
|
||||
else:
|
||||
ff = F / f
|
||||
T[i] = F + G * ff
|
||||
A[i,i-1] *= ff
|
||||
|
||||
H += G * f
|
||||
H = 1 / ctx.sqrt(H)
|
||||
|
||||
T[i] *= H
|
||||
for k in xrange(0, i - 1):
|
||||
A[i,k] *= H
|
||||
|
||||
for j in xrange(0, i):
|
||||
# apply housholder transformation (from right)
|
||||
|
||||
G = ctx.conj(T[i]) * A[j,i-1]
|
||||
for k in xrange(0, i-1):
|
||||
G += ctx.conj(A[i,k]) * A[j,k]
|
||||
|
||||
A[j,i-1] -= G * T[i]
|
||||
for k in xrange(0, i-1):
|
||||
A[j,k] -= G * A[i,k]
|
||||
|
||||
for j in xrange(0, n):
|
||||
# apply housholder transformation (from left)
|
||||
|
||||
G = T[i] * A[i-1,j]
|
||||
for k in xrange(0, i-1):
|
||||
G += A[i,k] * A[k,j]
|
||||
|
||||
A[i-1,j] -= G * ctx.conj(T[i])
|
||||
for k in xrange(0, i-1):
|
||||
A[k,j] -= G * ctx.conj(A[i,k])
|
||||
|
||||
|
||||
|
||||
def hessenberg_reduce_1(ctx, A, T):
|
||||
"""
|
||||
This routine forms the unitary matrix Q described in hessenberg_reduce_0.
|
||||
|
||||
parameters:
|
||||
A (input/output) On input, A is the same matrix as delivered by
|
||||
hessenberg_reduce_0. On output, A is set to Q.
|
||||
|
||||
T (input) On input, T is the same array as delivered by hessenberg_reduce_0.
|
||||
"""
|
||||
|
||||
n = A.rows
|
||||
|
||||
if n == 1:
|
||||
A[0,0] = 1
|
||||
return
|
||||
|
||||
A[0,0] = A[1,1] = 1
|
||||
A[0,1] = A[1,0] = 0
|
||||
|
||||
for i in xrange(2, n):
|
||||
if T[i] != 0:
|
||||
|
||||
for j in xrange(0, i):
|
||||
G = T[i] * A[i-1,j]
|
||||
for k in xrange(0, i-1):
|
||||
G += A[i,k] * A[k,j]
|
||||
|
||||
A[i-1,j] -= G * ctx.conj(T[i])
|
||||
for k in xrange(0, i-1):
|
||||
A[k,j] -= G * ctx.conj(A[i,k])
|
||||
|
||||
A[i,i] = 1
|
||||
for j in xrange(0, i):
|
||||
A[j,i] = A[i,j] = 0
|
||||
|
||||
|
||||
|
||||
@defun
|
||||
def hessenberg(ctx, A, overwrite_a = False):
|
||||
"""
|
||||
This routine computes the Hessenberg decomposition of a square matrix A.
|
||||
Given A, an unitary matrix Q is determined such that
|
||||
|
||||
Q' A Q = H and Q' Q = Q Q' = 1
|
||||
|
||||
where H is an upper right Hessenberg matrix. Here ' denotes the hermitian
|
||||
transpose (i.e. transposition and conjugation).
|
||||
|
||||
input:
|
||||
A : a real or complex square matrix
|
||||
overwrite_a : if true, allows modification of A which may improve
|
||||
performance. if false, A is not modified.
|
||||
|
||||
output:
|
||||
Q : an unitary matrix
|
||||
H : an upper right Hessenberg matrix
|
||||
|
||||
example:
|
||||
>>> from mpmath import mp
|
||||
>>> A = mp.matrix([[3, -1, 2], [2, 5, -5], [-2, -3, 7]])
|
||||
>>> Q, H = mp.hessenberg(A)
|
||||
>>> mp.nprint(H, 3) # doctest:+SKIP
|
||||
[ 3.15 2.23 4.44]
|
||||
[-0.769 4.85 3.05]
|
||||
[ 0.0 3.61 7.0]
|
||||
>>> print(mp.chop(A - Q * H * Q.transpose_conj()))
|
||||
[0.0 0.0 0.0]
|
||||
[0.0 0.0 0.0]
|
||||
[0.0 0.0 0.0]
|
||||
|
||||
return value: (Q, H)
|
||||
"""
|
||||
|
||||
n = A.rows
|
||||
|
||||
if n == 1:
|
||||
return (ctx.matrix([[1]]), A)
|
||||
|
||||
if not overwrite_a:
|
||||
A = A.copy()
|
||||
|
||||
T = ctx.matrix(n, 1)
|
||||
|
||||
hessenberg_reduce_0(ctx, A, T)
|
||||
Q = A.copy()
|
||||
hessenberg_reduce_1(ctx, Q, T)
|
||||
|
||||
for x in xrange(n):
|
||||
for y in xrange(x+2, n):
|
||||
A[y,x] = 0
|
||||
|
||||
return Q, A
|
||||
|
||||
|
||||
###########################################################################
|
||||
|
||||
|
||||
def qr_step(ctx, n0, n1, A, Q, shift):
|
||||
"""
|
||||
This subroutine executes a single implicitly shifted QR step applied to an
|
||||
upper Hessenberg matrix A. Given A and shift as input, first an QR
|
||||
decomposition is calculated:
|
||||
|
||||
Q R = A - shift * 1 .
|
||||
|
||||
The output is then following matrix:
|
||||
|
||||
R Q + shift * 1
|
||||
|
||||
parameters:
|
||||
n0, n1 (input) Two integers which specify the submatrix A[n0:n1,n0:n1]
|
||||
on which this subroutine operators. The subdiagonal elements
|
||||
to the left and below this submatrix must be deflated (i.e. zero).
|
||||
following restriction is imposed: n1>=n0+2
|
||||
A (input/output) On input, A is an upper Hessenberg matrix.
|
||||
On output, A is replaced by "R Q + shift * 1"
|
||||
Q (input/output) The parameter Q is multiplied by the unitary matrix
|
||||
Q arising from the QR decomposition. Q can also be false, in which
|
||||
case the unitary matrix Q is not computated.
|
||||
shift (input) a complex number specifying the shift. idealy close to an
|
||||
eigenvalue of the bottemmost part of the submatrix A[n0:n1,n0:n1].
|
||||
|
||||
references:
|
||||
Stoer, Bulirsch - Introduction to Numerical Analysis.
|
||||
Kresser : Numerical Methods for General and Structured Eigenvalue Problems
|
||||
"""
|
||||
|
||||
# implicitly shifted and bulge chasing is explained at p.398/399 in "Stoer, Bulirsch - Introduction to Numerical Analysis"
|
||||
# for bulge chasing see also "Watkins - The Matrix Eigenvalue Problem" sec.4.5,p.173
|
||||
|
||||
# the Givens rotation we used is determined as follows: let c,s be two complex
|
||||
# numbers. then we have following relation:
|
||||
#
|
||||
# v = sqrt(|c|^2 + |s|^2)
|
||||
#
|
||||
# 1/v [ c~ s~] [c] = [v]
|
||||
# [-s c ] [s] [0]
|
||||
#
|
||||
# the matrix on the left is our Givens rotation.
|
||||
|
||||
n = A.rows
|
||||
|
||||
# first step
|
||||
|
||||
# calculate givens rotation
|
||||
c = A[n0 ,n0] - shift
|
||||
s = A[n0+1,n0]
|
||||
|
||||
v = ctx.hypot(ctx.hypot(ctx.re(c), ctx.im(c)), ctx.hypot(ctx.re(s), ctx.im(s)))
|
||||
|
||||
if v == 0:
|
||||
v = 1
|
||||
c = 1
|
||||
s = 0
|
||||
else:
|
||||
c /= v
|
||||
s /= v
|
||||
|
||||
cc = ctx.conj(c)
|
||||
cs = ctx.conj(s)
|
||||
|
||||
for k in xrange(n0, n):
|
||||
# apply givens rotation from the left
|
||||
x = A[n0 ,k]
|
||||
y = A[n0+1,k]
|
||||
A[n0 ,k] = cc * x + cs * y
|
||||
A[n0+1,k] = c * y - s * x
|
||||
|
||||
for k in xrange(min(n1, n0+3)):
|
||||
# apply givens rotation from the right
|
||||
x = A[k,n0 ]
|
||||
y = A[k,n0+1]
|
||||
A[k,n0 ] = c * x + s * y
|
||||
A[k,n0+1] = cc * y - cs * x
|
||||
|
||||
if not isinstance(Q, bool):
|
||||
for k in xrange(n):
|
||||
# eigenvectors
|
||||
x = Q[k,n0 ]
|
||||
y = Q[k,n0+1]
|
||||
Q[k,n0 ] = c * x + s * y
|
||||
Q[k,n0+1] = cc * y - cs * x
|
||||
|
||||
# chase the bulge
|
||||
|
||||
for j in xrange(n0, n1 - 2):
|
||||
# calculate givens rotation
|
||||
|
||||
c = A[j+1,j]
|
||||
s = A[j+2,j]
|
||||
|
||||
v = ctx.hypot(ctx.hypot(ctx.re(c), ctx.im(c)), ctx.hypot(ctx.re(s), ctx.im(s)))
|
||||
|
||||
if v == 0:
|
||||
A[j+1,j] = 0
|
||||
v = 1
|
||||
c = 1
|
||||
s = 0
|
||||
else:
|
||||
A[j+1,j] = v
|
||||
c /= v
|
||||
s /= v
|
||||
|
||||
A[j+2,j] = 0
|
||||
|
||||
cc = ctx.conj(c)
|
||||
cs = ctx.conj(s)
|
||||
|
||||
for k in xrange(j+1, n):
|
||||
# apply givens rotation from the left
|
||||
x = A[j+1,k]
|
||||
y = A[j+2,k]
|
||||
A[j+1,k] = cc * x + cs * y
|
||||
A[j+2,k] = c * y - s * x
|
||||
|
||||
for k in xrange(0, min(n1, j+4)):
|
||||
# apply givens rotation from the right
|
||||
x = A[k,j+1]
|
||||
y = A[k,j+2]
|
||||
A[k,j+1] = c * x + s * y
|
||||
A[k,j+2] = cc * y - cs * x
|
||||
|
||||
if not isinstance(Q, bool):
|
||||
for k in xrange(0, n):
|
||||
# eigenvectors
|
||||
x = Q[k,j+1]
|
||||
y = Q[k,j+2]
|
||||
Q[k,j+1] = c * x + s * y
|
||||
Q[k,j+2] = cc * y - cs * x
|
||||
|
||||
|
||||
|
||||
def hessenberg_qr(ctx, A, Q):
|
||||
"""
|
||||
This routine computes the Schur decomposition of an upper Hessenberg matrix A.
|
||||
Given A, an unitary matrix Q is determined such that
|
||||
|
||||
Q' A Q = R and Q' Q = Q Q' = 1
|
||||
|
||||
where R is an upper right triangular matrix. Here ' denotes the hermitian
|
||||
transpose (i.e. transposition and conjugation).
|
||||
|
||||
parameters:
|
||||
A (input/output) On input, A contains an upper Hessenberg matrix.
|
||||
On output, A is replace by the upper right triangluar matrix R.
|
||||
|
||||
Q (input/output) The parameter Q is multiplied by the unitary
|
||||
matrix Q arising from the Schur decomposition. Q can also be
|
||||
false, in which case the unitary matrix Q is not computated.
|
||||
"""
|
||||
|
||||
n = A.rows
|
||||
|
||||
norm = 0
|
||||
for x in xrange(n):
|
||||
for y in xrange(min(x+2, n)):
|
||||
norm += ctx.re(A[y,x]) ** 2 + ctx.im(A[y,x]) ** 2
|
||||
norm = ctx.sqrt(norm) / n
|
||||
|
||||
if norm == 0:
|
||||
return
|
||||
|
||||
n0 = 0
|
||||
n1 = n
|
||||
|
||||
eps = ctx.eps / (100 * n)
|
||||
maxits = ctx.dps * 4
|
||||
|
||||
its = totalits = 0
|
||||
|
||||
while 1:
|
||||
# kressner p.32 algo 3
|
||||
# the active submatrix is A[n0:n1,n0:n1]
|
||||
|
||||
k = n0
|
||||
|
||||
while k + 1 < n1:
|
||||
s = abs(ctx.re(A[k,k])) + abs(ctx.im(A[k,k])) + abs(ctx.re(A[k+1,k+1])) + abs(ctx.im(A[k+1,k+1]))
|
||||
if s < eps * norm:
|
||||
s = norm
|
||||
if abs(A[k+1,k]) < eps * s:
|
||||
break
|
||||
k += 1
|
||||
|
||||
if k + 1 < n1:
|
||||
# deflation found at position (k+1, k)
|
||||
|
||||
A[k+1,k] = 0
|
||||
n0 = k + 1
|
||||
|
||||
its = 0
|
||||
|
||||
if n0 + 1 >= n1:
|
||||
# block of size at most two has converged
|
||||
n0 = 0
|
||||
n1 = k + 1
|
||||
if n1 < 2:
|
||||
# QR algorithm has converged
|
||||
return
|
||||
else:
|
||||
if (its % 30) == 10:
|
||||
# exceptional shift
|
||||
shift = A[n1-1,n1-2]
|
||||
elif (its % 30) == 20:
|
||||
# exceptional shift
|
||||
shift = abs(A[n1-1,n1-2])
|
||||
elif (its % 30) == 29:
|
||||
# exceptional shift
|
||||
shift = norm
|
||||
else:
|
||||
# A = [ a b ] det(x-A)=x*x-x*tr(A)+det(A)
|
||||
# [ c d ]
|
||||
#
|
||||
# eigenvalues bad: (tr(A)+sqrt((tr(A))**2-4*det(A)))/2
|
||||
# bad because of cancellation if |c| is small and |a-d| is small, too.
|
||||
#
|
||||
# eigenvalues good: (a+d+sqrt((a-d)**2+4*b*c))/2
|
||||
|
||||
t = A[n1-2,n1-2] + A[n1-1,n1-1]
|
||||
s = (A[n1-1,n1-1] - A[n1-2,n1-2]) ** 2 + 4 * A[n1-1,n1-2] * A[n1-2,n1-1]
|
||||
if ctx.re(s) > 0:
|
||||
s = ctx.sqrt(s)
|
||||
else:
|
||||
s = ctx.sqrt(-s) * 1j
|
||||
a = (t + s) / 2
|
||||
b = (t - s) / 2
|
||||
if abs(A[n1-1,n1-1] - a) > abs(A[n1-1,n1-1] - b):
|
||||
shift = b
|
||||
else:
|
||||
shift = a
|
||||
|
||||
its += 1
|
||||
totalits += 1
|
||||
|
||||
qr_step(ctx, n0, n1, A, Q, shift)
|
||||
|
||||
if its > maxits:
|
||||
raise RuntimeError("qr: failed to converge after %d steps" % its)
|
||||
|
||||
|
||||
@defun
|
||||
def schur(ctx, A, overwrite_a = False):
|
||||
"""
|
||||
This routine computes the Schur decomposition of a square matrix A.
|
||||
Given A, an unitary matrix Q is determined such that
|
||||
|
||||
Q' A Q = R and Q' Q = Q Q' = 1
|
||||
|
||||
where R is an upper right triangular matrix. Here ' denotes the
|
||||
hermitian transpose (i.e. transposition and conjugation).
|
||||
|
||||
input:
|
||||
A : a real or complex square matrix
|
||||
overwrite_a : if true, allows modification of A which may improve
|
||||
performance. if false, A is not modified.
|
||||
|
||||
output:
|
||||
Q : an unitary matrix
|
||||
R : an upper right triangular matrix
|
||||
|
||||
return value: (Q, R)
|
||||
|
||||
example:
|
||||
>>> from mpmath import mp
|
||||
>>> A = mp.matrix([[3, -1, 2], [2, 5, -5], [-2, -3, 7]])
|
||||
>>> Q, R = mp.schur(A)
|
||||
>>> mp.nprint(R, 3) # doctest:+SKIP
|
||||
[2.0 0.417 -2.53]
|
||||
[0.0 4.0 -4.74]
|
||||
[0.0 0.0 9.0]
|
||||
>>> print(mp.chop(A - Q * R * Q.transpose_conj()))
|
||||
[0.0 0.0 0.0]
|
||||
[0.0 0.0 0.0]
|
||||
[0.0 0.0 0.0]
|
||||
|
||||
warning: The Schur decomposition is not unique.
|
||||
"""
|
||||
|
||||
n = A.rows
|
||||
|
||||
if n == 1:
|
||||
return (ctx.matrix([[1]]), A)
|
||||
|
||||
if not overwrite_a:
|
||||
A = A.copy()
|
||||
|
||||
T = ctx.matrix(n, 1)
|
||||
|
||||
hessenberg_reduce_0(ctx, A, T)
|
||||
Q = A.copy()
|
||||
hessenberg_reduce_1(ctx, Q, T)
|
||||
|
||||
for x in xrange(n):
|
||||
for y in xrange(x + 2, n):
|
||||
A[y,x] = 0
|
||||
|
||||
hessenberg_qr(ctx, A, Q)
|
||||
|
||||
return Q, A
|
||||
|
||||
|
||||
def eig_tr_r(ctx, A):
|
||||
"""
|
||||
This routine calculates the right eigenvectors of an upper right triangular matrix.
|
||||
|
||||
input:
|
||||
A an upper right triangular matrix
|
||||
|
||||
output:
|
||||
ER a matrix whose columns form the right eigenvectors of A
|
||||
|
||||
return value: ER
|
||||
"""
|
||||
|
||||
# this subroutine is inspired by the lapack routines ctrevc.f,clatrs.f
|
||||
|
||||
n = A.rows
|
||||
|
||||
ER = ctx.eye(n)
|
||||
|
||||
eps = ctx.eps
|
||||
|
||||
unfl = ctx.ldexp(ctx.one, -ctx.prec * 30)
|
||||
# since mpmath effectively has no limits on the exponent, we simply scale doubles up
|
||||
# original double has prec*20
|
||||
|
||||
smlnum = unfl * (n / eps)
|
||||
simin = 1 / ctx.sqrt(eps)
|
||||
|
||||
rmax = 1
|
||||
|
||||
for i in xrange(1, n):
|
||||
s = A[i,i]
|
||||
|
||||
smin = max(eps * abs(s), smlnum)
|
||||
|
||||
for j in xrange(i - 1, -1, -1):
|
||||
|
||||
r = 0
|
||||
for k in xrange(j + 1, i + 1):
|
||||
r += A[j,k] * ER[k,i]
|
||||
|
||||
t = A[j,j] - s
|
||||
if abs(t) < smin:
|
||||
t = smin
|
||||
|
||||
r = -r / t
|
||||
ER[j,i] = r
|
||||
|
||||
rmax = max(rmax, abs(r))
|
||||
if rmax > simin:
|
||||
for k in xrange(j, i+1):
|
||||
ER[k,i] /= rmax
|
||||
rmax = 1
|
||||
|
||||
if rmax != 1:
|
||||
for k in xrange(0, i + 1):
|
||||
ER[k,i] /= rmax
|
||||
|
||||
return ER
|
||||
|
||||
def eig_tr_l(ctx, A):
|
||||
"""
|
||||
This routine calculates the left eigenvectors of an upper right triangular matrix.
|
||||
|
||||
input:
|
||||
A an upper right triangular matrix
|
||||
|
||||
output:
|
||||
EL a matrix whose rows form the left eigenvectors of A
|
||||
|
||||
return value: EL
|
||||
"""
|
||||
|
||||
n = A.rows
|
||||
|
||||
EL = ctx.eye(n)
|
||||
|
||||
eps = ctx.eps
|
||||
|
||||
unfl = ctx.ldexp(ctx.one, -ctx.prec * 30)
|
||||
# since mpmath effectively has no limits on the exponent, we simply scale doubles up
|
||||
# original double has prec*20
|
||||
|
||||
smlnum = unfl * (n / eps)
|
||||
simin = 1 / ctx.sqrt(eps)
|
||||
|
||||
rmax = 1
|
||||
|
||||
for i in xrange(0, n - 1):
|
||||
s = A[i,i]
|
||||
|
||||
smin = max(eps * abs(s), smlnum)
|
||||
|
||||
for j in xrange(i + 1, n):
|
||||
|
||||
r = 0
|
||||
for k in xrange(i, j):
|
||||
r += EL[i,k] * A[k,j]
|
||||
|
||||
t = A[j,j] - s
|
||||
if abs(t) < smin:
|
||||
t = smin
|
||||
|
||||
r = -r / t
|
||||
EL[i,j] = r
|
||||
|
||||
rmax = max(rmax, abs(r))
|
||||
if rmax > simin:
|
||||
for k in xrange(i, j + 1):
|
||||
EL[i,k] /= rmax
|
||||
rmax = 1
|
||||
|
||||
if rmax != 1:
|
||||
for k in xrange(i, n):
|
||||
EL[i,k] /= rmax
|
||||
|
||||
return EL
|
||||
|
||||
@defun
|
||||
def eig(ctx, A, left = False, right = True, overwrite_a = False):
|
||||
"""
|
||||
This routine computes the eigenvalues and optionally the left and right
|
||||
eigenvectors of a square matrix A. Given A, a vector E and matrices ER
|
||||
and EL are calculated such that
|
||||
|
||||
A ER[:,i] = E[i] ER[:,i]
|
||||
EL[i,:] A = EL[i,:] E[i]
|
||||
|
||||
E contains the eigenvalues of A. The columns of ER contain the right eigenvectors
|
||||
of A whereas the rows of EL contain the left eigenvectors.
|
||||
|
||||
|
||||
input:
|
||||
A : a real or complex square matrix of shape (n, n)
|
||||
left : if true, the left eigenvectors are calculated.
|
||||
right : if true, the right eigenvectors are calculated.
|
||||
overwrite_a : if true, allows modification of A which may improve
|
||||
performance. if false, A is not modified.
|
||||
|
||||
output:
|
||||
E : a list of length n containing the eigenvalues of A.
|
||||
ER : a matrix whose columns contain the right eigenvectors of A.
|
||||
EL : a matrix whose rows contain the left eigenvectors of A.
|
||||
|
||||
return values:
|
||||
E if left and right are both false.
|
||||
(E, ER) if right is true and left is false.
|
||||
(E, EL) if left is true and right is false.
|
||||
(E, EL, ER) if left and right are true.
|
||||
|
||||
|
||||
examples:
|
||||
>>> from mpmath import mp
|
||||
>>> A = mp.matrix([[3, -1, 2], [2, 5, -5], [-2, -3, 7]])
|
||||
>>> E, ER = mp.eig(A)
|
||||
>>> print(mp.chop(A * ER[:,0] - E[0] * ER[:,0]))
|
||||
[0.0]
|
||||
[0.0]
|
||||
[0.0]
|
||||
|
||||
>>> E, EL, ER = mp.eig(A,left = True, right = True)
|
||||
>>> E, EL, ER = mp.eig_sort(E, EL, ER)
|
||||
>>> mp.nprint(E)
|
||||
[2.0, 4.0, 9.0]
|
||||
>>> print(mp.chop(A * ER[:,0] - E[0] * ER[:,0]))
|
||||
[0.0]
|
||||
[0.0]
|
||||
[0.0]
|
||||
>>> print(mp.chop( EL[0,:] * A - EL[0,:] * E[0]))
|
||||
[0.0 0.0 0.0]
|
||||
|
||||
warning:
|
||||
- If there are multiple eigenvalues, the eigenvectors do not necessarily
|
||||
span the whole vectorspace, i.e. ER and EL may have not full rank.
|
||||
Furthermore in that case the eigenvectors are numerical ill-conditioned.
|
||||
- In the general case the eigenvalues have no natural order.
|
||||
|
||||
see also:
|
||||
- eigh (or eigsy, eighe) for the symmetric eigenvalue problem.
|
||||
- eig_sort for sorting of eigenvalues and eigenvectors
|
||||
"""
|
||||
|
||||
n = A.rows
|
||||
|
||||
if n == 1:
|
||||
if left and (not right):
|
||||
return ([A[0]], ctx.matrix([[1]]))
|
||||
|
||||
if right and (not left):
|
||||
return ([A[0]], ctx.matrix([[1]]))
|
||||
|
||||
return ([A[0]], ctx.matrix([[1]]), ctx.matrix([[1]]))
|
||||
|
||||
if not overwrite_a:
|
||||
A = A.copy()
|
||||
|
||||
T = ctx.zeros(n, 1)
|
||||
|
||||
hessenberg_reduce_0(ctx, A, T)
|
||||
|
||||
if left or right:
|
||||
Q = A.copy()
|
||||
hessenberg_reduce_1(ctx, Q, T)
|
||||
else:
|
||||
Q = False
|
||||
|
||||
for x in xrange(n):
|
||||
for y in xrange(x + 2, n):
|
||||
A[y,x] = 0
|
||||
|
||||
hessenberg_qr(ctx, A, Q)
|
||||
|
||||
E = [0 for i in xrange(n)]
|
||||
for i in xrange(n):
|
||||
E[i] = A[i,i]
|
||||
|
||||
if not (left or right):
|
||||
return E
|
||||
|
||||
if left:
|
||||
EL = eig_tr_l(ctx, A)
|
||||
EL = EL * Q.transpose_conj()
|
||||
|
||||
if right:
|
||||
ER = eig_tr_r(ctx, A)
|
||||
ER = Q * ER
|
||||
|
||||
if left and (not right):
|
||||
return (E, EL)
|
||||
|
||||
if right and (not left):
|
||||
return (E, ER)
|
||||
|
||||
return (E, EL, ER)
|
||||
|
||||
@defun
|
||||
def eig_sort(ctx, E, EL = False, ER = False, f = "real"):
|
||||
"""
|
||||
This routine sorts the eigenvalues and eigenvectors delivered by ``eig``.
|
||||
|
||||
parameters:
|
||||
E : the eigenvalues as delivered by eig
|
||||
EL : the left eigenvectors as delivered by eig, or false
|
||||
ER : the right eigenvectors as delivered by eig, or false
|
||||
f : either a string ("real" sort by increasing real part, "imag" sort by
|
||||
increasing imag part, "abs" sort by absolute value) or a function
|
||||
mapping complexs to the reals, i.e. ``f = lambda x: -mp.re(x) ``
|
||||
would sort the eigenvalues by decreasing real part.
|
||||
|
||||
return values:
|
||||
E if EL and ER are both false.
|
||||
(E, ER) if ER is not false and left is false.
|
||||
(E, EL) if EL is not false and right is false.
|
||||
(E, EL, ER) if EL and ER are not false.
|
||||
|
||||
example:
|
||||
>>> from mpmath import mp
|
||||
>>> A = mp.matrix([[3, -1, 2], [2, 5, -5], [-2, -3, 7]])
|
||||
>>> E, EL, ER = mp.eig(A,left = True, right = True)
|
||||
>>> E, EL, ER = mp.eig_sort(E, EL, ER)
|
||||
>>> mp.nprint(E)
|
||||
[2.0, 4.0, 9.0]
|
||||
>>> E, EL, ER = mp.eig_sort(E, EL, ER,f = lambda x: -mp.re(x))
|
||||
>>> mp.nprint(E)
|
||||
[9.0, 4.0, 2.0]
|
||||
>>> print(mp.chop(A * ER[:,0] - E[0] * ER[:,0]))
|
||||
[0.0]
|
||||
[0.0]
|
||||
[0.0]
|
||||
>>> print(mp.chop( EL[0,:] * A - EL[0,:] * E[0]))
|
||||
[0.0 0.0 0.0]
|
||||
"""
|
||||
|
||||
if isinstance(f, str):
|
||||
if f == "real":
|
||||
f = ctx.re
|
||||
elif f == "imag":
|
||||
f = ctx.im
|
||||
elif f == "abs":
|
||||
f = abs
|
||||
else:
|
||||
raise RuntimeError("unknown function %s" % f)
|
||||
|
||||
n = len(E)
|
||||
|
||||
# Sort eigenvalues (bubble-sort)
|
||||
|
||||
for i in xrange(n):
|
||||
imax = i
|
||||
s = f(E[i]) # s is the current maximal element
|
||||
|
||||
for j in xrange(i + 1, n):
|
||||
c = f(E[j])
|
||||
if c < s:
|
||||
s = c
|
||||
imax = j
|
||||
|
||||
if imax != i:
|
||||
# swap eigenvalues
|
||||
|
||||
z = E[i]
|
||||
E[i] = E[imax]
|
||||
E[imax] = z
|
||||
|
||||
if not isinstance(EL, bool):
|
||||
for j in xrange(n):
|
||||
z = EL[i,j]
|
||||
EL[i,j] = EL[imax,j]
|
||||
EL[imax,j] = z
|
||||
|
||||
if not isinstance(ER, bool):
|
||||
for j in xrange(n):
|
||||
z = ER[j,i]
|
||||
ER[j,i] = ER[j,imax]
|
||||
ER[j,imax] = z
|
||||
|
||||
if isinstance(EL, bool) and isinstance(ER, bool):
|
||||
return E
|
||||
|
||||
if isinstance(EL, bool) and not(isinstance(ER, bool)):
|
||||
return (E, ER)
|
||||
|
||||
if isinstance(ER, bool) and not(isinstance(EL, bool)):
|
||||
return (E, EL)
|
||||
|
||||
return (E, EL, ER)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,790 @@
|
||||
"""
|
||||
Linear algebra
|
||||
--------------
|
||||
|
||||
Linear equations
|
||||
................
|
||||
|
||||
Basic linear algebra is implemented; you can for example solve the linear
|
||||
equation system::
|
||||
|
||||
x + 2*y = -10
|
||||
3*x + 4*y = 10
|
||||
|
||||
using ``lu_solve``::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.pretty = False
|
||||
>>> A = matrix([[1, 2], [3, 4]])
|
||||
>>> b = matrix([-10, 10])
|
||||
>>> x = lu_solve(A, b)
|
||||
>>> x
|
||||
matrix(
|
||||
[['30.0'],
|
||||
['-20.0']])
|
||||
|
||||
If you don't trust the result, use ``residual`` to calculate the residual ||A*x-b||::
|
||||
|
||||
>>> residual(A, x, b)
|
||||
matrix(
|
||||
[['3.46944695195361e-18'],
|
||||
['3.46944695195361e-18']])
|
||||
>>> str(eps)
|
||||
'2.22044604925031e-16'
|
||||
|
||||
As you can see, the solution is quite accurate. The error is caused by the
|
||||
inaccuracy of the internal floating point arithmetic. Though, it's even smaller
|
||||
than the current machine epsilon, which basically means you can trust the
|
||||
result.
|
||||
|
||||
If you need more speed, use NumPy, or ``fp.lu_solve`` for a floating-point computation.
|
||||
|
||||
>>> fp.lu_solve(A, b) # doctest: +ELLIPSIS
|
||||
matrix(...)
|
||||
|
||||
``lu_solve`` accepts overdetermined systems. It is usually not possible to solve
|
||||
such systems, so the residual is minimized instead. Internally this is done
|
||||
using Cholesky decomposition to compute a least squares approximation. This means
|
||||
that that ``lu_solve`` will square the errors. If you can't afford this, use
|
||||
``qr_solve`` instead. It is twice as slow but more accurate, and it calculates
|
||||
the residual automatically.
|
||||
|
||||
|
||||
Matrix factorization
|
||||
....................
|
||||
|
||||
The function ``lu`` computes an explicit LU factorization of a matrix::
|
||||
|
||||
>>> P, L, U = lu(matrix([[0,2,3],[4,5,6],[7,8,9]]))
|
||||
>>> print(P)
|
||||
[0.0 0.0 1.0]
|
||||
[1.0 0.0 0.0]
|
||||
[0.0 1.0 0.0]
|
||||
>>> print(L)
|
||||
[ 1.0 0.0 0.0]
|
||||
[ 0.0 1.0 0.0]
|
||||
[0.571428571428571 0.214285714285714 1.0]
|
||||
>>> print(U)
|
||||
[7.0 8.0 9.0]
|
||||
[0.0 2.0 3.0]
|
||||
[0.0 0.0 0.214285714285714]
|
||||
>>> print(P.T*L*U)
|
||||
[0.0 2.0 3.0]
|
||||
[4.0 5.0 6.0]
|
||||
[7.0 8.0 9.0]
|
||||
|
||||
Interval matrices
|
||||
-----------------
|
||||
|
||||
Matrices may contain interval elements. This allows one to perform
|
||||
basic linear algebra operations such as matrix multiplication
|
||||
and equation solving with rigorous error bounds::
|
||||
|
||||
>>> a = iv.matrix([['0.1','0.3','1.0'],
|
||||
... ['7.1','5.5','4.8'],
|
||||
... ['3.2','4.4','5.6']])
|
||||
>>>
|
||||
>>> b = iv.matrix(['4','0.6','0.5'])
|
||||
>>> c = iv.lu_solve(a, b)
|
||||
>>> print(c)
|
||||
[ [5.2582327113062568605927528666, 5.25823271130625686059275702219]]
|
||||
[[-13.1550493962678375411635581388, -13.1550493962678375411635540152]]
|
||||
[ [7.42069154774972557628979076189, 7.42069154774972557628979190734]]
|
||||
>>> print(a*c)
|
||||
[ [3.99999999999999999999999844904, 4.00000000000000000000000155096]]
|
||||
[[0.599999999999999999999968898009, 0.600000000000000000000031763736]]
|
||||
[[0.499999999999999999999979320485, 0.500000000000000000000020679515]]
|
||||
"""
|
||||
|
||||
# TODO:
|
||||
# *implement high-level qr()
|
||||
# *test unitvector
|
||||
# *iterative solving
|
||||
|
||||
from copy import copy
|
||||
|
||||
from ..libmp.backend import xrange
|
||||
|
||||
class LinearAlgebraMethods(object):
|
||||
|
||||
def LU_decomp(ctx, A, overwrite=False, use_cache=True):
|
||||
"""
|
||||
LU-factorization of a n*n matrix using the Gauss algorithm.
|
||||
Returns L and U in one matrix and the pivot indices.
|
||||
|
||||
Use overwrite to specify whether A will be overwritten with L and U.
|
||||
"""
|
||||
if not A.rows == A.cols:
|
||||
raise ValueError('need n*n matrix')
|
||||
# get from cache if possible
|
||||
if use_cache and isinstance(A, ctx.matrix) and A._LU:
|
||||
return A._LU
|
||||
if not overwrite:
|
||||
orig = A
|
||||
A = A.copy()
|
||||
tol = ctx.absmin(ctx.mnorm(A,1) * ctx.eps) # each pivot element has to be bigger
|
||||
n = A.rows
|
||||
p = [None]*(n - 1)
|
||||
for j in xrange(n - 1):
|
||||
# pivoting, choose max(abs(reciprocal row sum)*abs(pivot element))
|
||||
biggest = 0
|
||||
for k in xrange(j, n):
|
||||
s = ctx.fsum([ctx.absmin(A[k,l]) for l in xrange(j, n)])
|
||||
if ctx.absmin(s) <= tol:
|
||||
raise ZeroDivisionError('matrix is numerically singular')
|
||||
current = 1/s * ctx.absmin(A[k,j])
|
||||
if current > biggest: # TODO: what if equal?
|
||||
biggest = current
|
||||
p[j] = k
|
||||
# swap rows according to p
|
||||
ctx.swap_row(A, j, p[j])
|
||||
if ctx.absmin(A[j,j]) <= tol:
|
||||
raise ZeroDivisionError('matrix is numerically singular')
|
||||
# calculate elimination factors and add rows
|
||||
for i in xrange(j + 1, n):
|
||||
A[i,j] /= A[j,j]
|
||||
for k in xrange(j + 1, n):
|
||||
A[i,k] -= A[i,j]*A[j,k]
|
||||
if ctx.absmin(A[n - 1,n - 1]) <= tol:
|
||||
raise ZeroDivisionError('matrix is numerically singular')
|
||||
# cache decomposition
|
||||
if not overwrite and isinstance(orig, ctx.matrix):
|
||||
orig._LU = (A, p)
|
||||
return A, p
|
||||
|
||||
def L_solve(ctx, L, b, p=None):
|
||||
"""
|
||||
Solve the lower part of a LU factorized matrix for y.
|
||||
"""
|
||||
if L.rows != L.cols:
|
||||
raise RuntimeError("need n*n matrix")
|
||||
n = L.rows
|
||||
if len(b) != n:
|
||||
raise ValueError("Value should be equal to n")
|
||||
b = copy(b)
|
||||
if p: # swap b according to p
|
||||
for k in xrange(0, len(p)):
|
||||
ctx.swap_row(b, k, p[k])
|
||||
# solve
|
||||
for i in xrange(1, n):
|
||||
for j in xrange(i):
|
||||
b[i] -= L[i,j] * b[j]
|
||||
return b
|
||||
|
||||
def U_solve(ctx, U, y):
|
||||
"""
|
||||
Solve the upper part of a LU factorized matrix for x.
|
||||
"""
|
||||
if U.rows != U.cols:
|
||||
raise RuntimeError("need n*n matrix")
|
||||
n = U.rows
|
||||
if len(y) != n:
|
||||
raise ValueError("Value should be equal to n")
|
||||
x = copy(y)
|
||||
for i in xrange(n - 1, -1, -1):
|
||||
for j in xrange(i + 1, n):
|
||||
x[i] -= U[i,j] * x[j]
|
||||
x[i] /= U[i,i]
|
||||
return x
|
||||
|
||||
def lu_solve(ctx, A, b, **kwargs):
|
||||
"""
|
||||
Ax = b => x
|
||||
|
||||
Solve a determined or overdetermined linear equations system.
|
||||
Fast LU decomposition is used, which is less accurate than QR decomposition
|
||||
(especially for overdetermined systems), but it's twice as efficient.
|
||||
Use qr_solve if you want more precision or have to solve a very ill-
|
||||
conditioned system.
|
||||
|
||||
If you specify real=True, it does not check for overdeterminded complex
|
||||
systems.
|
||||
"""
|
||||
prec = ctx.prec
|
||||
try:
|
||||
ctx.prec += 10
|
||||
# do not overwrite A nor b
|
||||
A, b = ctx.matrix(A, **kwargs).copy(), ctx.matrix(b, **kwargs).copy()
|
||||
if A.rows < A.cols:
|
||||
raise ValueError('cannot solve underdetermined system')
|
||||
if A.rows > A.cols:
|
||||
# use least-squares method if overdetermined
|
||||
# (this increases errors)
|
||||
AH = A.H
|
||||
A = AH * A
|
||||
b = AH * b
|
||||
if (kwargs.get('real', False) or
|
||||
not sum(type(i) is ctx.mpc for i in A)):
|
||||
# TODO: necessary to check also b?
|
||||
x = ctx.cholesky_solve(A, b)
|
||||
else:
|
||||
x = ctx.lu_solve(A, b)
|
||||
else:
|
||||
# LU factorization
|
||||
A, p = ctx.LU_decomp(A)
|
||||
b = ctx.L_solve(A, b, p)
|
||||
x = ctx.U_solve(A, b)
|
||||
finally:
|
||||
ctx.prec = prec
|
||||
return x
|
||||
|
||||
def improve_solution(ctx, A, x, b, maxsteps=1):
|
||||
"""
|
||||
Improve a solution to a linear equation system iteratively.
|
||||
|
||||
This re-uses the LU decomposition and is thus cheap.
|
||||
Usually 3 up to 4 iterations are giving the maximal improvement.
|
||||
"""
|
||||
if A.rows != A.cols:
|
||||
raise RuntimeError("need n*n matrix") # TODO: really?
|
||||
for _ in xrange(maxsteps):
|
||||
r = ctx.residual(A, x, b)
|
||||
if ctx.norm(r, 2) < 10*ctx.eps:
|
||||
break
|
||||
# this uses cached LU decomposition and is thus cheap
|
||||
dx = ctx.lu_solve(A, -r)
|
||||
x += dx
|
||||
return x
|
||||
|
||||
def lu(ctx, A):
|
||||
"""
|
||||
A -> P, L, U
|
||||
|
||||
LU factorisation of a square matrix A. L is the lower, U the upper part.
|
||||
P is the permutation matrix indicating the row swaps.
|
||||
|
||||
P*A = L*U
|
||||
|
||||
If you need efficiency, use the low-level method LU_decomp instead, it's
|
||||
much more memory efficient.
|
||||
"""
|
||||
# get factorization
|
||||
A, p = ctx.LU_decomp(A)
|
||||
n = A.rows
|
||||
L = ctx.matrix(n)
|
||||
U = ctx.matrix(n)
|
||||
for i in xrange(n):
|
||||
for j in xrange(n):
|
||||
if i > j:
|
||||
L[i,j] = A[i,j]
|
||||
elif i == j:
|
||||
L[i,j] = 1
|
||||
U[i,j] = A[i,j]
|
||||
else:
|
||||
U[i,j] = A[i,j]
|
||||
# calculate permutation matrix
|
||||
P = ctx.eye(n)
|
||||
for k in xrange(len(p)):
|
||||
ctx.swap_row(P, k, p[k])
|
||||
return P, L, U
|
||||
|
||||
def unitvector(ctx, n, i):
|
||||
"""
|
||||
Return the i-th n-dimensional unit vector.
|
||||
"""
|
||||
assert 0 < i <= n, 'this unit vector does not exist'
|
||||
return [ctx.zero]*(i-1) + [ctx.one] + [ctx.zero]*(n-i)
|
||||
|
||||
def inverse(ctx, A, **kwargs):
|
||||
"""
|
||||
Calculate the inverse of a matrix.
|
||||
|
||||
If you want to solve an equation system Ax = b, it's recommended to use
|
||||
solve(A, b) instead, it's about 3 times more efficient.
|
||||
"""
|
||||
prec = ctx.prec
|
||||
try:
|
||||
ctx.prec += 10
|
||||
# do not overwrite A
|
||||
A = ctx.matrix(A, **kwargs).copy()
|
||||
n = A.rows
|
||||
# get LU factorisation
|
||||
A, p = ctx.LU_decomp(A)
|
||||
cols = []
|
||||
# calculate unit vectors and solve corresponding system to get columns
|
||||
for i in xrange(1, n + 1):
|
||||
e = ctx.unitvector(n, i)
|
||||
y = ctx.L_solve(A, e, p)
|
||||
cols.append(ctx.U_solve(A, y))
|
||||
# convert columns to matrix
|
||||
inv = []
|
||||
for i in xrange(n):
|
||||
row = []
|
||||
for j in xrange(n):
|
||||
row.append(cols[j][i])
|
||||
inv.append(row)
|
||||
result = ctx.matrix(inv, **kwargs)
|
||||
finally:
|
||||
ctx.prec = prec
|
||||
return result
|
||||
|
||||
def householder(ctx, A):
|
||||
"""
|
||||
(A|b) -> H, p, x, res
|
||||
|
||||
(A|b) is the coefficient matrix with left hand side of an optionally
|
||||
overdetermined linear equation system.
|
||||
H and p contain all information about the transformation matrices.
|
||||
x is the solution, res the residual.
|
||||
"""
|
||||
if not isinstance(A, ctx.matrix):
|
||||
raise TypeError("A should be a type of ctx.matrix")
|
||||
m = A.rows
|
||||
n = A.cols
|
||||
if m < n - 1:
|
||||
raise RuntimeError("Columns should not be less than rows")
|
||||
# calculate Householder matrix
|
||||
p = []
|
||||
for j in xrange(0, n - 1):
|
||||
s = ctx.fsum(abs(A[i,j])**2 for i in xrange(j, m))
|
||||
if not abs(s) > ctx.eps:
|
||||
raise ValueError('matrix is numerically singular')
|
||||
p.append(-ctx.sign(ctx.re(A[j,j])) * ctx.sqrt(s))
|
||||
kappa = ctx.one / (s - p[j] * A[j,j])
|
||||
A[j,j] -= p[j]
|
||||
for k in xrange(j+1, n):
|
||||
y = ctx.fsum(ctx.conj(A[i,j]) * A[i,k] for i in xrange(j, m)) * kappa
|
||||
for i in xrange(j, m):
|
||||
A[i,k] -= A[i,j] * y
|
||||
# solve Rx = c1
|
||||
x = [A[i,n - 1] for i in xrange(n - 1)]
|
||||
for i in xrange(n - 2, -1, -1):
|
||||
x[i] -= ctx.fsum(A[i,j] * x[j] for j in xrange(i + 1, n - 1))
|
||||
x[i] /= p[i]
|
||||
# calculate residual
|
||||
if not m == n - 1:
|
||||
r = [A[m-1-i, n-1] for i in xrange(m - n + 1)]
|
||||
else:
|
||||
# determined system, residual should be 0
|
||||
r = [0]*m # maybe a bad idea, changing r[i] will change all elements
|
||||
return A, p, x, r
|
||||
|
||||
#def qr(ctx, A):
|
||||
# """
|
||||
# A -> Q, R
|
||||
#
|
||||
# QR factorisation of a square matrix A using Householder decomposition.
|
||||
# Q is orthogonal, this leads to very few numerical errors.
|
||||
#
|
||||
# A = Q*R
|
||||
# """
|
||||
# H, p, x, res = householder(A)
|
||||
# TODO: implement this
|
||||
|
||||
def residual(ctx, A, x, b, **kwargs):
|
||||
"""
|
||||
Calculate the residual of a solution to a linear equation system.
|
||||
|
||||
r = A*x - b for A*x = b
|
||||
"""
|
||||
oldprec = ctx.prec
|
||||
try:
|
||||
ctx.prec *= 2
|
||||
A, x, b = ctx.matrix(A, **kwargs), ctx.matrix(x, **kwargs), ctx.matrix(b, **kwargs)
|
||||
return A*x - b
|
||||
finally:
|
||||
ctx.prec = oldprec
|
||||
|
||||
def qr_solve(ctx, A, b, norm=None, **kwargs):
|
||||
"""
|
||||
Ax = b => x, ||Ax - b||
|
||||
|
||||
Solve a determined or overdetermined linear equations system and
|
||||
calculate the norm of the residual (error).
|
||||
QR decomposition using Householder factorization is applied, which gives very
|
||||
accurate results even for ill-conditioned matrices. qr_solve is twice as
|
||||
efficient.
|
||||
"""
|
||||
if norm is None:
|
||||
norm = ctx.norm
|
||||
prec = ctx.prec
|
||||
try:
|
||||
ctx.prec += 10
|
||||
# do not overwrite A nor b
|
||||
A, b = ctx.matrix(A, **kwargs).copy(), ctx.matrix(b, **kwargs).copy()
|
||||
if A.rows < A.cols:
|
||||
raise ValueError('cannot solve underdetermined system')
|
||||
H, p, x, r = ctx.householder(ctx.extend(A, b))
|
||||
res = ctx.norm(r)
|
||||
# calculate residual "manually" for determined systems
|
||||
if res == 0:
|
||||
res = ctx.norm(ctx.residual(A, x, b))
|
||||
return ctx.matrix(x, **kwargs), res
|
||||
finally:
|
||||
ctx.prec = prec
|
||||
|
||||
def cholesky(ctx, A, tol=None):
|
||||
r"""
|
||||
Cholesky decomposition of a symmetric positive-definite matrix `A`.
|
||||
Returns a lower triangular matrix `L` such that `A = L \times L^T`.
|
||||
More generally, for a complex Hermitian positive-definite matrix,
|
||||
a Cholesky decomposition satisfying `A = L \times L^H` is returned.
|
||||
|
||||
The Cholesky decomposition can be used to solve linear equation
|
||||
systems twice as efficiently as LU decomposition, or to
|
||||
test whether `A` is positive-definite.
|
||||
|
||||
The optional parameter ``tol`` determines the tolerance for
|
||||
verifying positive-definiteness.
|
||||
|
||||
**Examples**
|
||||
|
||||
Cholesky decomposition of a positive-definite symmetric matrix::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 25; mp.pretty = True
|
||||
>>> A = eye(3) + hilbert(3)
|
||||
>>> nprint(A)
|
||||
[ 2.0 0.5 0.333333]
|
||||
[ 0.5 1.33333 0.25]
|
||||
[0.333333 0.25 1.2]
|
||||
>>> L = cholesky(A)
|
||||
>>> nprint(L)
|
||||
[ 1.41421 0.0 0.0]
|
||||
[0.353553 1.09924 0.0]
|
||||
[0.235702 0.15162 1.05899]
|
||||
>>> chop(A - L*L.T)
|
||||
[0.0 0.0 0.0]
|
||||
[0.0 0.0 0.0]
|
||||
[0.0 0.0 0.0]
|
||||
|
||||
Cholesky decomposition of a Hermitian matrix::
|
||||
|
||||
>>> A = eye(3) + matrix([[0,0.25j,-0.5j],[-0.25j,0,0],[0.5j,0,0]])
|
||||
>>> L = cholesky(A)
|
||||
>>> nprint(L)
|
||||
[ 1.0 0.0 0.0]
|
||||
[(0.0 - 0.25j) (0.968246 + 0.0j) 0.0]
|
||||
[ (0.0 + 0.5j) (0.129099 + 0.0j) (0.856349 + 0.0j)]
|
||||
>>> chop(A - L*L.H)
|
||||
[0.0 0.0 0.0]
|
||||
[0.0 0.0 0.0]
|
||||
[0.0 0.0 0.0]
|
||||
|
||||
Attempted Cholesky decomposition of a matrix that is not positive
|
||||
definite::
|
||||
|
||||
>>> A = -eye(3) + hilbert(3)
|
||||
>>> L = cholesky(A)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: matrix is not positive-definite
|
||||
|
||||
**References**
|
||||
|
||||
1. [Wikipedia]_ http://en.wikipedia.org/wiki/Cholesky_decomposition
|
||||
|
||||
"""
|
||||
if not isinstance(A, ctx.matrix):
|
||||
raise RuntimeError("A should be a type of ctx.matrix")
|
||||
if not A.rows == A.cols:
|
||||
raise ValueError('need n*n matrix')
|
||||
if tol is None:
|
||||
tol = +ctx.eps
|
||||
n = A.rows
|
||||
L = ctx.matrix(n)
|
||||
for j in xrange(n):
|
||||
c = ctx.re(A[j,j])
|
||||
if abs(c-A[j,j]) > tol:
|
||||
raise ValueError('matrix is not Hermitian')
|
||||
s = c - ctx.fsum((L[j,k] for k in xrange(j)),
|
||||
absolute=True, squared=True)
|
||||
if s < tol:
|
||||
raise ValueError('matrix is not positive-definite')
|
||||
L[j,j] = ctx.sqrt(s)
|
||||
for i in xrange(j, n):
|
||||
it1 = (L[i,k] for k in xrange(j))
|
||||
it2 = (L[j,k] for k in xrange(j))
|
||||
t = ctx.fdot(it1, it2, conjugate=True)
|
||||
L[i,j] = (A[i,j] - t) / L[j,j]
|
||||
return L
|
||||
|
||||
def cholesky_solve(ctx, A, b, **kwargs):
|
||||
"""
|
||||
Ax = b => x
|
||||
|
||||
Solve a symmetric positive-definite linear equation system.
|
||||
This is twice as efficient as lu_solve.
|
||||
|
||||
Typical use cases:
|
||||
* A.T*A
|
||||
* Hessian matrix
|
||||
* differential equations
|
||||
"""
|
||||
prec = ctx.prec
|
||||
try:
|
||||
ctx.prec += 10
|
||||
# do not overwrite A nor b
|
||||
A, b = ctx.matrix(A, **kwargs).copy(), ctx.matrix(b, **kwargs).copy()
|
||||
if A.rows != A.cols:
|
||||
raise ValueError('can only solve determined system')
|
||||
# Cholesky factorization
|
||||
L = ctx.cholesky(A)
|
||||
# solve
|
||||
n = L.rows
|
||||
if len(b) != n:
|
||||
raise ValueError("Value should be equal to n")
|
||||
for i in xrange(n):
|
||||
b[i] -= ctx.fsum(L[i,j] * b[j] for j in xrange(i))
|
||||
b[i] /= L[i,i]
|
||||
x = ctx.U_solve(L.T, b)
|
||||
return x
|
||||
finally:
|
||||
ctx.prec = prec
|
||||
|
||||
def det(ctx, A):
|
||||
"""
|
||||
Calculate the determinant of a matrix.
|
||||
"""
|
||||
prec = ctx.prec
|
||||
try:
|
||||
# do not overwrite A
|
||||
A = ctx.matrix(A).copy()
|
||||
# use LU factorization to calculate determinant
|
||||
try:
|
||||
R, p = ctx.LU_decomp(A)
|
||||
except ZeroDivisionError:
|
||||
return 0
|
||||
z = 1
|
||||
for i, e in enumerate(p):
|
||||
if i != e:
|
||||
z *= -1
|
||||
for i in xrange(A.rows):
|
||||
z *= R[i,i]
|
||||
return z
|
||||
finally:
|
||||
ctx.prec = prec
|
||||
|
||||
def cond(ctx, A, norm=None):
|
||||
"""
|
||||
Calculate the condition number of a matrix using a specified matrix norm.
|
||||
|
||||
The condition number estimates the sensitivity of a matrix to errors.
|
||||
Example: small input errors for ill-conditioned coefficient matrices
|
||||
alter the solution of the system dramatically.
|
||||
|
||||
For ill-conditioned matrices it's recommended to use qr_solve() instead
|
||||
of lu_solve(). This does not help with input errors however, it just avoids
|
||||
to add additional errors.
|
||||
|
||||
Definition: cond(A) = ||A|| * ||A**-1||
|
||||
"""
|
||||
if norm is None:
|
||||
norm = lambda x: ctx.mnorm(x,1)
|
||||
return norm(A) * norm(ctx.inverse(A))
|
||||
|
||||
def lu_solve_mat(ctx, a, b):
|
||||
"""Solve a * x = b where a and b are matrices."""
|
||||
r = ctx.matrix(a.rows, b.cols)
|
||||
for i in range(b.cols):
|
||||
c = ctx.lu_solve(a, b.column(i))
|
||||
for j in range(len(c)):
|
||||
r[j, i] = c[j]
|
||||
return r
|
||||
|
||||
def qr(ctx, A, mode = 'full', edps = 10):
|
||||
"""
|
||||
Compute a QR factorization $A = QR$ where
|
||||
A is an m x n matrix of real or complex numbers where m >= n
|
||||
|
||||
mode has following meanings:
|
||||
(1) mode = 'raw' returns two matrixes (A, tau) in the
|
||||
internal format used by LAPACK
|
||||
(2) mode = 'skinny' returns the leading n columns of Q
|
||||
and n rows of R
|
||||
(3) Any other value returns the leading m columns of Q
|
||||
and m rows of R
|
||||
|
||||
edps is the increase in mp precision used for calculations
|
||||
|
||||
**Examples**
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 15
|
||||
>>> mp.pretty = True
|
||||
>>> A = matrix([[1, 2], [3, 4], [1, 1]])
|
||||
>>> Q, R = qr(A)
|
||||
>>> Q
|
||||
[-0.301511344577764 0.861640436855329 0.408248290463863]
|
||||
[-0.904534033733291 -0.123091490979333 -0.408248290463863]
|
||||
[-0.301511344577764 -0.492365963917331 0.816496580927726]
|
||||
>>> R
|
||||
[-3.3166247903554 -4.52267016866645]
|
||||
[ 0.0 0.738548945875996]
|
||||
[ 0.0 0.0]
|
||||
>>> Q * R
|
||||
[1.0 2.0]
|
||||
[3.0 4.0]
|
||||
[1.0 1.0]
|
||||
>>> chop(Q.T * Q)
|
||||
[1.0 0.0 0.0]
|
||||
[0.0 1.0 0.0]
|
||||
[0.0 0.0 1.0]
|
||||
>>> B = matrix([[1+0j, 2-3j], [3+j, 4+5j]])
|
||||
>>> Q, R = qr(B)
|
||||
>>> nprint(Q)
|
||||
[ (-0.301511 + 0.0j) (0.0695795 - 0.95092j)]
|
||||
[(-0.904534 - 0.301511j) (-0.115966 + 0.278318j)]
|
||||
>>> nprint(R)
|
||||
[(-3.31662 + 0.0j) (-5.72872 - 2.41209j)]
|
||||
[ 0.0 (3.91965 + 0.0j)]
|
||||
>>> Q * R
|
||||
[(1.0 + 0.0j) (2.0 - 3.0j)]
|
||||
[(3.0 + 1.0j) (4.0 + 5.0j)]
|
||||
>>> chop(Q.T * Q.conjugate())
|
||||
[1.0 0.0]
|
||||
[0.0 1.0]
|
||||
|
||||
"""
|
||||
|
||||
# check values before continuing
|
||||
assert isinstance(A, ctx.matrix)
|
||||
m = A.rows
|
||||
n = A.cols
|
||||
assert n >= 0
|
||||
assert m >= n
|
||||
assert edps >= 0
|
||||
|
||||
# check for complex data type
|
||||
cmplx = any(type(x) is ctx.mpc for x in A)
|
||||
|
||||
# temporarily increase the precision and initialize
|
||||
with ctx.extradps(edps):
|
||||
tau = ctx.matrix(n,1)
|
||||
A = A.copy()
|
||||
|
||||
# ---------------
|
||||
# FACTOR MATRIX A
|
||||
# ---------------
|
||||
if cmplx:
|
||||
one = ctx.mpc('1.0', '0.0')
|
||||
zero = ctx.mpc('0.0', '0.0')
|
||||
rzero = ctx.mpf('0.0')
|
||||
|
||||
# main loop to factor A (complex)
|
||||
for j in xrange(0, n):
|
||||
alpha = A[j,j]
|
||||
alphr = ctx.re(alpha)
|
||||
alphi = ctx.im(alpha)
|
||||
|
||||
if (m-j) >= 2:
|
||||
xnorm = ctx.fsum( A[i,j]*ctx.conj(A[i,j]) for i in xrange(j+1, m) )
|
||||
xnorm = ctx.re( ctx.sqrt(xnorm) )
|
||||
else:
|
||||
xnorm = rzero
|
||||
|
||||
if (xnorm == rzero) and (alphi == rzero):
|
||||
tau[j] = zero
|
||||
continue
|
||||
|
||||
if alphr < rzero:
|
||||
beta = ctx.sqrt(alphr**2 + alphi**2 + xnorm**2)
|
||||
else:
|
||||
beta = -ctx.sqrt(alphr**2 + alphi**2 + xnorm**2)
|
||||
|
||||
tau[j] = ctx.mpc( (beta - alphr) / beta, -alphi / beta )
|
||||
t = -ctx.conj(tau[j])
|
||||
za = one / (alpha - beta)
|
||||
|
||||
for i in xrange(j+1, m):
|
||||
A[i,j] *= za
|
||||
|
||||
A[j,j] = one
|
||||
for k in xrange(j+1, n):
|
||||
y = ctx.fsum(A[i,j] * ctx.conj(A[i,k]) for i in xrange(j, m))
|
||||
temp = t * ctx.conj(y)
|
||||
for i in xrange(j, m):
|
||||
A[i,k] += A[i,j] * temp
|
||||
|
||||
A[j,j] = ctx.mpc(beta, '0.0')
|
||||
else:
|
||||
one = ctx.mpf('1.0')
|
||||
zero = ctx.mpf('0.0')
|
||||
|
||||
# main loop to factor A (real)
|
||||
for j in xrange(0, n):
|
||||
alpha = A[j,j]
|
||||
|
||||
if (m-j) > 2:
|
||||
xnorm = ctx.fsum( (A[i,j])**2 for i in xrange(j+1, m) )
|
||||
xnorm = ctx.sqrt(xnorm)
|
||||
elif (m-j) == 2:
|
||||
xnorm = abs( A[m-1,j] )
|
||||
else:
|
||||
xnorm = zero
|
||||
|
||||
if xnorm == zero:
|
||||
tau[j] = zero
|
||||
continue
|
||||
|
||||
if alpha < zero:
|
||||
beta = ctx.sqrt(alpha**2 + xnorm**2)
|
||||
else:
|
||||
beta = -ctx.sqrt(alpha**2 + xnorm**2)
|
||||
|
||||
tau[j] = (beta - alpha) / beta
|
||||
t = -tau[j]
|
||||
da = one / (alpha - beta)
|
||||
|
||||
for i in xrange(j+1, m):
|
||||
A[i,j] *= da
|
||||
|
||||
A[j,j] = one
|
||||
for k in xrange(j+1, n):
|
||||
y = ctx.fsum( A[i,j] * A[i,k] for i in xrange(j, m) )
|
||||
temp = t * y
|
||||
for i in xrange(j,m):
|
||||
A[i,k] += A[i,j] * temp
|
||||
|
||||
A[j,j] = beta
|
||||
|
||||
# return factorization in same internal format as LAPACK
|
||||
if (mode == 'raw') or (mode == 'RAW'):
|
||||
return A, tau
|
||||
|
||||
# ----------------------------------
|
||||
# FORM Q USING BACKWARD ACCUMULATION
|
||||
# ----------------------------------
|
||||
|
||||
# form R before the values are overwritten
|
||||
R = A.copy()
|
||||
for j in xrange(0, n):
|
||||
for i in xrange(j+1, m):
|
||||
R[i,j] = zero
|
||||
|
||||
# set the value of p (number of columns of Q to return)
|
||||
p = m
|
||||
if (mode == 'skinny') or (mode == 'SKINNY'):
|
||||
p = n
|
||||
|
||||
# add columns to A if needed and initialize
|
||||
A.cols += (p-n)
|
||||
for j in xrange(0, p):
|
||||
A[j,j] = one
|
||||
for i in xrange(0, j):
|
||||
A[i,j] = zero
|
||||
|
||||
# main loop to form Q
|
||||
for j in xrange(n-1, -1, -1):
|
||||
t = -tau[j]
|
||||
A[j,j] += t
|
||||
|
||||
for k in xrange(j+1, p):
|
||||
if cmplx:
|
||||
y = ctx.fsum(A[i,j] * ctx.conj(A[i,k]) for i in xrange(j+1, m))
|
||||
temp = t * ctx.conj(y)
|
||||
else:
|
||||
y = ctx.fsum(A[i,j] * A[i,k] for i in xrange(j+1, m))
|
||||
temp = t * y
|
||||
A[j,k] = temp
|
||||
for i in xrange(j+1, m):
|
||||
A[i,k] += A[i,j] * temp
|
||||
|
||||
for i in xrange(j+1, m):
|
||||
A[i, j] *= t
|
||||
|
||||
return A, R[0:p,0:n]
|
||||
|
||||
# ------------------
|
||||
# END OF FUNCTION QR
|
||||
# ------------------
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,240 @@
|
||||
import operator
|
||||
import sys
|
||||
from .libmp import int_types, mpf_hash, bitcount, from_man_exp, HASH_MODULUS
|
||||
|
||||
new = object.__new__
|
||||
|
||||
def create_reduced(p, q, _cache={}):
|
||||
key = p, q
|
||||
if key in _cache:
|
||||
return _cache[key]
|
||||
x, y = p, q
|
||||
while y:
|
||||
x, y = y, x % y
|
||||
if x != 1:
|
||||
p //= x
|
||||
q //= x
|
||||
v = new(mpq)
|
||||
v._mpq_ = p, q
|
||||
# Speedup integers, half-integers and other small fractions
|
||||
if q <= 4 and abs(key[0]) < 100:
|
||||
_cache[key] = v
|
||||
return v
|
||||
|
||||
class mpq(object):
|
||||
"""
|
||||
Exact rational type, currently only intended for internal use.
|
||||
"""
|
||||
|
||||
__slots__ = ["_mpq_"]
|
||||
|
||||
def __new__(cls, p, q=1):
|
||||
if type(p) is tuple:
|
||||
p, q = p
|
||||
elif hasattr(p, '_mpq_'):
|
||||
p, q = p._mpq_
|
||||
return create_reduced(p, q)
|
||||
|
||||
def __repr__(s):
|
||||
return "mpq(%s,%s)" % s._mpq_
|
||||
|
||||
def __str__(s):
|
||||
return "(%s/%s)" % s._mpq_
|
||||
|
||||
def __int__(s):
|
||||
a, b = s._mpq_
|
||||
return a // b
|
||||
|
||||
def __nonzero__(s):
|
||||
return bool(s._mpq_[0])
|
||||
|
||||
__bool__ = __nonzero__
|
||||
|
||||
def __hash__(s):
|
||||
a, b = s._mpq_
|
||||
if sys.version_info >= (3, 2):
|
||||
inverse = pow(b, HASH_MODULUS-2, HASH_MODULUS)
|
||||
if not inverse:
|
||||
h = sys.hash_info.inf
|
||||
else:
|
||||
h = (abs(a) * inverse) % HASH_MODULUS
|
||||
if a < 0: h = -h
|
||||
if h == -1: h = -2
|
||||
return h
|
||||
else:
|
||||
if b == 1:
|
||||
return hash(a)
|
||||
# Power of two: mpf compatible hash
|
||||
if not (b & (b-1)):
|
||||
return mpf_hash(from_man_exp(a, 1-bitcount(b)))
|
||||
return hash((a,b))
|
||||
|
||||
def __eq__(s, t):
|
||||
ttype = type(t)
|
||||
if ttype is mpq:
|
||||
return s._mpq_ == t._mpq_
|
||||
if ttype in int_types:
|
||||
a, b = s._mpq_
|
||||
if b != 1:
|
||||
return False
|
||||
return a == t
|
||||
return NotImplemented
|
||||
|
||||
def __ne__(s, t):
|
||||
ttype = type(t)
|
||||
if ttype is mpq:
|
||||
return s._mpq_ != t._mpq_
|
||||
if ttype in int_types:
|
||||
a, b = s._mpq_
|
||||
if b != 1:
|
||||
return True
|
||||
return a != t
|
||||
return NotImplemented
|
||||
|
||||
def _cmp(s, t, op):
|
||||
ttype = type(t)
|
||||
if ttype in int_types:
|
||||
a, b = s._mpq_
|
||||
return op(a, t*b)
|
||||
if ttype is mpq:
|
||||
a, b = s._mpq_
|
||||
c, d = t._mpq_
|
||||
return op(a*d, b*c)
|
||||
return NotImplementedError
|
||||
|
||||
def __lt__(s, t): return s._cmp(t, operator.lt)
|
||||
def __le__(s, t): return s._cmp(t, operator.le)
|
||||
def __gt__(s, t): return s._cmp(t, operator.gt)
|
||||
def __ge__(s, t): return s._cmp(t, operator.ge)
|
||||
|
||||
def __abs__(s):
|
||||
a, b = s._mpq_
|
||||
if a >= 0:
|
||||
return s
|
||||
v = new(mpq)
|
||||
v._mpq_ = -a, b
|
||||
return v
|
||||
|
||||
def __neg__(s):
|
||||
a, b = s._mpq_
|
||||
v = new(mpq)
|
||||
v._mpq_ = -a, b
|
||||
return v
|
||||
|
||||
def __pos__(s):
|
||||
return s
|
||||
|
||||
def __add__(s, t):
|
||||
ttype = type(t)
|
||||
if ttype is mpq:
|
||||
a, b = s._mpq_
|
||||
c, d = t._mpq_
|
||||
return create_reduced(a*d+b*c, b*d)
|
||||
if ttype in int_types:
|
||||
a, b = s._mpq_
|
||||
v = new(mpq)
|
||||
v._mpq_ = a+b*t, b
|
||||
return v
|
||||
return NotImplemented
|
||||
|
||||
__radd__ = __add__
|
||||
|
||||
def __sub__(s, t):
|
||||
ttype = type(t)
|
||||
if ttype is mpq:
|
||||
a, b = s._mpq_
|
||||
c, d = t._mpq_
|
||||
return create_reduced(a*d-b*c, b*d)
|
||||
if ttype in int_types:
|
||||
a, b = s._mpq_
|
||||
v = new(mpq)
|
||||
v._mpq_ = a-b*t, b
|
||||
return v
|
||||
return NotImplemented
|
||||
|
||||
def __rsub__(s, t):
|
||||
ttype = type(t)
|
||||
if ttype is mpq:
|
||||
a, b = s._mpq_
|
||||
c, d = t._mpq_
|
||||
return create_reduced(b*c-a*d, b*d)
|
||||
if ttype in int_types:
|
||||
a, b = s._mpq_
|
||||
v = new(mpq)
|
||||
v._mpq_ = b*t-a, b
|
||||
return v
|
||||
return NotImplemented
|
||||
|
||||
def __mul__(s, t):
|
||||
ttype = type(t)
|
||||
if ttype is mpq:
|
||||
a, b = s._mpq_
|
||||
c, d = t._mpq_
|
||||
return create_reduced(a*c, b*d)
|
||||
if ttype in int_types:
|
||||
a, b = s._mpq_
|
||||
return create_reduced(a*t, b)
|
||||
return NotImplemented
|
||||
|
||||
__rmul__ = __mul__
|
||||
|
||||
def __div__(s, t):
|
||||
ttype = type(t)
|
||||
if ttype is mpq:
|
||||
a, b = s._mpq_
|
||||
c, d = t._mpq_
|
||||
return create_reduced(a*d, b*c)
|
||||
if ttype in int_types:
|
||||
a, b = s._mpq_
|
||||
return create_reduced(a, b*t)
|
||||
return NotImplemented
|
||||
|
||||
def __rdiv__(s, t):
|
||||
ttype = type(t)
|
||||
if ttype is mpq:
|
||||
a, b = s._mpq_
|
||||
c, d = t._mpq_
|
||||
return create_reduced(b*c, a*d)
|
||||
if ttype in int_types:
|
||||
a, b = s._mpq_
|
||||
return create_reduced(b*t, a)
|
||||
return NotImplemented
|
||||
|
||||
def __pow__(s, t):
|
||||
ttype = type(t)
|
||||
if ttype in int_types:
|
||||
a, b = s._mpq_
|
||||
if t:
|
||||
if t < 0:
|
||||
a, b, t = b, a, -t
|
||||
v = new(mpq)
|
||||
v._mpq_ = a**t, b**t
|
||||
return v
|
||||
raise ZeroDivisionError
|
||||
return NotImplemented
|
||||
|
||||
|
||||
mpq_1 = mpq((1,1))
|
||||
mpq_0 = mpq((0,1))
|
||||
mpq_1_2 = mpq((1,2))
|
||||
mpq_3_2 = mpq((3,2))
|
||||
mpq_1_4 = mpq((1,4))
|
||||
mpq_1_16 = mpq((1,16))
|
||||
mpq_3_16 = mpq((3,16))
|
||||
mpq_5_2 = mpq((5,2))
|
||||
mpq_3_4 = mpq((3,4))
|
||||
mpq_7_4 = mpq((7,4))
|
||||
mpq_5_4 = mpq((5,4))
|
||||
|
||||
|
||||
# Register with "numbers" ABC
|
||||
# We do not subclass, hence we do not use the @abstractmethod checks. While
|
||||
# this is less invasive it may turn out that we do not actually support
|
||||
# parts of the expected interfaces. See
|
||||
# http://docs.python.org/2/library/numbers.html for list of abstract
|
||||
# methods.
|
||||
try:
|
||||
import numbers
|
||||
numbers.Rational.register(mpq)
|
||||
except ImportError:
|
||||
pass
|
||||
@@ -0,0 +1,215 @@
|
||||
from mpmath import *
|
||||
from mpmath.libmp import ifac
|
||||
|
||||
import sys
|
||||
if "-dps" in sys.argv:
|
||||
maxdps = int(sys.argv[sys.argv.index("-dps")+1])
|
||||
else:
|
||||
maxdps = 1000
|
||||
|
||||
raise_ = "-raise" in sys.argv
|
||||
|
||||
errcount = 0
|
||||
|
||||
def check(name, func, z, y):
|
||||
global errcount
|
||||
try:
|
||||
x = func(z)
|
||||
except:
|
||||
errcount += 1
|
||||
if raise_:
|
||||
raise
|
||||
print()
|
||||
print(name)
|
||||
print("EXCEPTION")
|
||||
import traceback
|
||||
traceback.print_tb(sys.exc_info()[2])
|
||||
print()
|
||||
return
|
||||
xre = x.real
|
||||
xim = x.imag
|
||||
yre = y.real
|
||||
yim = y.imag
|
||||
tol = eps*8
|
||||
err = 0
|
||||
if abs(xre-yre) > abs(yre)*tol:
|
||||
err = 1
|
||||
print()
|
||||
print("Error! %s (re = %s, wanted %s, err=%s)" % (name, nstr(xre,10), nstr(yre,10), nstr(abs(xre-yre))))
|
||||
errcount += 1
|
||||
if raise_:
|
||||
raise SystemExit
|
||||
if abs(xim-yim) > abs(yim)*tol:
|
||||
err = 1
|
||||
print()
|
||||
print("Error! %s (im = %s, wanted %s, err=%s)" % (name, nstr(xim,10), nstr(yim,10), nstr(abs(xim-yim))))
|
||||
errcount += 1
|
||||
if raise_:
|
||||
raise SystemExit
|
||||
if not err:
|
||||
sys.stdout.write("%s ok; " % name)
|
||||
|
||||
def testcase(case):
|
||||
z, result = case
|
||||
print("Testing z =", z)
|
||||
mp.dps = 1010
|
||||
z = eval(z)
|
||||
mp.dps = maxdps + 50
|
||||
if result is None:
|
||||
gamma_val = gamma(z)
|
||||
loggamma_val = loggamma(z)
|
||||
factorial_val = factorial(z)
|
||||
rgamma_val = rgamma(z)
|
||||
else:
|
||||
loggamma_val = eval(result)
|
||||
gamma_val = exp(loggamma_val)
|
||||
factorial_val = z * gamma_val
|
||||
rgamma_val = 1/gamma_val
|
||||
for dps in [5, 10, 15, 25, 40, 60, 90, 120, 250, 600, 1000, 1800, 3600]:
|
||||
if dps > maxdps:
|
||||
break
|
||||
mp.dps = dps
|
||||
print("dps = %s" % dps)
|
||||
check("gamma", gamma, z, gamma_val)
|
||||
check("rgamma", rgamma, z, rgamma_val)
|
||||
check("loggamma", loggamma, z, loggamma_val)
|
||||
check("factorial", factorial, z, factorial_val)
|
||||
print()
|
||||
mp.dps = 15
|
||||
|
||||
testcases = []
|
||||
|
||||
# Basic values
|
||||
for n in list(range(1,200)) + list(range(201,2000,17)):
|
||||
testcases.append(["%s" % n, None])
|
||||
for n in range(-200,200):
|
||||
testcases.append(["%s+0.5" % n, None])
|
||||
testcases.append(["%s+0.37" % n, None])
|
||||
|
||||
testcases += [\
|
||||
["(0.1+1j)", None],
|
||||
["(-0.1+1j)", None],
|
||||
["(0.1-1j)", None],
|
||||
["(-0.1-1j)", None],
|
||||
["10j", None],
|
||||
["-10j", None],
|
||||
["100j", None],
|
||||
["10000j", None],
|
||||
["-10000000j", None],
|
||||
["(10**100)*j", None],
|
||||
["125+(10**100)*j", None],
|
||||
["-125+(10**100)*j", None],
|
||||
["(10**10)*(1+j)", None],
|
||||
["(10**10)*(-1+j)", None],
|
||||
["(10**100)*(1+j)", None],
|
||||
["(10**100)*(-1+j)", None],
|
||||
["(1.5-1j)", None],
|
||||
["(6+4j)", None],
|
||||
["(4+1j)", None],
|
||||
["(3.5+2j)", None],
|
||||
["(1.5-1j)", None],
|
||||
["(-6-4j)", None],
|
||||
["(-2-3j)", None],
|
||||
["(-2.5-2j)", None],
|
||||
["(4+1j)", None],
|
||||
["(3+3j)", None],
|
||||
["(2-2j)", None],
|
||||
["1", "0"],
|
||||
["2", "0"],
|
||||
["3", "log(2)"],
|
||||
["4", "log(6)"],
|
||||
["5", "log(24)"],
|
||||
["0.5", "log(pi)/2"],
|
||||
["1.5", "log(sqrt(pi)/2)"],
|
||||
["2.5", "log(3*sqrt(pi)/4)"],
|
||||
["mpf('0.37')", None],
|
||||
["0.25", "log(sqrt(2*sqrt(2*pi**3)/agm(1,sqrt(2))))"],
|
||||
["-0.4", None],
|
||||
["mpf('-1.9')", None],
|
||||
["mpf('12.8')", None],
|
||||
["mpf('33.7')", None],
|
||||
["mpf('95.2')", None],
|
||||
["mpf('160.3')", None],
|
||||
["mpf('2057.8')", None],
|
||||
["25", "log(ifac(24))"],
|
||||
["80", "log(ifac(79))"],
|
||||
["500", "log(ifac(500-1))"],
|
||||
["8000", "log(ifac(8000-1))"],
|
||||
["8000.5", None],
|
||||
["mpf('8000.1')", None],
|
||||
["mpf('1.37e10')", None],
|
||||
["mpf('1.37e10')*(1+j)", None],
|
||||
["mpf('1.37e10')*(-1+j)", None],
|
||||
["mpf('1.37e10')*(-1-j)", None],
|
||||
["mpf('1.37e10')*(-1+j)", None],
|
||||
["mpf('1.37e100')", None],
|
||||
["mpf('1.37e100')*(1+j)", None],
|
||||
["mpf('1.37e100')*(-1+j)", None],
|
||||
["mpf('1.37e100')*(-1-j)", None],
|
||||
["mpf('1.37e100')*(-1+j)", None],
|
||||
["3+4j",
|
||||
"mpc('"
|
||||
"-1.7566267846037841105306041816232757851567066070613445016197619371316057169"
|
||||
"4723618263960834804618463052988607348289672535780644470689771115236512106002"
|
||||
"5970873471563240537307638968509556191696167970488390423963867031934333890838"
|
||||
"8009531786948197210025029725361069435208930363494971027388382086721660805397"
|
||||
"9163230643216054580167976201709951509519218635460317367338612500626714783631"
|
||||
"7498317478048447525674016344322545858832610325861086336204591943822302971823"
|
||||
"5161814175530618223688296232894588415495615809337292518431903058265147109853"
|
||||
"1710568942184987827643886816200452860853873815413367529829631430146227470517"
|
||||
"6579967222200868632179482214312673161276976117132204633283806161971389519137"
|
||||
"1243359764435612951384238091232760634271570950240717650166551484551654327989"
|
||||
"9360285030081716934130446150245110557038117075172576825490035434069388648124"
|
||||
"6678152254554001586736120762641422590778766100376515737713938521275749049949"
|
||||
"1284143906816424244705094759339932733567910991920631339597278805393743140853"
|
||||
"391550313363278558195609260225928','"
|
||||
"4.74266443803465792819488940755002274088830335171164611359052405215840070271"
|
||||
"5906813009373171139767051863542508136875688550817670379002790304870822775498"
|
||||
"2809996675877564504192565392367259119610438951593128982646945990372179860613"
|
||||
"4294436498090428077839141927485901735557543641049637962003652638924845391650"
|
||||
"9546290137755550107224907606529385248390667634297183361902055842228798984200"
|
||||
"9591180450211798341715874477629099687609819466457990642030707080894518168924"
|
||||
"6805549314043258530272479246115112769957368212585759640878745385160943755234"
|
||||
"9398036774908108204370323896757543121853650025529763655312360354244898913463"
|
||||
"7115955702828838923393113618205074162812089732064414530813087483533203244056"
|
||||
"0546577484241423134079056537777170351934430586103623577814746004431994179990"
|
||||
"5318522939077992613855205801498201930221975721246498720895122345420698451980"
|
||||
"0051215797310305885845964334761831751370672996984756815410977750799748813563"
|
||||
"8784405288158432214886648743541773208808731479748217023665577802702269468013"
|
||||
"673719173759245720489020315779001')"],
|
||||
]
|
||||
|
||||
for z in [4, 14, 34, 64]:
|
||||
testcases.append(["(2+j)*%s/3" % z, None])
|
||||
testcases.append(["(-2+j)*%s/3" % z, None])
|
||||
testcases.append(["(1+2*j)*%s/3" % z, None])
|
||||
testcases.append(["(2-j)*%s/3" % z, None])
|
||||
testcases.append(["(20+j)*%s/3" % z, None])
|
||||
testcases.append(["(-20+j)*%s/3" % z, None])
|
||||
testcases.append(["(1+20*j)*%s/3" % z, None])
|
||||
testcases.append(["(20-j)*%s/3" % z, None])
|
||||
testcases.append(["(200+j)*%s/3" % z, None])
|
||||
testcases.append(["(-200+j)*%s/3" % z, None])
|
||||
testcases.append(["(1+200*j)*%s/3" % z, None])
|
||||
testcases.append(["(200-j)*%s/3" % z, None])
|
||||
|
||||
# Poles
|
||||
for n in [0,1,2,3,4,25,-1,-2,-3,-4,-20,-21,-50,-51,-200,-201,-20000,-20001]:
|
||||
for t in ['1e-5', '1e-20', '1e-100', '1e-10000']:
|
||||
testcases.append(["fadd(%s,'%s',exact=True)" % (n, t), None])
|
||||
testcases.append(["fsub(%s,'%s',exact=True)" % (n, t), None])
|
||||
testcases.append(["fadd(%s,'%sj',exact=True)" % (n, t), None])
|
||||
testcases.append(["fsub(%s,'%sj',exact=True)" % (n, t), None])
|
||||
|
||||
if __name__ == "__main__":
|
||||
from timeit import default_timer as clock
|
||||
tot_time = 0.0
|
||||
for case in testcases:
|
||||
t1 = clock()
|
||||
testcase(case)
|
||||
t2 = clock()
|
||||
print("Test time:", t2-t1)
|
||||
print()
|
||||
tot_time += (t2-t1)
|
||||
print("Total time:", tot_time)
|
||||
print("Errors:", errcount)
|
||||
@@ -0,0 +1,30 @@
|
||||
from mpmath import zetazero
|
||||
from timeit import default_timer as clock
|
||||
|
||||
def test_zetazero():
|
||||
cases = [\
|
||||
(399999999, 156762524.6750591511),
|
||||
(241389216, 97490234.2276711795),
|
||||
(526196239, 202950727.691229534),
|
||||
(542964976, 209039046.578535272),
|
||||
(1048449112, 388858885.231056486),
|
||||
(1048449113, 388858885.384337406),
|
||||
(1048449114, 388858886.002285122),
|
||||
(1048449115, 388858886.00239369),
|
||||
(1048449116, 388858886.690745053)
|
||||
]
|
||||
for n, v in cases:
|
||||
print(n, v)
|
||||
t1 = clock()
|
||||
ok = zetazero(n).ae(complex(0.5,v))
|
||||
t2 = clock()
|
||||
print("ok =", ok, ("(time = %s)" % round(t2-t1,3)))
|
||||
print("Now computing two huge zeros (this may take hours)")
|
||||
print("Computing zetazero(8637740722917)")
|
||||
ok = zetazero(8637740722917).ae(complex(0.5,2124447368584.39296466152))
|
||||
print("ok =", ok)
|
||||
ok = zetazero(8637740722918).ae(complex(0.5,2124447368584.39298170604))
|
||||
print("ok =", ok)
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_zetazero()
|
||||
@@ -0,0 +1,161 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
python runtests.py -py
|
||||
Use py.test to run tests (more useful for debugging)
|
||||
|
||||
python runtests.py -coverage
|
||||
Generate test coverage report. Statistics are written to /tmp
|
||||
|
||||
python runtests.py -profile
|
||||
Generate profile stats (this is much slower)
|
||||
|
||||
python runtests.py -nogmpy
|
||||
Run tests without using GMPY even if it exists
|
||||
|
||||
python runtests.py -strict
|
||||
Enforce extra tests in normalize()
|
||||
|
||||
python runtests.py -local
|
||||
Insert '../..' at the beginning of sys.path to use local mpmath
|
||||
|
||||
python runtests.py -skip ...
|
||||
Skip tests from the listed modules
|
||||
|
||||
Additional arguments are used to filter the tests to run. Only files that have
|
||||
one of the arguments in their name are executed.
|
||||
|
||||
"""
|
||||
|
||||
import sys, os, traceback
|
||||
|
||||
profile = False
|
||||
if "-profile" in sys.argv:
|
||||
sys.argv.remove('-profile')
|
||||
profile = True
|
||||
|
||||
coverage = False
|
||||
if "-coverage" in sys.argv:
|
||||
sys.argv.remove('-coverage')
|
||||
coverage = True
|
||||
|
||||
if "-nogmpy" in sys.argv:
|
||||
sys.argv.remove('-nogmpy')
|
||||
os.environ['MPMATH_NOGMPY'] = 'Y'
|
||||
|
||||
if "-strict" in sys.argv:
|
||||
sys.argv.remove('-strict')
|
||||
os.environ['MPMATH_STRICT'] = 'Y'
|
||||
|
||||
if "-local" in sys.argv:
|
||||
sys.argv.remove('-local')
|
||||
importdir = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]),
|
||||
'../..'))
|
||||
else:
|
||||
importdir = ''
|
||||
|
||||
# TODO: add a flag for this
|
||||
testdir = ''
|
||||
|
||||
def testit(importdir='', testdir='', exit_on_fail=False):
|
||||
"""Run all tests in testdir while importing from importdir."""
|
||||
if importdir:
|
||||
sys.path.insert(1, importdir)
|
||||
if testdir:
|
||||
sys.path.insert(1, testdir)
|
||||
import os.path
|
||||
import mpmath
|
||||
print("mpmath imported from %s" % os.path.dirname(mpmath.__file__))
|
||||
print("mpmath backend: %s" % mpmath.libmp.backend.BACKEND)
|
||||
print("mpmath mp class: %s" % repr(mpmath.mp))
|
||||
print("mpmath version: %s" % mpmath.__version__)
|
||||
print("Python version: %s" % sys.version)
|
||||
print("")
|
||||
if "-py" in sys.argv:
|
||||
sys.argv.remove('-py')
|
||||
import py
|
||||
py.test.cmdline.main()
|
||||
else:
|
||||
import glob
|
||||
from timeit import default_timer as clock
|
||||
modules = []
|
||||
args = sys.argv[1:]
|
||||
excluded = []
|
||||
if '-skip' in args:
|
||||
excluded = args[args.index('-skip')+1:]
|
||||
args = args[:args.index('-skip')]
|
||||
# search for tests in directory of this file if not otherwise specified
|
||||
if not testdir:
|
||||
pattern = os.path.dirname(sys.argv[0])
|
||||
else:
|
||||
pattern = testdir
|
||||
if pattern:
|
||||
pattern += '/'
|
||||
pattern += 'test*.py'
|
||||
# look for tests (respecting specified filter)
|
||||
for f in glob.glob(pattern):
|
||||
name = os.path.splitext(os.path.basename(f))[0]
|
||||
# If run as a script, only run tests given as args, if any are given
|
||||
if args and __name__ == "__main__":
|
||||
ok = False
|
||||
for arg in args:
|
||||
if arg in name:
|
||||
ok = True
|
||||
break
|
||||
if not ok:
|
||||
continue
|
||||
elif name in excluded:
|
||||
continue
|
||||
module = __import__(name)
|
||||
priority = module.__dict__.get('priority', 100)
|
||||
if priority == 666:
|
||||
modules = [[priority, name, module]]
|
||||
break
|
||||
modules.append([priority, name, module])
|
||||
# execute tests
|
||||
modules.sort()
|
||||
tstart = clock()
|
||||
for priority, name, module in modules:
|
||||
print(name)
|
||||
for f in sorted(module.__dict__.keys()):
|
||||
if f.startswith('test_'):
|
||||
if coverage and ('numpy' in f):
|
||||
continue
|
||||
sys.stdout.write(" " + f[5:].ljust(25) + " ")
|
||||
t1 = clock()
|
||||
try:
|
||||
module.__dict__[f]()
|
||||
except:
|
||||
etype, evalue, trb = sys.exc_info()
|
||||
if etype in (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
print("")
|
||||
print("TEST FAILED!")
|
||||
print("")
|
||||
traceback.print_exc()
|
||||
if exit_on_fail:
|
||||
return
|
||||
t2 = clock()
|
||||
print("ok " + " " + ("%.7f" % (t2-t1)) + " s")
|
||||
tend = clock()
|
||||
print("")
|
||||
print("finished tests in " + ("%.2f" % (tend-tstart)) + " seconds")
|
||||
# clean sys.path
|
||||
if importdir:
|
||||
sys.path.remove(importdir)
|
||||
if testdir:
|
||||
sys.path.remove(testdir)
|
||||
|
||||
if __name__ == '__main__':
|
||||
if profile:
|
||||
import cProfile
|
||||
cProfile.run("testit('%s', '%s')" % (importdir, testdir), sort=1)
|
||||
elif coverage:
|
||||
import trace
|
||||
tracer = trace.Trace(ignoredirs=[sys.prefix, sys.exec_prefix],
|
||||
trace=0, count=1)
|
||||
tracer.run('testit(importdir, testdir)')
|
||||
r = tracer.results()
|
||||
r.write_results(show_missing=True, summary=True, coverdir="/tmp")
|
||||
else:
|
||||
testit(importdir, testdir)
|
||||
@@ -0,0 +1,451 @@
|
||||
import mpmath
|
||||
from mpmath import *
|
||||
from mpmath.libmp import *
|
||||
import random
|
||||
import sys
|
||||
|
||||
try:
|
||||
long = long
|
||||
except NameError:
|
||||
long = int
|
||||
|
||||
def test_type_compare():
|
||||
assert mpf(2) == mpc(2,0)
|
||||
assert mpf(0) == mpc(0)
|
||||
assert mpf(2) != mpc(2, 0.00001)
|
||||
assert mpf(2) == 2.0
|
||||
assert mpf(2) != 3.0
|
||||
assert mpf(2) == 2
|
||||
assert mpf(2) != '2.0'
|
||||
assert mpc(2) != '2.0'
|
||||
|
||||
def test_add():
|
||||
assert mpf(2.5) + mpf(3) == 5.5
|
||||
assert mpf(2.5) + 3 == 5.5
|
||||
assert mpf(2.5) + 3.0 == 5.5
|
||||
assert 3 + mpf(2.5) == 5.5
|
||||
assert 3.0 + mpf(2.5) == 5.5
|
||||
assert (3+0j) + mpf(2.5) == 5.5
|
||||
assert mpc(2.5) + mpf(3) == 5.5
|
||||
assert mpc(2.5) + 3 == 5.5
|
||||
assert mpc(2.5) + 3.0 == 5.5
|
||||
assert mpc(2.5) + (3+0j) == 5.5
|
||||
assert 3 + mpc(2.5) == 5.5
|
||||
assert 3.0 + mpc(2.5) == 5.5
|
||||
assert (3+0j) + mpc(2.5) == 5.5
|
||||
|
||||
def test_sub():
|
||||
assert mpf(2.5) - mpf(3) == -0.5
|
||||
assert mpf(2.5) - 3 == -0.5
|
||||
assert mpf(2.5) - 3.0 == -0.5
|
||||
assert 3 - mpf(2.5) == 0.5
|
||||
assert 3.0 - mpf(2.5) == 0.5
|
||||
assert (3+0j) - mpf(2.5) == 0.5
|
||||
assert mpc(2.5) - mpf(3) == -0.5
|
||||
assert mpc(2.5) - 3 == -0.5
|
||||
assert mpc(2.5) - 3.0 == -0.5
|
||||
assert mpc(2.5) - (3+0j) == -0.5
|
||||
assert 3 - mpc(2.5) == 0.5
|
||||
assert 3.0 - mpc(2.5) == 0.5
|
||||
assert (3+0j) - mpc(2.5) == 0.5
|
||||
|
||||
def test_mul():
|
||||
assert mpf(2.5) * mpf(3) == 7.5
|
||||
assert mpf(2.5) * 3 == 7.5
|
||||
assert mpf(2.5) * 3.0 == 7.5
|
||||
assert 3 * mpf(2.5) == 7.5
|
||||
assert 3.0 * mpf(2.5) == 7.5
|
||||
assert (3+0j) * mpf(2.5) == 7.5
|
||||
assert mpc(2.5) * mpf(3) == 7.5
|
||||
assert mpc(2.5) * 3 == 7.5
|
||||
assert mpc(2.5) * 3.0 == 7.5
|
||||
assert mpc(2.5) * (3+0j) == 7.5
|
||||
assert 3 * mpc(2.5) == 7.5
|
||||
assert 3.0 * mpc(2.5) == 7.5
|
||||
assert (3+0j) * mpc(2.5) == 7.5
|
||||
|
||||
def test_div():
|
||||
assert mpf(6) / mpf(3) == 2.0
|
||||
assert mpf(6) / 3 == 2.0
|
||||
assert mpf(6) / 3.0 == 2.0
|
||||
assert 6 / mpf(3) == 2.0
|
||||
assert 6.0 / mpf(3) == 2.0
|
||||
assert (6+0j) / mpf(3.0) == 2.0
|
||||
assert mpc(6) / mpf(3) == 2.0
|
||||
assert mpc(6) / 3 == 2.0
|
||||
assert mpc(6) / 3.0 == 2.0
|
||||
assert mpc(6) / (3+0j) == 2.0
|
||||
assert 6 / mpc(3) == 2.0
|
||||
assert 6.0 / mpc(3) == 2.0
|
||||
assert (6+0j) / mpc(3) == 2.0
|
||||
|
||||
def test_pow():
|
||||
assert mpf(6) ** mpf(3) == 216.0
|
||||
assert mpf(6) ** 3 == 216.0
|
||||
assert mpf(6) ** 3.0 == 216.0
|
||||
assert 6 ** mpf(3) == 216.0
|
||||
assert 6.0 ** mpf(3) == 216.0
|
||||
assert (6+0j) ** mpf(3.0) == 216.0
|
||||
assert mpc(6) ** mpf(3) == 216.0
|
||||
assert mpc(6) ** 3 == 216.0
|
||||
assert mpc(6) ** 3.0 == 216.0
|
||||
assert mpc(6) ** (3+0j) == 216.0
|
||||
assert 6 ** mpc(3) == 216.0
|
||||
assert 6.0 ** mpc(3) == 216.0
|
||||
assert (6+0j) ** mpc(3) == 216.0
|
||||
|
||||
def test_mixed_misc():
|
||||
assert 1 + mpf(3) == mpf(3) + 1 == 4
|
||||
assert 1 - mpf(3) == -(mpf(3) - 1) == -2
|
||||
assert 3 * mpf(2) == mpf(2) * 3 == 6
|
||||
assert 6 / mpf(2) == mpf(6) / 2 == 3
|
||||
assert 1.0 + mpf(3) == mpf(3) + 1.0 == 4
|
||||
assert 1.0 - mpf(3) == -(mpf(3) - 1.0) == -2
|
||||
assert 3.0 * mpf(2) == mpf(2) * 3.0 == 6
|
||||
assert 6.0 / mpf(2) == mpf(6) / 2.0 == 3
|
||||
|
||||
def test_add_misc():
|
||||
mp.dps = 15
|
||||
assert mpf(4) + mpf(-70) == -66
|
||||
assert mpf(1) + mpf(1.1)/80 == 1 + 1.1/80
|
||||
assert mpf((1, 10000000000)) + mpf(3) == mpf((1, 10000000000))
|
||||
assert mpf(3) + mpf((1, 10000000000)) == mpf((1, 10000000000))
|
||||
assert mpf((1, -10000000000)) + mpf(3) == mpf(3)
|
||||
assert mpf(3) + mpf((1, -10000000000)) == mpf(3)
|
||||
assert mpf(1) + 1e-15 != 1
|
||||
assert mpf(1) + 1e-20 == 1
|
||||
assert mpf(1.07e-22) + 0 == mpf(1.07e-22)
|
||||
assert mpf(0) + mpf(1.07e-22) == mpf(1.07e-22)
|
||||
|
||||
def test_complex_misc():
|
||||
# many more tests needed
|
||||
assert 1 + mpc(2) == 3
|
||||
assert not mpc(2).ae(2 + 1e-13)
|
||||
assert mpc(2+1e-15j).ae(2)
|
||||
|
||||
def test_complex_zeros():
|
||||
for a in [0,2]:
|
||||
for b in [0,3]:
|
||||
for c in [0,4]:
|
||||
for d in [0,5]:
|
||||
assert mpc(a,b)*mpc(c,d) == complex(a,b)*complex(c,d)
|
||||
|
||||
def test_hash():
|
||||
for i in range(-256, 256):
|
||||
assert hash(mpf(i)) == hash(i)
|
||||
assert hash(mpf(0.5)) == hash(0.5)
|
||||
assert hash(mpc(2,3)) == hash(2+3j)
|
||||
# Check that this doesn't fail
|
||||
assert hash(inf)
|
||||
# Check that overflow doesn't assign equal hashes to large numbers
|
||||
assert hash(mpf('1e1000')) != hash('1e10000')
|
||||
assert hash(mpc(100,'1e1000')) != hash(mpc(200,'1e1000'))
|
||||
from mpmath.rational import mpq
|
||||
assert hash(mp.mpq(1,3))
|
||||
assert hash(mp.mpq(0,1)) == 0
|
||||
assert hash(mp.mpq(-1,1)) == hash(-1)
|
||||
assert hash(mp.mpq(1,1)) == hash(1)
|
||||
assert hash(mp.mpq(5,1)) == hash(5)
|
||||
assert hash(mp.mpq(1,2)) == hash(0.5)
|
||||
if sys.version_info >= (3, 2):
|
||||
assert hash(mpf(1)*2**2000) == hash(2**2000)
|
||||
assert hash(mpf(1)/2**2000) == hash(mpq(1,2**2000))
|
||||
|
||||
# Advanced rounding test
|
||||
def test_add_rounding():
|
||||
mp.dps = 15
|
||||
a = from_float(1e-50)
|
||||
assert mpf_sub(mpf_add(fone, a, 53, round_up), fone, 53, round_up) == from_float(2.2204460492503131e-16)
|
||||
assert mpf_sub(fone, a, 53, round_up) == fone
|
||||
assert mpf_sub(fone, mpf_sub(fone, a, 53, round_down), 53, round_down) == from_float(1.1102230246251565e-16)
|
||||
assert mpf_add(fone, a, 53, round_down) == fone
|
||||
|
||||
def test_almost_equal():
|
||||
assert mpf(1.2).ae(mpf(1.20000001), 1e-7)
|
||||
assert not mpf(1.2).ae(mpf(1.20000001), 1e-9)
|
||||
assert not mpf(-0.7818314824680298).ae(mpf(-0.774695868667929))
|
||||
|
||||
def test_arithmetic_functions():
|
||||
import operator
|
||||
ops = [(operator.add, fadd), (operator.sub, fsub), (operator.mul, fmul),
|
||||
(operator.truediv, fdiv)]
|
||||
a = mpf(0.27)
|
||||
b = mpf(1.13)
|
||||
c = mpc(0.51+2.16j)
|
||||
d = mpc(1.08-0.99j)
|
||||
for x in [a,b,c,d]:
|
||||
for y in [a,b,c,d]:
|
||||
for op, fop in ops:
|
||||
if fop is not fdiv:
|
||||
mp.prec = 200
|
||||
z0 = op(x,y)
|
||||
mp.prec = 60
|
||||
z1 = op(x,y)
|
||||
mp.prec = 53
|
||||
z2 = op(x,y)
|
||||
assert fop(x, y, prec=60) == z1
|
||||
assert fop(x, y) == z2
|
||||
if fop is not fdiv:
|
||||
assert fop(x, y, prec=inf) == z0
|
||||
assert fop(x, y, dps=inf) == z0
|
||||
assert fop(x, y, exact=True) == z0
|
||||
assert fneg(fneg(z1, exact=True), prec=inf) == z1
|
||||
assert fneg(z1) == -(+z1)
|
||||
mp.dps = 15
|
||||
|
||||
def test_exact_integer_arithmetic():
|
||||
# XXX: re-fix this so that all operations are tested with all rounding modes
|
||||
random.seed(0)
|
||||
for prec in [6, 10, 25, 40, 100, 250, 725]:
|
||||
for rounding in ['d', 'u', 'f', 'c', 'n']:
|
||||
mp.dps = prec
|
||||
M = 10**(prec-2)
|
||||
M2 = 10**(prec//2-2)
|
||||
for i in range(10):
|
||||
a = random.randint(-M, M)
|
||||
b = random.randint(-M, M)
|
||||
assert mpf(a, rounding=rounding) == a
|
||||
assert int(mpf(a, rounding=rounding)) == a
|
||||
assert int(mpf(str(a), rounding=rounding)) == a
|
||||
assert mpf(a) + mpf(b) == a + b
|
||||
assert mpf(a) - mpf(b) == a - b
|
||||
assert -mpf(a) == -a
|
||||
a = random.randint(-M2, M2)
|
||||
b = random.randint(-M2, M2)
|
||||
assert mpf(a) * mpf(b) == a*b
|
||||
assert mpf_mul(from_int(a), from_int(b), mp.prec, rounding) == from_int(a*b)
|
||||
mp.dps = 15
|
||||
|
||||
def test_odd_int_bug():
|
||||
assert to_int(from_int(3), round_nearest) == 3
|
||||
|
||||
def test_str_1000_digits():
|
||||
mp.dps = 1001
|
||||
# last digit may be wrong
|
||||
assert str(mpf(2)**0.5)[-10:-1] == '9518488472'[:9]
|
||||
assert str(pi)[-10:-1] == '2164201989'[:9]
|
||||
mp.dps = 15
|
||||
|
||||
def test_str_10000_digits():
|
||||
mp.dps = 10001
|
||||
# last digit may be wrong
|
||||
assert str(mpf(2)**0.5)[-10:-1] == '5873258351'[:9]
|
||||
assert str(pi)[-10:-1] == '5256375678'[:9]
|
||||
mp.dps = 15
|
||||
|
||||
def test_monitor():
|
||||
f = lambda x: x**2
|
||||
a = []
|
||||
b = []
|
||||
g = monitor(f, a.append, b.append)
|
||||
assert g(3) == 9
|
||||
assert g(4) == 16
|
||||
assert a[0] == ((3,), {})
|
||||
assert b[0] == 9
|
||||
|
||||
def test_nint_distance():
|
||||
assert nint_distance(mpf(-3)) == (-3, -inf)
|
||||
assert nint_distance(mpc(-3)) == (-3, -inf)
|
||||
assert nint_distance(mpf(-3.1)) == (-3, -3)
|
||||
assert nint_distance(mpf(-3.01)) == (-3, -6)
|
||||
assert nint_distance(mpf(-3.001)) == (-3, -9)
|
||||
assert nint_distance(mpf(-3.0001)) == (-3, -13)
|
||||
assert nint_distance(mpf(-2.9)) == (-3, -3)
|
||||
assert nint_distance(mpf(-2.99)) == (-3, -6)
|
||||
assert nint_distance(mpf(-2.999)) == (-3, -9)
|
||||
assert nint_distance(mpf(-2.9999)) == (-3, -13)
|
||||
assert nint_distance(mpc(-3+0.1j)) == (-3, -3)
|
||||
assert nint_distance(mpc(-3+0.01j)) == (-3, -6)
|
||||
assert nint_distance(mpc(-3.1+0.1j)) == (-3, -3)
|
||||
assert nint_distance(mpc(-3.01+0.01j)) == (-3, -6)
|
||||
assert nint_distance(mpc(-3.001+0.001j)) == (-3, -9)
|
||||
assert nint_distance(mpf(0)) == (0, -inf)
|
||||
assert nint_distance(mpf(0.01)) == (0, -6)
|
||||
assert nint_distance(mpf('1e-100')) == (0, -332)
|
||||
|
||||
def test_floor_ceil_nint_frac():
|
||||
mp.dps = 15
|
||||
for n in range(-10,10):
|
||||
assert floor(n) == n
|
||||
assert floor(n+0.5) == n
|
||||
assert ceil(n) == n
|
||||
assert ceil(n+0.5) == n+1
|
||||
assert nint(n) == n
|
||||
# nint rounds to even
|
||||
if n % 2 == 1:
|
||||
assert nint(n+0.5) == n+1
|
||||
else:
|
||||
assert nint(n+0.5) == n
|
||||
assert floor(inf) == inf
|
||||
assert floor(ninf) == ninf
|
||||
assert isnan(floor(nan))
|
||||
assert ceil(inf) == inf
|
||||
assert ceil(ninf) == ninf
|
||||
assert isnan(ceil(nan))
|
||||
assert nint(inf) == inf
|
||||
assert nint(ninf) == ninf
|
||||
assert isnan(nint(nan))
|
||||
assert floor(0.1) == 0
|
||||
assert floor(0.9) == 0
|
||||
assert floor(-0.1) == -1
|
||||
assert floor(-0.9) == -1
|
||||
assert floor(10000000000.1) == 10000000000
|
||||
assert floor(10000000000.9) == 10000000000
|
||||
assert floor(-10000000000.1) == -10000000000-1
|
||||
assert floor(-10000000000.9) == -10000000000-1
|
||||
assert floor(1e-100) == 0
|
||||
assert floor(-1e-100) == -1
|
||||
assert floor(1e100) == 1e100
|
||||
assert floor(-1e100) == -1e100
|
||||
assert ceil(0.1) == 1
|
||||
assert ceil(0.9) == 1
|
||||
assert ceil(-0.1) == 0
|
||||
assert ceil(-0.9) == 0
|
||||
assert ceil(10000000000.1) == 10000000000+1
|
||||
assert ceil(10000000000.9) == 10000000000+1
|
||||
assert ceil(-10000000000.1) == -10000000000
|
||||
assert ceil(-10000000000.9) == -10000000000
|
||||
assert ceil(1e-100) == 1
|
||||
assert ceil(-1e-100) == 0
|
||||
assert ceil(1e100) == 1e100
|
||||
assert ceil(-1e100) == -1e100
|
||||
assert nint(0.1) == 0
|
||||
assert nint(0.9) == 1
|
||||
assert nint(-0.1) == 0
|
||||
assert nint(-0.9) == -1
|
||||
assert nint(10000000000.1) == 10000000000
|
||||
assert nint(10000000000.9) == 10000000000+1
|
||||
assert nint(-10000000000.1) == -10000000000
|
||||
assert nint(-10000000000.9) == -10000000000-1
|
||||
assert nint(1e-100) == 0
|
||||
assert nint(-1e-100) == 0
|
||||
assert nint(1e100) == 1e100
|
||||
assert nint(-1e100) == -1e100
|
||||
assert floor(3.2+4.6j) == 3+4j
|
||||
assert ceil(3.2+4.6j) == 4+5j
|
||||
assert nint(3.2+4.6j) == 3+5j
|
||||
for n in range(-10,10):
|
||||
assert frac(n) == 0
|
||||
assert frac(0.25) == 0.25
|
||||
assert frac(1.25) == 0.25
|
||||
assert frac(2.25) == 0.25
|
||||
assert frac(-0.25) == 0.75
|
||||
assert frac(-1.25) == 0.75
|
||||
assert frac(-2.25) == 0.75
|
||||
assert frac('1e100000000000000') == 0
|
||||
u = mpf('1e-100000000000000')
|
||||
assert frac(u) == u
|
||||
assert frac(-u) == 1 # rounding!
|
||||
u = mpf('1e-400')
|
||||
assert frac(-u, prec=0) == fsub(1, u, exact=True)
|
||||
assert frac(3.25+4.75j) == 0.25+0.75j
|
||||
|
||||
def test_isnan_etc():
|
||||
from mpmath.rational import mpq
|
||||
assert isnan(nan) == True
|
||||
assert isnan(3) == False
|
||||
assert isnan(mpf(3)) == False
|
||||
assert isnan(inf) == False
|
||||
assert isnan(mpc(2,nan)) == True
|
||||
assert isnan(mpc(2,nan)) == True
|
||||
assert isnan(mpc(nan,nan)) == True
|
||||
assert isnan(mpc(2,2)) == False
|
||||
assert isnan(mpc(nan,inf)) == True
|
||||
assert isnan(mpc(inf,inf)) == False
|
||||
assert isnan(mpq((3,2))) == False
|
||||
assert isnan(mpq((0,1))) == False
|
||||
assert isinf(inf) == True
|
||||
assert isinf(-inf) == True
|
||||
assert isinf(3) == False
|
||||
assert isinf(nan) == False
|
||||
assert isinf(3+4j) == False
|
||||
assert isinf(mpc(inf)) == True
|
||||
assert isinf(mpc(3,inf)) == True
|
||||
assert isinf(mpc(inf,3)) == True
|
||||
assert isinf(mpc(inf,inf)) == True
|
||||
assert isinf(mpc(nan,inf)) == True
|
||||
assert isinf(mpc(inf,nan)) == True
|
||||
assert isinf(mpc(nan,nan)) == False
|
||||
assert isinf(mpq((3,2))) == False
|
||||
assert isinf(mpq((0,1))) == False
|
||||
assert isnormal(3) == True
|
||||
assert isnormal(3.5) == True
|
||||
assert isnormal(mpf(3.5)) == True
|
||||
assert isnormal(0) == False
|
||||
assert isnormal(mpf(0)) == False
|
||||
assert isnormal(0.0) == False
|
||||
assert isnormal(inf) == False
|
||||
assert isnormal(-inf) == False
|
||||
assert isnormal(nan) == False
|
||||
assert isnormal(float(inf)) == False
|
||||
assert isnormal(mpc(0,0)) == False
|
||||
assert isnormal(mpc(3,0)) == True
|
||||
assert isnormal(mpc(0,3)) == True
|
||||
assert isnormal(mpc(3,3)) == True
|
||||
assert isnormal(mpc(0,nan)) == False
|
||||
assert isnormal(mpc(0,inf)) == False
|
||||
assert isnormal(mpc(3,nan)) == False
|
||||
assert isnormal(mpc(3,inf)) == False
|
||||
assert isnormal(mpc(3,-inf)) == False
|
||||
assert isnormal(mpc(nan,0)) == False
|
||||
assert isnormal(mpc(inf,0)) == False
|
||||
assert isnormal(mpc(nan,3)) == False
|
||||
assert isnormal(mpc(inf,3)) == False
|
||||
assert isnormal(mpc(inf,nan)) == False
|
||||
assert isnormal(mpc(nan,inf)) == False
|
||||
assert isnormal(mpc(nan,nan)) == False
|
||||
assert isnormal(mpc(inf,inf)) == False
|
||||
assert isnormal(mpq((3,2))) == True
|
||||
assert isnormal(mpq((0,1))) == False
|
||||
assert isint(3) == True
|
||||
assert isint(0) == True
|
||||
assert isint(long(3)) == True
|
||||
assert isint(long(0)) == True
|
||||
assert isint(mpf(3)) == True
|
||||
assert isint(mpf(0)) == True
|
||||
assert isint(mpf(-3)) == True
|
||||
assert isint(mpf(3.2)) == False
|
||||
assert isint(3.2) == False
|
||||
assert isint(nan) == False
|
||||
assert isint(inf) == False
|
||||
assert isint(-inf) == False
|
||||
assert isint(mpc(0)) == True
|
||||
assert isint(mpc(3)) == True
|
||||
assert isint(mpc(3.2)) == False
|
||||
assert isint(mpc(3,inf)) == False
|
||||
assert isint(mpc(inf)) == False
|
||||
assert isint(mpc(3,2)) == False
|
||||
assert isint(mpc(0,2)) == False
|
||||
assert isint(mpc(3,2),gaussian=True) == True
|
||||
assert isint(mpc(3,0),gaussian=True) == True
|
||||
assert isint(mpc(0,3),gaussian=True) == True
|
||||
assert isint(3+4j) == False
|
||||
assert isint(3+4j, gaussian=True) == True
|
||||
assert isint(3+0j) == True
|
||||
assert isint(mpq((3,2))) == False
|
||||
assert isint(mpq((3,9))) == False
|
||||
assert isint(mpq((9,3))) == True
|
||||
assert isint(mpq((0,4))) == True
|
||||
assert isint(mpq((1,1))) == True
|
||||
assert isint(mpq((-1,1))) == True
|
||||
assert mp.isnpint(0) == True
|
||||
assert mp.isnpint(1) == False
|
||||
assert mp.isnpint(-1) == True
|
||||
assert mp.isnpint(-1.1) == False
|
||||
assert mp.isnpint(-1.0) == True
|
||||
assert mp.isnpint(mp.mpq(1,2)) == False
|
||||
assert mp.isnpint(mp.mpq(-1,2)) == False
|
||||
assert mp.isnpint(mp.mpq(-3,1)) == True
|
||||
assert mp.isnpint(mp.mpq(0,1)) == True
|
||||
assert mp.isnpint(mp.mpq(1,1)) == False
|
||||
assert mp.isnpint(0+0j) == True
|
||||
assert mp.isnpint(-1+0j) == True
|
||||
assert mp.isnpint(-1.1+0j) == False
|
||||
assert mp.isnpint(-1+0.1j) == False
|
||||
assert mp.isnpint(0+0.1j) == False
|
||||
|
||||
|
||||
def test_issue_438():
|
||||
assert mpf(finf) == mpf('inf')
|
||||
assert mpf(fninf) == mpf('-inf')
|
||||
assert mpf(fnan)._mpf_ == mpf('nan')._mpf_
|
||||
@@ -0,0 +1,188 @@
|
||||
"""
|
||||
Test bit-level integer and mpf operations
|
||||
"""
|
||||
|
||||
from mpmath import *
|
||||
from mpmath.libmp import *
|
||||
|
||||
def test_bitcount():
|
||||
assert bitcount(0) == 0
|
||||
assert bitcount(1) == 1
|
||||
assert bitcount(7) == 3
|
||||
assert bitcount(8) == 4
|
||||
assert bitcount(2**100) == 101
|
||||
assert bitcount(2**100-1) == 100
|
||||
|
||||
def test_trailing():
|
||||
assert trailing(0) == 0
|
||||
assert trailing(1) == 0
|
||||
assert trailing(2) == 1
|
||||
assert trailing(7) == 0
|
||||
assert trailing(8) == 3
|
||||
assert trailing(2**100) == 100
|
||||
assert trailing(2**100-1) == 0
|
||||
|
||||
def test_round_down():
|
||||
assert from_man_exp(0, -4, 4, round_down)[:3] == (0, 0, 0)
|
||||
assert from_man_exp(0xf0, -4, 4, round_down)[:3] == (0, 15, 0)
|
||||
assert from_man_exp(0xf1, -4, 4, round_down)[:3] == (0, 15, 0)
|
||||
assert from_man_exp(0xff, -4, 4, round_down)[:3] == (0, 15, 0)
|
||||
assert from_man_exp(-0xf0, -4, 4, round_down)[:3] == (1, 15, 0)
|
||||
assert from_man_exp(-0xf1, -4, 4, round_down)[:3] == (1, 15, 0)
|
||||
assert from_man_exp(-0xff, -4, 4, round_down)[:3] == (1, 15, 0)
|
||||
|
||||
def test_round_up():
|
||||
assert from_man_exp(0, -4, 4, round_up)[:3] == (0, 0, 0)
|
||||
assert from_man_exp(0xf0, -4, 4, round_up)[:3] == (0, 15, 0)
|
||||
assert from_man_exp(0xf1, -4, 4, round_up)[:3] == (0, 1, 4)
|
||||
assert from_man_exp(0xff, -4, 4, round_up)[:3] == (0, 1, 4)
|
||||
assert from_man_exp(-0xf0, -4, 4, round_up)[:3] == (1, 15, 0)
|
||||
assert from_man_exp(-0xf1, -4, 4, round_up)[:3] == (1, 1, 4)
|
||||
assert from_man_exp(-0xff, -4, 4, round_up)[:3] == (1, 1, 4)
|
||||
|
||||
def test_round_floor():
|
||||
assert from_man_exp(0, -4, 4, round_floor)[:3] == (0, 0, 0)
|
||||
assert from_man_exp(0xf0, -4, 4, round_floor)[:3] == (0, 15, 0)
|
||||
assert from_man_exp(0xf1, -4, 4, round_floor)[:3] == (0, 15, 0)
|
||||
assert from_man_exp(0xff, -4, 4, round_floor)[:3] == (0, 15, 0)
|
||||
assert from_man_exp(-0xf0, -4, 4, round_floor)[:3] == (1, 15, 0)
|
||||
assert from_man_exp(-0xf1, -4, 4, round_floor)[:3] == (1, 1, 4)
|
||||
assert from_man_exp(-0xff, -4, 4, round_floor)[:3] == (1, 1, 4)
|
||||
|
||||
def test_round_ceiling():
|
||||
assert from_man_exp(0, -4, 4, round_ceiling)[:3] == (0, 0, 0)
|
||||
assert from_man_exp(0xf0, -4, 4, round_ceiling)[:3] == (0, 15, 0)
|
||||
assert from_man_exp(0xf1, -4, 4, round_ceiling)[:3] == (0, 1, 4)
|
||||
assert from_man_exp(0xff, -4, 4, round_ceiling)[:3] == (0, 1, 4)
|
||||
assert from_man_exp(-0xf0, -4, 4, round_ceiling)[:3] == (1, 15, 0)
|
||||
assert from_man_exp(-0xf1, -4, 4, round_ceiling)[:3] == (1, 15, 0)
|
||||
assert from_man_exp(-0xff, -4, 4, round_ceiling)[:3] == (1, 15, 0)
|
||||
|
||||
def test_round_nearest():
|
||||
assert from_man_exp(0, -4, 4, round_nearest)[:3] == (0, 0, 0)
|
||||
assert from_man_exp(0xf0, -4, 4, round_nearest)[:3] == (0, 15, 0)
|
||||
assert from_man_exp(0xf7, -4, 4, round_nearest)[:3] == (0, 15, 0)
|
||||
assert from_man_exp(0xf8, -4, 4, round_nearest)[:3] == (0, 1, 4) # 1111.1000 -> 10000.0
|
||||
assert from_man_exp(0xf9, -4, 4, round_nearest)[:3] == (0, 1, 4) # 1111.1001 -> 10000.0
|
||||
assert from_man_exp(0xe8, -4, 4, round_nearest)[:3] == (0, 7, 1) # 1110.1000 -> 1110.0
|
||||
assert from_man_exp(0xe9, -4, 4, round_nearest)[:3] == (0, 15, 0) # 1110.1001 -> 1111.0
|
||||
assert from_man_exp(-0xf0, -4, 4, round_nearest)[:3] == (1, 15, 0)
|
||||
assert from_man_exp(-0xf7, -4, 4, round_nearest)[:3] == (1, 15, 0)
|
||||
assert from_man_exp(-0xf8, -4, 4, round_nearest)[:3] == (1, 1, 4)
|
||||
assert from_man_exp(-0xf9, -4, 4, round_nearest)[:3] == (1, 1, 4)
|
||||
assert from_man_exp(-0xe8, -4, 4, round_nearest)[:3] == (1, 7, 1)
|
||||
assert from_man_exp(-0xe9, -4, 4, round_nearest)[:3] == (1, 15, 0)
|
||||
|
||||
def test_rounding_bugs():
|
||||
# 1 less than power-of-two cases
|
||||
assert from_man_exp(72057594037927935, -56, 53, round_up) == (0, 1, 0, 1)
|
||||
assert from_man_exp(73786976294838205979, -65, 53, round_nearest) == (0, 1, 1, 1)
|
||||
assert from_man_exp(31, 0, 4, round_up) == (0, 1, 5, 1)
|
||||
assert from_man_exp(-31, 0, 4, round_floor) == (1, 1, 5, 1)
|
||||
assert from_man_exp(255, 0, 7, round_up) == (0, 1, 8, 1)
|
||||
assert from_man_exp(-255, 0, 7, round_floor) == (1, 1, 8, 1)
|
||||
|
||||
def test_rounding_issue_200():
|
||||
a = from_man_exp(9867,-100)
|
||||
b = from_man_exp(9867,-200)
|
||||
c = from_man_exp(-1,0)
|
||||
z = (1, 1023, -10, 10)
|
||||
assert mpf_add(a, c, 10, 'd') == z
|
||||
assert mpf_add(b, c, 10, 'd') == z
|
||||
assert mpf_add(c, a, 10, 'd') == z
|
||||
assert mpf_add(c, b, 10, 'd') == z
|
||||
|
||||
def test_perturb():
|
||||
a = fone
|
||||
b = from_float(0.99999999999999989)
|
||||
c = from_float(1.0000000000000002)
|
||||
assert mpf_perturb(a, 0, 53, round_nearest) == a
|
||||
assert mpf_perturb(a, 1, 53, round_nearest) == a
|
||||
assert mpf_perturb(a, 0, 53, round_up) == c
|
||||
assert mpf_perturb(a, 0, 53, round_ceiling) == c
|
||||
assert mpf_perturb(a, 0, 53, round_down) == a
|
||||
assert mpf_perturb(a, 0, 53, round_floor) == a
|
||||
assert mpf_perturb(a, 1, 53, round_up) == a
|
||||
assert mpf_perturb(a, 1, 53, round_ceiling) == a
|
||||
assert mpf_perturb(a, 1, 53, round_down) == b
|
||||
assert mpf_perturb(a, 1, 53, round_floor) == b
|
||||
a = mpf_neg(a)
|
||||
b = mpf_neg(b)
|
||||
c = mpf_neg(c)
|
||||
assert mpf_perturb(a, 0, 53, round_nearest) == a
|
||||
assert mpf_perturb(a, 1, 53, round_nearest) == a
|
||||
assert mpf_perturb(a, 0, 53, round_up) == a
|
||||
assert mpf_perturb(a, 0, 53, round_floor) == a
|
||||
assert mpf_perturb(a, 0, 53, round_down) == b
|
||||
assert mpf_perturb(a, 0, 53, round_ceiling) == b
|
||||
assert mpf_perturb(a, 1, 53, round_up) == c
|
||||
assert mpf_perturb(a, 1, 53, round_floor) == c
|
||||
assert mpf_perturb(a, 1, 53, round_down) == a
|
||||
assert mpf_perturb(a, 1, 53, round_ceiling) == a
|
||||
|
||||
def test_add_exact():
|
||||
ff = from_float
|
||||
assert mpf_add(ff(3.0), ff(2.5)) == ff(5.5)
|
||||
assert mpf_add(ff(3.0), ff(-2.5)) == ff(0.5)
|
||||
assert mpf_add(ff(-3.0), ff(2.5)) == ff(-0.5)
|
||||
assert mpf_add(ff(-3.0), ff(-2.5)) == ff(-5.5)
|
||||
assert mpf_sub(mpf_add(fone, ff(1e-100)), fone) == ff(1e-100)
|
||||
assert mpf_sub(mpf_add(ff(1e-100), fone), fone) == ff(1e-100)
|
||||
assert mpf_sub(mpf_add(fone, ff(-1e-100)), fone) == ff(-1e-100)
|
||||
assert mpf_sub(mpf_add(ff(-1e-100), fone), fone) == ff(-1e-100)
|
||||
assert mpf_add(fone, fzero) == fone
|
||||
assert mpf_add(fzero, fone) == fone
|
||||
assert mpf_add(fzero, fzero) == fzero
|
||||
|
||||
def test_long_exponent_shifts():
|
||||
mp.dps = 15
|
||||
# Check for possible bugs due to exponent arithmetic overflow
|
||||
# in a C implementation
|
||||
x = mpf(1)
|
||||
for p in [32, 64]:
|
||||
a = ldexp(1,2**(p-1))
|
||||
b = ldexp(1,2**p)
|
||||
c = ldexp(1,2**(p+1))
|
||||
d = ldexp(1,-2**(p-1))
|
||||
e = ldexp(1,-2**p)
|
||||
f = ldexp(1,-2**(p+1))
|
||||
assert (x+a) == a
|
||||
assert (x+b) == b
|
||||
assert (x+c) == c
|
||||
assert (x+d) == x
|
||||
assert (x+e) == x
|
||||
assert (x+f) == x
|
||||
assert (a+x) == a
|
||||
assert (b+x) == b
|
||||
assert (c+x) == c
|
||||
assert (d+x) == x
|
||||
assert (e+x) == x
|
||||
assert (f+x) == x
|
||||
assert (x-a) == -a
|
||||
assert (x-b) == -b
|
||||
assert (x-c) == -c
|
||||
assert (x-d) == x
|
||||
assert (x-e) == x
|
||||
assert (x-f) == x
|
||||
assert (a-x) == a
|
||||
assert (b-x) == b
|
||||
assert (c-x) == c
|
||||
assert (d-x) == -x
|
||||
assert (e-x) == -x
|
||||
assert (f-x) == -x
|
||||
|
||||
def test_float_rounding():
|
||||
mp.prec = 64
|
||||
for x in [mpf(1), mpf(1)+eps, mpf(1)-eps, -mpf(1)+eps, -mpf(1)-eps]:
|
||||
fa = float(x)
|
||||
fb = float(fadd(x,0,prec=53,rounding='n'))
|
||||
assert fa == fb
|
||||
z = mpc(x,x)
|
||||
ca = complex(z)
|
||||
cb = complex(fadd(z,0,prec=53,rounding='n'))
|
||||
assert ca == cb
|
||||
for rnd in ['n', 'd', 'u', 'f', 'c']:
|
||||
fa = to_float(x._mpf_, rnd=rnd)
|
||||
fb = to_float(fadd(x,0,prec=53,rounding=rnd)._mpf_, rnd=rnd)
|
||||
assert fa == fb
|
||||
mp.prec = 53
|
||||
@@ -0,0 +1,216 @@
|
||||
import pytest
|
||||
from mpmath import *
|
||||
|
||||
def test_approximation():
|
||||
mp.dps = 15
|
||||
f = lambda x: cos(2-2*x)/x
|
||||
p, err = chebyfit(f, [2, 4], 8, error=True)
|
||||
assert err < 1e-5
|
||||
for i in range(10):
|
||||
x = 2 + i/5.
|
||||
assert abs(polyval(p, x) - f(x)) < err
|
||||
|
||||
def test_limits():
|
||||
mp.dps = 15
|
||||
assert limit(lambda x: (x-sin(x))/x**3, 0).ae(mpf(1)/6)
|
||||
assert limit(lambda n: (1+1/n)**n, inf).ae(e)
|
||||
|
||||
def test_polyval():
|
||||
assert polyval([], 3) == 0
|
||||
assert polyval([0], 3) == 0
|
||||
assert polyval([5], 3) == 5
|
||||
# 4x^3 - 2x + 5
|
||||
p = [4, 0, -2, 5]
|
||||
assert polyval(p,4) == 253
|
||||
assert polyval(p,4,derivative=True) == (253, 190)
|
||||
|
||||
def test_polyroots():
|
||||
p = polyroots([1,-4])
|
||||
assert p[0].ae(4)
|
||||
p, q = polyroots([1,2,3])
|
||||
assert p.ae(-1 - sqrt(2)*j)
|
||||
assert q.ae(-1 + sqrt(2)*j)
|
||||
#this is not a real test, it only tests a specific case
|
||||
assert polyroots([1]) == []
|
||||
pytest.raises(ValueError, lambda: polyroots([0]))
|
||||
|
||||
def test_polyroots_legendre():
|
||||
n = 64
|
||||
coeffs = [11975573020964041433067793888190275875, 0,
|
||||
-190100434726484311252477736051902332000, 0,
|
||||
1437919688271127330313741595496589239248, 0,
|
||||
-6897338342113537600691931230430793911840, 0,
|
||||
23556405536185284408974715545252277554280, 0,
|
||||
-60969520211303089058522793175947071316960, 0,
|
||||
124284021969194758465450309166353645376880, 0,
|
||||
-204721258548015217049921875719981284186016, 0,
|
||||
277415422258095841688223780704620656114900, 0,
|
||||
-313237834141273382807123548182995095192800, 0,
|
||||
297432255354328395601259515935229287637200, 0,
|
||||
-239057700565161140389797367947941296605600, 0,
|
||||
163356095386193445933028201431093219347160, 0,
|
||||
-95158890516229191805647495979277603503200, 0,
|
||||
47310254620162038075933656063247634556400, 0,
|
||||
-20071017111583894941305187420771723751200, 0,
|
||||
7255051932731034189479516844750603752850, 0,
|
||||
-2228176940331017311443863996901733412640, 0,
|
||||
579006552594977616773047095969088431600, 0,
|
||||
-126584428502545713788439446082310831200, 0,
|
||||
23112325428835593809686977515028663000, 0,
|
||||
-3491517141958743235617737161547844000, 0,
|
||||
431305058712550634988073414073557200, 0,
|
||||
-42927166660756742088912492757452000, 0,
|
||||
3378527005707706553294038781836500, 0,
|
||||
-205277590220215081719131470288800, 0,
|
||||
9330799555464321896324157740400, 0,
|
||||
-304114948474392713657972548576, 0,
|
||||
6695289961520387531608984680, 0,
|
||||
-91048139350447232095702560, 0,
|
||||
659769125727878493447120, 0,
|
||||
-1905929106580294155360, 0,
|
||||
916312070471295267]
|
||||
|
||||
with mp.workdps(3):
|
||||
with pytest.raises(mp.NoConvergence):
|
||||
polyroots(coeffs, maxsteps=5, cleanup=True, error=False,
|
||||
extraprec=n*10)
|
||||
|
||||
roots = polyroots(coeffs, maxsteps=50, cleanup=True, error=False,
|
||||
extraprec=n*10)
|
||||
roots = [str(r) for r in roots]
|
||||
assert roots == \
|
||||
['-0.999', '-0.996', '-0.991', '-0.983', '-0.973', '-0.961',
|
||||
'-0.946', '-0.93', '-0.911', '-0.889', '-0.866', '-0.841',
|
||||
'-0.813', '-0.784', '-0.753', '-0.72', '-0.685', '-0.649',
|
||||
'-0.611', '-0.572', '-0.531', '-0.489', '-0.446', '-0.402',
|
||||
'-0.357', '-0.311', '-0.265', '-0.217', '-0.17', '-0.121',
|
||||
'-0.073', '-0.0243', '0.0243', '0.073', '0.121', '0.17', '0.217',
|
||||
'0.265', '0.311', '0.357', '0.402', '0.446', '0.489', '0.531',
|
||||
'0.572', '0.611', '0.649', '0.685', '0.72', '0.753', '0.784',
|
||||
'0.813', '0.841', '0.866', '0.889', '0.911', '0.93', '0.946',
|
||||
'0.961', '0.973', '0.983', '0.991', '0.996', '0.999']
|
||||
|
||||
def test_polyroots_legendre_init():
|
||||
extra_prec = 100
|
||||
coeffs = [11975573020964041433067793888190275875, 0,
|
||||
-190100434726484311252477736051902332000, 0,
|
||||
1437919688271127330313741595496589239248, 0,
|
||||
-6897338342113537600691931230430793911840, 0,
|
||||
23556405536185284408974715545252277554280, 0,
|
||||
-60969520211303089058522793175947071316960, 0,
|
||||
124284021969194758465450309166353645376880, 0,
|
||||
-204721258548015217049921875719981284186016, 0,
|
||||
277415422258095841688223780704620656114900, 0,
|
||||
-313237834141273382807123548182995095192800, 0,
|
||||
297432255354328395601259515935229287637200, 0,
|
||||
-239057700565161140389797367947941296605600, 0,
|
||||
163356095386193445933028201431093219347160, 0,
|
||||
-95158890516229191805647495979277603503200, 0,
|
||||
47310254620162038075933656063247634556400, 0,
|
||||
-20071017111583894941305187420771723751200, 0,
|
||||
7255051932731034189479516844750603752850, 0,
|
||||
-2228176940331017311443863996901733412640, 0,
|
||||
579006552594977616773047095969088431600, 0,
|
||||
-126584428502545713788439446082310831200, 0,
|
||||
23112325428835593809686977515028663000, 0,
|
||||
-3491517141958743235617737161547844000, 0,
|
||||
431305058712550634988073414073557200, 0,
|
||||
-42927166660756742088912492757452000, 0,
|
||||
3378527005707706553294038781836500, 0,
|
||||
-205277590220215081719131470288800, 0,
|
||||
9330799555464321896324157740400, 0,
|
||||
-304114948474392713657972548576, 0,
|
||||
6695289961520387531608984680, 0,
|
||||
-91048139350447232095702560, 0,
|
||||
659769125727878493447120, 0,
|
||||
-1905929106580294155360, 0,
|
||||
916312070471295267]
|
||||
|
||||
roots_init = matrix(['-0.999', '-0.996', '-0.991', '-0.983', '-0.973',
|
||||
'-0.961', '-0.946', '-0.93', '-0.911', '-0.889',
|
||||
'-0.866', '-0.841', '-0.813', '-0.784', '-0.753',
|
||||
'-0.72', '-0.685', '-0.649', '-0.611', '-0.572',
|
||||
'-0.531', '-0.489', '-0.446', '-0.402', '-0.357',
|
||||
'-0.311', '-0.265', '-0.217', '-0.17', '-0.121',
|
||||
'-0.073', '-0.0243', '0.0243', '0.073', '0.121',
|
||||
'0.17', '0.217', '0.265', ' 0.311', '0.357',
|
||||
'0.402', '0.446', '0.489', '0.531', '0.572',
|
||||
'0.611', '0.649', '0.685', '0.72', '0.753',
|
||||
'0.784', '0.813', '0.841', '0.866', '0.889',
|
||||
'0.911', '0.93', '0.946', '0.961', '0.973',
|
||||
'0.983', '0.991', '0.996', '0.999', '1.0'])
|
||||
with mp.workdps(2*mp.dps):
|
||||
roots_exact = polyroots(coeffs, maxsteps=50, cleanup=True, error=False,
|
||||
extraprec=2*extra_prec)
|
||||
with pytest.raises(mp.NoConvergence):
|
||||
polyroots(coeffs, maxsteps=5, cleanup=True, error=False,
|
||||
extraprec=extra_prec)
|
||||
roots,err = polyroots(coeffs, maxsteps=5, cleanup=True, error=True,
|
||||
extraprec=extra_prec,roots_init=roots_init)
|
||||
assert max(matrix(roots_exact)-matrix(roots).apply(abs)) < err
|
||||
roots1,err1 = polyroots(coeffs, maxsteps=25, cleanup=True, error=True,
|
||||
extraprec=extra_prec,roots_init=roots_init[:60])
|
||||
assert max(matrix(roots_exact)-matrix(roots1).apply(abs)) < err1
|
||||
|
||||
def test_pade():
|
||||
one = mpf(1)
|
||||
mp.dps = 20
|
||||
N = 10
|
||||
a = [one]
|
||||
k = 1
|
||||
for i in range(1, N+1):
|
||||
k *= i
|
||||
a.append(one/k)
|
||||
p, q = pade(a, N//2, N//2)
|
||||
for x in arange(0, 1, 0.1):
|
||||
r = polyval(p[::-1], x)/polyval(q[::-1], x)
|
||||
assert(r.ae(exp(x), 1.0e-10))
|
||||
mp.dps = 15
|
||||
|
||||
def test_fourier():
|
||||
mp.dps = 15
|
||||
c, s = fourier(lambda x: x+1, [-1, 2], 2)
|
||||
#plot([lambda x: x+1, lambda x: fourierval((c, s), [-1, 2], x)], [-1, 2])
|
||||
assert c[0].ae(1.5)
|
||||
assert c[1].ae(-3*sqrt(3)/(2*pi))
|
||||
assert c[2].ae(3*sqrt(3)/(4*pi))
|
||||
assert s[0] == 0
|
||||
assert s[1].ae(3/(2*pi))
|
||||
assert s[2].ae(3/(4*pi))
|
||||
assert fourierval((c, s), [-1, 2], 1).ae(1.9134966715663442)
|
||||
|
||||
def test_differint():
|
||||
mp.dps = 15
|
||||
assert differint(lambda t: t, 2, -0.5).ae(8*sqrt(2/pi)/3)
|
||||
|
||||
def test_invlap():
|
||||
mp.dps = 15
|
||||
t = 0.01
|
||||
fp = lambda p: 1/(p+1)**2
|
||||
ft = lambda t: t*exp(-t)
|
||||
ftt = ft(t)
|
||||
assert invertlaplace(fp,t,method='talbot').ae(ftt)
|
||||
assert invertlaplace(fp,t,method='stehfest').ae(ftt)
|
||||
assert invertlaplace(fp,t,method='dehoog').ae(ftt)
|
||||
assert invertlaplace(fp,t,method='cohen').ae(ftt)
|
||||
t = 1.0
|
||||
ftt = ft(t)
|
||||
assert invertlaplace(fp,t,method='talbot').ae(ftt)
|
||||
assert invertlaplace(fp,t,method='stehfest').ae(ftt)
|
||||
assert invertlaplace(fp,t,method='dehoog').ae(ftt)
|
||||
assert invertlaplace(fp,t,method='cohen').ae(ftt)
|
||||
|
||||
t = 0.01
|
||||
fp = lambda p: log(p)/p
|
||||
ft = lambda t: -euler-log(t)
|
||||
ftt = ft(t)
|
||||
assert invertlaplace(fp,t,method='talbot').ae(ftt)
|
||||
assert invertlaplace(fp,t,method='stehfest').ae(ftt)
|
||||
assert invertlaplace(fp,t,method='dehoog').ae(ftt)
|
||||
assert invertlaplace(fp,t,method='cohen').ae(ftt)
|
||||
t = 1.0
|
||||
ftt = ft(t)
|
||||
assert invertlaplace(fp,t,method='talbot').ae(ftt)
|
||||
assert invertlaplace(fp,t,method='stehfest').ae(ftt)
|
||||
assert invertlaplace(fp,t,method='dehoog').ae(ftt)
|
||||
assert invertlaplace(fp,t,method='cohen').ae(ftt)
|
||||
@@ -0,0 +1,77 @@
|
||||
from mpmath import *
|
||||
from random import seed, randint, random
|
||||
import math
|
||||
|
||||
# Test compatibility with Python floats, which are
|
||||
# IEEE doubles (53-bit)
|
||||
|
||||
N = 5000
|
||||
seed(1)
|
||||
|
||||
# Choosing exponents between roughly -140, 140 ensures that
|
||||
# the Python floats don't overflow or underflow
|
||||
xs = [(random()-1) * 10**randint(-140, 140) for x in range(N)]
|
||||
ys = [(random()-1) * 10**randint(-140, 140) for x in range(N)]
|
||||
|
||||
# include some equal values
|
||||
ys[int(N*0.8):] = xs[int(N*0.8):]
|
||||
|
||||
# Detect whether Python is compiled to use 80-bit floating-point
|
||||
# instructions, in which case the double compatibility test breaks
|
||||
uses_x87 = -4.1974624032366689e+117 / -8.4657370748010221e-47 \
|
||||
== 4.9581771393902231e+163
|
||||
|
||||
def test_double_compatibility():
|
||||
mp.prec = 53
|
||||
for x, y in zip(xs, ys):
|
||||
mpx = mpf(x)
|
||||
mpy = mpf(y)
|
||||
assert mpf(x) == x
|
||||
assert (mpx < mpy) == (x < y)
|
||||
assert (mpx > mpy) == (x > y)
|
||||
assert (mpx == mpy) == (x == y)
|
||||
assert (mpx != mpy) == (x != y)
|
||||
assert (mpx <= mpy) == (x <= y)
|
||||
assert (mpx >= mpy) == (x >= y)
|
||||
assert mpx == mpx
|
||||
if uses_x87:
|
||||
mp.prec = 64
|
||||
a = mpx + mpy
|
||||
b = mpx * mpy
|
||||
c = mpx / mpy
|
||||
d = mpx % mpy
|
||||
mp.prec = 53
|
||||
assert +a == x + y
|
||||
assert +b == x * y
|
||||
assert +c == x / y
|
||||
assert +d == x % y
|
||||
else:
|
||||
assert mpx + mpy == x + y
|
||||
assert mpx * mpy == x * y
|
||||
assert mpx / mpy == x / y
|
||||
assert mpx % mpy == x % y
|
||||
assert abs(mpx) == abs(x)
|
||||
assert mpf(repr(x)) == x
|
||||
assert ceil(mpx) == math.ceil(x)
|
||||
assert floor(mpx) == math.floor(x)
|
||||
|
||||
def test_sqrt():
|
||||
# this fails quite often. it appers to be float
|
||||
# that rounds the wrong way, not mpf
|
||||
fail = 0
|
||||
mp.prec = 53
|
||||
for x in xs:
|
||||
x = abs(x)
|
||||
mp.prec = 100
|
||||
mp_high = mpf(x)**0.5
|
||||
mp.prec = 53
|
||||
mp_low = mpf(x)**0.5
|
||||
fp = x**0.5
|
||||
assert abs(mp_low-mp_high) <= abs(fp-mp_high)
|
||||
fail += mp_low != fp
|
||||
assert fail < N/10
|
||||
|
||||
def test_bugs():
|
||||
# particular bugs
|
||||
assert mpf(4.4408920985006262E-16) < mpf(1.7763568394002505E-15)
|
||||
assert mpf(-4.4408920985006262E-16) > mpf(-1.7763568394002505E-15)
|
||||
@@ -0,0 +1,233 @@
|
||||
import random
|
||||
from mpmath import *
|
||||
from mpmath.libmp import *
|
||||
|
||||
|
||||
def test_basic_string():
|
||||
"""
|
||||
Test basic string conversion
|
||||
"""
|
||||
mp.dps = 15
|
||||
assert mpf('3') == mpf('3.0') == mpf('0003.') == mpf('0.03e2') == mpf(3.0)
|
||||
assert mpf('30') == mpf('30.0') == mpf('00030.') == mpf(30.0)
|
||||
for i in range(10):
|
||||
for j in range(10):
|
||||
assert mpf('%ie%i' % (i,j)) == i * 10**j
|
||||
assert str(mpf('25000.0')) == '25000.0'
|
||||
assert str(mpf('2500.0')) == '2500.0'
|
||||
assert str(mpf('250.0')) == '250.0'
|
||||
assert str(mpf('25.0')) == '25.0'
|
||||
assert str(mpf('2.5')) == '2.5'
|
||||
assert str(mpf('0.25')) == '0.25'
|
||||
assert str(mpf('0.025')) == '0.025'
|
||||
assert str(mpf('0.0025')) == '0.0025'
|
||||
assert str(mpf('0.00025')) == '0.00025'
|
||||
assert str(mpf('0.000025')) == '2.5e-5'
|
||||
assert str(mpf(0)) == '0.0'
|
||||
assert str(mpf('2.5e1000000000000000000000')) == '2.5e+1000000000000000000000'
|
||||
assert str(mpf('2.6e-1000000000000000000000')) == '2.6e-1000000000000000000000'
|
||||
assert str(mpf(1.23402834e-15)) == '1.23402834e-15'
|
||||
assert str(mpf(-1.23402834e-15)) == '-1.23402834e-15'
|
||||
assert str(mpf(-1.2344e-15)) == '-1.2344e-15'
|
||||
assert repr(mpf(-1.2344e-15)) == "mpf('-1.2343999999999999e-15')"
|
||||
assert str(mpf("2163048125L")) == '2163048125.0'
|
||||
assert str(mpf("-2163048125l")) == '-2163048125.0'
|
||||
assert str(mpf("-2163048125L/1088391168")) == '-1.98738118113799'
|
||||
assert str(mpf("2163048125/1088391168l")) == '1.98738118113799'
|
||||
|
||||
def test_pretty():
|
||||
mp.pretty = True
|
||||
assert repr(mpf(2.5)) == '2.5'
|
||||
assert repr(mpc(2.5,3.5)) == '(2.5 + 3.5j)'
|
||||
mp.pretty = False
|
||||
iv.pretty = True
|
||||
assert repr(mpi(2.5,3.5)) == '[2.5, 3.5]'
|
||||
iv.pretty = False
|
||||
|
||||
def test_str_whitespace():
|
||||
assert mpf('1.26 ') == 1.26
|
||||
|
||||
def test_unicode():
|
||||
mp.dps = 15
|
||||
try:
|
||||
unicode = unicode
|
||||
except NameError:
|
||||
unicode = str
|
||||
assert mpf(unicode('2.76')) == 2.76
|
||||
assert mpf(unicode('inf')) == inf
|
||||
|
||||
def test_str_format():
|
||||
assert to_str(from_float(0.1),15,strip_zeros=False) == '0.100000000000000'
|
||||
assert to_str(from_float(0.0),15,show_zero_exponent=True) == '0.0e+0'
|
||||
assert to_str(from_float(0.0),0,show_zero_exponent=True) == '.0e+0'
|
||||
assert to_str(from_float(0.0),0,show_zero_exponent=False) == '.0'
|
||||
assert to_str(from_float(0.0),1,show_zero_exponent=True) == '0.0e+0'
|
||||
assert to_str(from_float(0.0),1,show_zero_exponent=False) == '0.0'
|
||||
assert to_str(from_float(1.23),3,show_zero_exponent=True) == '1.23e+0'
|
||||
assert to_str(from_float(1.23456789000000e-2),15,strip_zeros=False,min_fixed=0,max_fixed=0) == '1.23456789000000e-2'
|
||||
assert to_str(from_float(1.23456789000000e+2),15,strip_zeros=False,min_fixed=0,max_fixed=0) == '1.23456789000000e+2'
|
||||
assert to_str(from_float(2.1287e14), 15, max_fixed=1000) == '212870000000000.0'
|
||||
assert to_str(from_float(2.1287e15), 15, max_fixed=1000) == '2128700000000000.0'
|
||||
assert to_str(from_float(2.1287e16), 15, max_fixed=1000) == '21287000000000000.0'
|
||||
assert to_str(from_float(2.1287e30), 15, max_fixed=1000) == '2128700000000000000000000000000.0'
|
||||
|
||||
def test_tight_string_conversion():
|
||||
mp.dps = 15
|
||||
# In an old version, '0.5' wasn't recognized as representing
|
||||
# an exact binary number and was erroneously rounded up or down
|
||||
assert from_str('0.5', 10, round_floor) == fhalf
|
||||
assert from_str('0.5', 10, round_ceiling) == fhalf
|
||||
|
||||
def test_eval_repr_invariant():
|
||||
"""Test that eval(repr(x)) == x"""
|
||||
random.seed(123)
|
||||
for dps in [10, 15, 20, 50, 100]:
|
||||
mp.dps = dps
|
||||
for i in range(1000):
|
||||
a = mpf(random.random())**0.5 * 10**random.randint(-100, 100)
|
||||
assert eval(repr(a)) == a
|
||||
mp.dps = 15
|
||||
|
||||
def test_str_bugs():
|
||||
mp.dps = 15
|
||||
# Decimal rounding used to give the wrong exponent in some cases
|
||||
assert str(mpf('1e600')) == '1.0e+600'
|
||||
assert str(mpf('1e10000')) == '1.0e+10000'
|
||||
|
||||
def test_str_prec0():
|
||||
assert to_str(from_float(1.234), 0) == '.0e+0'
|
||||
assert to_str(from_float(1e-15), 0) == '.0e-15'
|
||||
assert to_str(from_float(1e+15), 0) == '.0e+15'
|
||||
assert to_str(from_float(-1e-15), 0) == '-.0e-15'
|
||||
assert to_str(from_float(-1e+15), 0) == '-.0e+15'
|
||||
|
||||
def test_convert_rational():
|
||||
mp.dps = 15
|
||||
assert from_rational(30, 5, 53, round_nearest) == (0, 3, 1, 2)
|
||||
assert from_rational(-7, 4, 53, round_nearest) == (1, 7, -2, 3)
|
||||
assert to_rational((0, 1, -1, 1)) == (1, 2)
|
||||
|
||||
def test_custom_class():
|
||||
class mympf:
|
||||
@property
|
||||
def _mpf_(self):
|
||||
return mpf(3.5)._mpf_
|
||||
class mympc:
|
||||
@property
|
||||
def _mpc_(self):
|
||||
return mpf(3.5)._mpf_, mpf(2.5)._mpf_
|
||||
assert mpf(2) + mympf() == 5.5
|
||||
assert mympf() + mpf(2) == 5.5
|
||||
assert mpf(mympf()) == 3.5
|
||||
assert mympc() + mpc(2) == mpc(5.5, 2.5)
|
||||
assert mpc(2) + mympc() == mpc(5.5, 2.5)
|
||||
assert mpc(mympc()) == (3.5+2.5j)
|
||||
|
||||
def test_conversion_methods():
|
||||
class SomethingRandom:
|
||||
pass
|
||||
class SomethingReal:
|
||||
def _mpmath_(self, prec, rounding):
|
||||
return mp.make_mpf(from_str('1.3', prec, rounding))
|
||||
class SomethingComplex:
|
||||
def _mpmath_(self, prec, rounding):
|
||||
return mp.make_mpc((from_str('1.3', prec, rounding), \
|
||||
from_str('1.7', prec, rounding)))
|
||||
x = mpf(3)
|
||||
z = mpc(3)
|
||||
a = SomethingRandom()
|
||||
y = SomethingReal()
|
||||
w = SomethingComplex()
|
||||
for d in [15, 45]:
|
||||
mp.dps = d
|
||||
assert (x+y).ae(mpf('4.3'))
|
||||
assert (y+x).ae(mpf('4.3'))
|
||||
assert (x+w).ae(mpc('4.3', '1.7'))
|
||||
assert (w+x).ae(mpc('4.3', '1.7'))
|
||||
assert (z+y).ae(mpc('4.3'))
|
||||
assert (y+z).ae(mpc('4.3'))
|
||||
assert (z+w).ae(mpc('4.3', '1.7'))
|
||||
assert (w+z).ae(mpc('4.3', '1.7'))
|
||||
x-y; y-x; x-w; w-x; z-y; y-z; z-w; w-z
|
||||
x*y; y*x; x*w; w*x; z*y; y*z; z*w; w*z
|
||||
x/y; y/x; x/w; w/x; z/y; y/z; z/w; w/z
|
||||
x**y; y**x; x**w; w**x; z**y; y**z; z**w; w**z
|
||||
x==y; y==x; x==w; w==x; z==y; y==z; z==w; w==z
|
||||
mp.dps = 15
|
||||
assert x.__add__(a) is NotImplemented
|
||||
assert x.__radd__(a) is NotImplemented
|
||||
assert x.__lt__(a) is NotImplemented
|
||||
assert x.__gt__(a) is NotImplemented
|
||||
assert x.__le__(a) is NotImplemented
|
||||
assert x.__ge__(a) is NotImplemented
|
||||
assert x.__eq__(a) is NotImplemented
|
||||
assert x.__ne__(a) is NotImplemented
|
||||
# implementation detail
|
||||
if hasattr(x, "__cmp__"):
|
||||
assert x.__cmp__(a) is NotImplemented
|
||||
assert x.__sub__(a) is NotImplemented
|
||||
assert x.__rsub__(a) is NotImplemented
|
||||
assert x.__mul__(a) is NotImplemented
|
||||
assert x.__rmul__(a) is NotImplemented
|
||||
assert x.__div__(a) is NotImplemented
|
||||
assert x.__rdiv__(a) is NotImplemented
|
||||
assert x.__mod__(a) is NotImplemented
|
||||
assert x.__rmod__(a) is NotImplemented
|
||||
assert x.__pow__(a) is NotImplemented
|
||||
assert x.__rpow__(a) is NotImplemented
|
||||
assert z.__add__(a) is NotImplemented
|
||||
assert z.__radd__(a) is NotImplemented
|
||||
assert z.__eq__(a) is NotImplemented
|
||||
assert z.__ne__(a) is NotImplemented
|
||||
assert z.__sub__(a) is NotImplemented
|
||||
assert z.__rsub__(a) is NotImplemented
|
||||
assert z.__mul__(a) is NotImplemented
|
||||
assert z.__rmul__(a) is NotImplemented
|
||||
assert z.__div__(a) is NotImplemented
|
||||
assert z.__rdiv__(a) is NotImplemented
|
||||
assert z.__pow__(a) is NotImplemented
|
||||
assert z.__rpow__(a) is NotImplemented
|
||||
|
||||
def test_mpmathify():
|
||||
assert mpmathify('1/2') == 0.5
|
||||
assert mpmathify('(1.0+1.0j)') == mpc(1, 1)
|
||||
assert mpmathify('(1.2e-10 - 3.4e5j)') == mpc('1.2e-10', '-3.4e5')
|
||||
assert mpmathify('1j') == mpc(1j)
|
||||
|
||||
def test_issue548():
|
||||
try:
|
||||
# This expression is invalid, but may trigger the ReDOS vulnerability
|
||||
# in the regular expression for parsing complex numbers.
|
||||
mpmathify('(' + '1' * 5000 + '!j')
|
||||
except:
|
||||
return
|
||||
# The expression is invalid and should raise an exception.
|
||||
assert False
|
||||
|
||||
def test_compatibility():
|
||||
try:
|
||||
import numpy as np
|
||||
from fractions import Fraction
|
||||
from decimal import Decimal
|
||||
import decimal
|
||||
except ImportError:
|
||||
return
|
||||
# numpy types
|
||||
for nptype in np.core.numerictypes.typeDict.values():
|
||||
if issubclass(nptype, np.complexfloating):
|
||||
x = nptype(complex(0.5, -0.5))
|
||||
elif issubclass(nptype, np.floating):
|
||||
x = nptype(0.5)
|
||||
elif issubclass(nptype, np.integer):
|
||||
x = nptype(2)
|
||||
# Handle the weird types
|
||||
try: diff = np.abs(type(np.sqrt(x))(sqrt(x)) - np.sqrt(x))
|
||||
except: continue
|
||||
assert diff < 2.0**-53
|
||||
#Fraction and Decimal
|
||||
oldprec = mp.prec
|
||||
mp.prec = 1000
|
||||
decimal.getcontext().prec = mp.dps
|
||||
assert sqrt(Fraction(2, 3)).ae(sqrt(mpf('2/3')))
|
||||
assert sqrt(Decimal(2)/Decimal(3)).ae(sqrt(mpf('2/3')))
|
||||
mp.prec = oldprec
|
||||
@@ -0,0 +1,61 @@
|
||||
from mpmath import *
|
||||
|
||||
def test_diff():
|
||||
mp.dps = 15
|
||||
assert diff(log, 2.0, n=0).ae(log(2))
|
||||
assert diff(cos, 1.0).ae(-sin(1))
|
||||
assert diff(abs, 0.0) == 0
|
||||
assert diff(abs, 0.0, direction=1) == 1
|
||||
assert diff(abs, 0.0, direction=-1) == -1
|
||||
assert diff(exp, 1.0).ae(e)
|
||||
assert diff(exp, 1.0, n=5).ae(e)
|
||||
assert diff(exp, 2.0, n=5, direction=3*j).ae(e**2)
|
||||
assert diff(lambda x: x**2, 3.0, method='quad').ae(6)
|
||||
assert diff(lambda x: 3+x**5, 3.0, n=2, method='quad').ae(540)
|
||||
assert diff(lambda x: 3+x**5, 3.0, n=2, method='step').ae(540)
|
||||
assert diffun(sin)(2).ae(cos(2))
|
||||
assert diffun(sin, n=2)(2).ae(-sin(2))
|
||||
|
||||
def test_diffs():
|
||||
mp.dps = 15
|
||||
assert [chop(d) for d in diffs(sin, 0, 1)] == [0, 1]
|
||||
assert [chop(d) for d in diffs(sin, 0, 1, method='quad')] == [0, 1]
|
||||
assert [chop(d) for d in diffs(sin, 0, 2)] == [0, 1, 0]
|
||||
assert [chop(d) for d in diffs(sin, 0, 2, method='quad')] == [0, 1, 0]
|
||||
|
||||
def test_taylor():
|
||||
mp.dps = 15
|
||||
# Easy to test since the coefficients are exact in floating-point
|
||||
assert taylor(sqrt, 1, 4) == [1, 0.5, -0.125, 0.0625, -0.0390625]
|
||||
|
||||
def test_diff_partial():
|
||||
mp.dps = 15
|
||||
x,y,z = xyz = 2,3,7
|
||||
f = lambda x,y,z: 3*x**2 * (y+2)**3 * z**5
|
||||
assert diff(f, xyz, (0,0,0)).ae(25210500)
|
||||
assert diff(f, xyz, (0,0,1)).ae(18007500)
|
||||
assert diff(f, xyz, (0,0,2)).ae(10290000)
|
||||
assert diff(f, xyz, (0,1,0)).ae(15126300)
|
||||
assert diff(f, xyz, (0,1,1)).ae(10804500)
|
||||
assert diff(f, xyz, (0,1,2)).ae(6174000)
|
||||
assert diff(f, xyz, (0,2,0)).ae(6050520)
|
||||
assert diff(f, xyz, (0,2,1)).ae(4321800)
|
||||
assert diff(f, xyz, (0,2,2)).ae(2469600)
|
||||
assert diff(f, xyz, (1,0,0)).ae(25210500)
|
||||
assert diff(f, xyz, (1,0,1)).ae(18007500)
|
||||
assert diff(f, xyz, (1,0,2)).ae(10290000)
|
||||
assert diff(f, xyz, (1,1,0)).ae(15126300)
|
||||
assert diff(f, xyz, (1,1,1)).ae(10804500)
|
||||
assert diff(f, xyz, (1,1,2)).ae(6174000)
|
||||
assert diff(f, xyz, (1,2,0)).ae(6050520)
|
||||
assert diff(f, xyz, (1,2,1)).ae(4321800)
|
||||
assert diff(f, xyz, (1,2,2)).ae(2469600)
|
||||
assert diff(f, xyz, (2,0,0)).ae(12605250)
|
||||
assert diff(f, xyz, (2,0,1)).ae(9003750)
|
||||
assert diff(f, xyz, (2,0,2)).ae(5145000)
|
||||
assert diff(f, xyz, (2,1,0)).ae(7563150)
|
||||
assert diff(f, xyz, (2,1,1)).ae(5402250)
|
||||
assert diff(f, xyz, (2,1,2)).ae(3087000)
|
||||
assert diff(f, xyz, (2,2,0)).ae(3025260)
|
||||
assert diff(f, xyz, (2,2,1)).ae(2160900)
|
||||
assert diff(f, xyz, (2,2,2)).ae(1234800)
|
||||
@@ -0,0 +1,143 @@
|
||||
from mpmath.libmp import *
|
||||
from mpmath import mpf, mp
|
||||
|
||||
from random import randint, choice, seed
|
||||
|
||||
all_modes = [round_floor, round_ceiling, round_down, round_up, round_nearest]
|
||||
|
||||
fb = from_bstr
|
||||
fi = from_int
|
||||
ff = from_float
|
||||
|
||||
|
||||
def test_div_1_3():
|
||||
a = fi(1)
|
||||
b = fi(3)
|
||||
c = fi(-1)
|
||||
|
||||
# floor rounds down, ceiling rounds up
|
||||
assert mpf_div(a, b, 7, round_floor) == fb('0.01010101')
|
||||
assert mpf_div(a, b, 7, round_ceiling) == fb('0.01010110')
|
||||
assert mpf_div(a, b, 7, round_down) == fb('0.01010101')
|
||||
assert mpf_div(a, b, 7, round_up) == fb('0.01010110')
|
||||
assert mpf_div(a, b, 7, round_nearest) == fb('0.01010101')
|
||||
|
||||
# floor rounds up, ceiling rounds down
|
||||
assert mpf_div(c, b, 7, round_floor) == fb('-0.01010110')
|
||||
assert mpf_div(c, b, 7, round_ceiling) == fb('-0.01010101')
|
||||
assert mpf_div(c, b, 7, round_down) == fb('-0.01010101')
|
||||
assert mpf_div(c, b, 7, round_up) == fb('-0.01010110')
|
||||
assert mpf_div(c, b, 7, round_nearest) == fb('-0.01010101')
|
||||
|
||||
def test_mpf_divi_1_3():
|
||||
a = 1
|
||||
b = fi(3)
|
||||
c = -1
|
||||
assert mpf_rdiv_int(a, b, 7, round_floor) == fb('0.01010101')
|
||||
assert mpf_rdiv_int(a, b, 7, round_ceiling) == fb('0.01010110')
|
||||
assert mpf_rdiv_int(a, b, 7, round_down) == fb('0.01010101')
|
||||
assert mpf_rdiv_int(a, b, 7, round_up) == fb('0.01010110')
|
||||
assert mpf_rdiv_int(a, b, 7, round_nearest) == fb('0.01010101')
|
||||
assert mpf_rdiv_int(c, b, 7, round_floor) == fb('-0.01010110')
|
||||
assert mpf_rdiv_int(c, b, 7, round_ceiling) == fb('-0.01010101')
|
||||
assert mpf_rdiv_int(c, b, 7, round_down) == fb('-0.01010101')
|
||||
assert mpf_rdiv_int(c, b, 7, round_up) == fb('-0.01010110')
|
||||
assert mpf_rdiv_int(c, b, 7, round_nearest) == fb('-0.01010101')
|
||||
|
||||
|
||||
def test_div_300():
|
||||
|
||||
q = fi(1000000)
|
||||
a = fi(300499999) # a/q is a little less than a half-integer
|
||||
b = fi(300500000) # b/q exactly a half-integer
|
||||
c = fi(300500001) # c/q is a little more than a half-integer
|
||||
|
||||
# Check nearest integer rounding (prec=9 as 2**8 < 300 < 2**9)
|
||||
|
||||
assert mpf_div(a, q, 9, round_down) == fi(300)
|
||||
assert mpf_div(b, q, 9, round_down) == fi(300)
|
||||
assert mpf_div(c, q, 9, round_down) == fi(300)
|
||||
assert mpf_div(a, q, 9, round_up) == fi(301)
|
||||
assert mpf_div(b, q, 9, round_up) == fi(301)
|
||||
assert mpf_div(c, q, 9, round_up) == fi(301)
|
||||
|
||||
# Nearest even integer is down
|
||||
assert mpf_div(a, q, 9, round_nearest) == fi(300)
|
||||
assert mpf_div(b, q, 9, round_nearest) == fi(300)
|
||||
assert mpf_div(c, q, 9, round_nearest) == fi(301)
|
||||
|
||||
# Nearest even integer is up
|
||||
a = fi(301499999)
|
||||
b = fi(301500000)
|
||||
c = fi(301500001)
|
||||
assert mpf_div(a, q, 9, round_nearest) == fi(301)
|
||||
assert mpf_div(b, q, 9, round_nearest) == fi(302)
|
||||
assert mpf_div(c, q, 9, round_nearest) == fi(302)
|
||||
|
||||
|
||||
def test_tight_integer_division():
|
||||
# Test that integer division at tightest possible precision is exact
|
||||
N = 100
|
||||
seed(1)
|
||||
for i in range(N):
|
||||
a = choice([1, -1]) * randint(1, 1<<randint(10, 100))
|
||||
b = choice([1, -1]) * randint(1, 1<<randint(10, 100))
|
||||
p = a * b
|
||||
width = bitcount(abs(b)) - trailing(b)
|
||||
a = fi(a); b = fi(b); p = fi(p)
|
||||
for mode in all_modes:
|
||||
assert mpf_div(p, a, width, mode) == b
|
||||
|
||||
|
||||
def test_epsilon_rounding():
|
||||
# Verify that mpf_div uses infinite precision; this result will
|
||||
# appear to be exactly 0.101 to a near-sighted algorithm
|
||||
|
||||
a = fb('0.101' + ('0'*200) + '1')
|
||||
b = fb('1.10101')
|
||||
c = mpf_mul(a, b, 250, round_floor) # exact
|
||||
assert mpf_div(c, b, bitcount(a[1]), round_floor) == a # exact
|
||||
|
||||
assert mpf_div(c, b, 2, round_down) == fb('0.10')
|
||||
assert mpf_div(c, b, 3, round_down) == fb('0.101')
|
||||
assert mpf_div(c, b, 2, round_up) == fb('0.11')
|
||||
assert mpf_div(c, b, 3, round_up) == fb('0.110')
|
||||
assert mpf_div(c, b, 2, round_floor) == fb('0.10')
|
||||
assert mpf_div(c, b, 3, round_floor) == fb('0.101')
|
||||
assert mpf_div(c, b, 2, round_ceiling) == fb('0.11')
|
||||
assert mpf_div(c, b, 3, round_ceiling) == fb('0.110')
|
||||
|
||||
# The same for negative numbers
|
||||
a = fb('-0.101' + ('0'*200) + '1')
|
||||
b = fb('1.10101')
|
||||
c = mpf_mul(a, b, 250, round_floor)
|
||||
assert mpf_div(c, b, bitcount(a[1]), round_floor) == a
|
||||
|
||||
assert mpf_div(c, b, 2, round_down) == fb('-0.10')
|
||||
assert mpf_div(c, b, 3, round_up) == fb('-0.110')
|
||||
|
||||
# Floor goes up, ceiling goes down
|
||||
assert mpf_div(c, b, 2, round_floor) == fb('-0.11')
|
||||
assert mpf_div(c, b, 3, round_floor) == fb('-0.110')
|
||||
assert mpf_div(c, b, 2, round_ceiling) == fb('-0.10')
|
||||
assert mpf_div(c, b, 3, round_ceiling) == fb('-0.101')
|
||||
|
||||
|
||||
def test_mod():
|
||||
mp.dps = 15
|
||||
assert mpf(234) % 1 == 0
|
||||
assert mpf(-3) % 256 == 253
|
||||
assert mpf(0.25) % 23490.5 == 0.25
|
||||
assert mpf(0.25) % -23490.5 == -23490.25
|
||||
assert mpf(-0.25) % 23490.5 == 23490.25
|
||||
assert mpf(-0.25) % -23490.5 == -0.25
|
||||
# Check that these cases are handled efficiently
|
||||
assert mpf('1e10000000000') % 1 == 0
|
||||
assert mpf('1.23e-1000000000') % 1 == mpf('1.23e-1000000000')
|
||||
# test __rmod__
|
||||
assert 3 % mpf('1.75') == 1.25
|
||||
|
||||
def test_div_negative_rnd_bug():
|
||||
mp.dps = 15
|
||||
assert (-3) / mpf('0.1531879017645047') == mpf('-19.583791966887116')
|
||||
assert mpf('-2.6342475750861301') / mpf('0.35126216427941814') == mpf('-7.4993775104985909')
|
||||
@@ -0,0 +1,179 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from mpmath import mp
|
||||
from mpmath import libmp
|
||||
|
||||
xrange = libmp.backend.xrange
|
||||
|
||||
def run_hessenberg(A, verbose = 0):
|
||||
if verbose > 1:
|
||||
print("original matrix (hessenberg):\n", A)
|
||||
|
||||
n = A.rows
|
||||
|
||||
Q, H = mp.hessenberg(A)
|
||||
|
||||
if verbose > 1:
|
||||
print("Q:\n",Q)
|
||||
print("H:\n",H)
|
||||
|
||||
B = Q * H * Q.transpose_conj()
|
||||
|
||||
eps = mp.exp(0.8 * mp.log(mp.eps))
|
||||
|
||||
err0 = 0
|
||||
for x in xrange(n):
|
||||
for y in xrange(n):
|
||||
err0 += abs(A[y,x] - B[y,x])
|
||||
err0 /= n * n
|
||||
|
||||
err1 = 0
|
||||
for x in xrange(n):
|
||||
for y in xrange(x + 2, n):
|
||||
err1 += abs(H[y,x])
|
||||
|
||||
if verbose > 0:
|
||||
print("difference (H):", err0, err1)
|
||||
|
||||
if verbose > 1:
|
||||
print("B:\n", B)
|
||||
|
||||
assert err0 < eps
|
||||
assert err1 == 0
|
||||
|
||||
|
||||
def run_schur(A, verbose = 0):
|
||||
if verbose > 1:
|
||||
print("original matrix (schur):\n", A)
|
||||
|
||||
n = A.rows
|
||||
|
||||
Q, R = mp.schur(A)
|
||||
|
||||
if verbose > 1:
|
||||
print("Q:\n", Q)
|
||||
print("R:\n", R)
|
||||
|
||||
B = Q * R * Q.transpose_conj()
|
||||
C = Q * Q.transpose_conj()
|
||||
|
||||
eps = mp.exp(0.8 * mp.log(mp.eps))
|
||||
|
||||
err0 = 0
|
||||
for x in xrange(n):
|
||||
for y in xrange(n):
|
||||
err0 += abs(A[y,x] - B[y,x])
|
||||
err0 /= n * n
|
||||
|
||||
err1 = 0
|
||||
for x in xrange(n):
|
||||
for y in xrange(n):
|
||||
if x == y:
|
||||
C[y,x] -= 1
|
||||
err1 += abs(C[y,x])
|
||||
err1 /= n * n
|
||||
|
||||
err2 = 0
|
||||
for x in xrange(n):
|
||||
for y in xrange(x + 1, n):
|
||||
err2 += abs(R[y,x])
|
||||
|
||||
if verbose > 0:
|
||||
print("difference (S):", err0, err1, err2)
|
||||
|
||||
if verbose > 1:
|
||||
print("B:\n", B)
|
||||
|
||||
assert err0 < eps
|
||||
assert err1 < eps
|
||||
assert err2 == 0
|
||||
|
||||
def run_eig(A, verbose = 0):
|
||||
if verbose > 1:
|
||||
print("original matrix (eig):\n", A)
|
||||
|
||||
n = A.rows
|
||||
|
||||
E, EL, ER = mp.eig(A, left = True, right = True)
|
||||
|
||||
if verbose > 1:
|
||||
print("E:\n", E)
|
||||
print("EL:\n", EL)
|
||||
print("ER:\n", ER)
|
||||
|
||||
eps = mp.exp(0.8 * mp.log(mp.eps))
|
||||
|
||||
err0 = 0
|
||||
for i in xrange(n):
|
||||
B = A * ER[:,i] - E[i] * ER[:,i]
|
||||
err0 = max(err0, mp.mnorm(B))
|
||||
|
||||
B = EL[i,:] * A - EL[i,:] * E[i]
|
||||
err0 = max(err0, mp.mnorm(B))
|
||||
|
||||
err0 /= n * n
|
||||
|
||||
if verbose > 0:
|
||||
print("difference (E):", err0)
|
||||
|
||||
assert err0 < eps
|
||||
|
||||
#####################
|
||||
|
||||
def test_eig_dyn():
|
||||
v = 0
|
||||
for i in xrange(5):
|
||||
n = 1 + int(mp.rand() * 5)
|
||||
if mp.rand() > 0.5:
|
||||
# real
|
||||
A = 2 * mp.randmatrix(n, n) - 1
|
||||
if mp.rand() > 0.5:
|
||||
A *= 10
|
||||
for x in xrange(n):
|
||||
for y in xrange(n):
|
||||
A[x,y] = int(A[x,y])
|
||||
else:
|
||||
A = (2 * mp.randmatrix(n, n) - 1) + 1j * (2 * mp.randmatrix(n, n) - 1)
|
||||
if mp.rand() > 0.5:
|
||||
A *= 10
|
||||
for x in xrange(n):
|
||||
for y in xrange(n):
|
||||
A[x,y] = int(mp.re(A[x,y])) + 1j * int(mp.im(A[x,y]))
|
||||
|
||||
run_hessenberg(A, verbose = v)
|
||||
run_schur(A, verbose = v)
|
||||
run_eig(A, verbose = v)
|
||||
|
||||
def test_eig():
|
||||
v = 0
|
||||
AS = []
|
||||
|
||||
A = mp.matrix([[2, 1, 0], # jordan block of size 3
|
||||
[0, 2, 1],
|
||||
[0, 0, 2]])
|
||||
AS.append(A)
|
||||
AS.append(A.transpose())
|
||||
|
||||
A = mp.matrix([[2, 0, 0], # jordan block of size 2
|
||||
[0, 2, 1],
|
||||
[0, 0, 2]])
|
||||
AS.append(A)
|
||||
AS.append(A.transpose())
|
||||
|
||||
A = mp.matrix([[2, 0, 1], # jordan block of size 2
|
||||
[0, 2, 0],
|
||||
[0, 0, 2]])
|
||||
AS.append(A)
|
||||
AS.append(A.transpose())
|
||||
|
||||
A= mp.matrix([[0, 0, 1], # cyclic
|
||||
[1, 0, 0],
|
||||
[0, 1, 0]])
|
||||
AS.append(A)
|
||||
AS.append(A.transpose())
|
||||
|
||||
for A in AS:
|
||||
run_hessenberg(A, verbose = v)
|
||||
run_schur(A, verbose = v)
|
||||
run_eig(A, verbose = v)
|
||||
@@ -0,0 +1,357 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from mpmath import mp
|
||||
from mpmath import libmp
|
||||
|
||||
xrange = libmp.backend.xrange
|
||||
|
||||
def run_eigsy(A, verbose = False):
|
||||
if verbose:
|
||||
print("original matrix:\n", str(A))
|
||||
|
||||
D, Q = mp.eigsy(A)
|
||||
B = Q * mp.diag(D) * Q.transpose()
|
||||
C = A - B
|
||||
E = Q * Q.transpose() - mp.eye(A.rows)
|
||||
|
||||
if verbose:
|
||||
print("eigenvalues:\n", D)
|
||||
print("eigenvectors:\n", Q)
|
||||
|
||||
NC = mp.mnorm(C)
|
||||
NE = mp.mnorm(E)
|
||||
|
||||
if verbose:
|
||||
print("difference:", NC, "\n", C, "\n")
|
||||
print("difference:", NE, "\n", E, "\n")
|
||||
|
||||
eps = mp.exp( 0.8 * mp.log(mp.eps))
|
||||
|
||||
assert NC < eps
|
||||
assert NE < eps
|
||||
|
||||
return NC
|
||||
|
||||
def run_eighe(A, verbose = False):
|
||||
if verbose:
|
||||
print("original matrix:\n", str(A))
|
||||
|
||||
D, Q = mp.eighe(A)
|
||||
B = Q * mp.diag(D) * Q.transpose_conj()
|
||||
C = A - B
|
||||
E = Q * Q.transpose_conj() - mp.eye(A.rows)
|
||||
|
||||
if verbose:
|
||||
print("eigenvalues:\n", D)
|
||||
print("eigenvectors:\n", Q)
|
||||
|
||||
NC = mp.mnorm(C)
|
||||
NE = mp.mnorm(E)
|
||||
|
||||
if verbose:
|
||||
print("difference:", NC, "\n", C, "\n")
|
||||
print("difference:", NE, "\n", E, "\n")
|
||||
|
||||
eps = mp.exp( 0.8 * mp.log(mp.eps))
|
||||
|
||||
assert NC < eps
|
||||
assert NE < eps
|
||||
|
||||
return NC
|
||||
|
||||
def run_svd_r(A, full_matrices = False, verbose = True):
|
||||
|
||||
m, n = A.rows, A.cols
|
||||
|
||||
eps = mp.exp(0.8 * mp.log(mp.eps))
|
||||
|
||||
if verbose:
|
||||
print("original matrix:\n", str(A))
|
||||
print("full", full_matrices)
|
||||
|
||||
U, S0, V = mp.svd_r(A, full_matrices = full_matrices)
|
||||
|
||||
S = mp.zeros(U.cols, V.rows)
|
||||
for j in xrange(min(m, n)):
|
||||
S[j,j] = S0[j]
|
||||
|
||||
if verbose:
|
||||
print("U:\n", str(U))
|
||||
print("S:\n", str(S0))
|
||||
print("V:\n", str(V))
|
||||
|
||||
C = U * S * V - A
|
||||
err = mp.mnorm(C)
|
||||
if verbose:
|
||||
print("C\n", str(C), "\n", err)
|
||||
assert err < eps
|
||||
|
||||
D = V * V.transpose() - mp.eye(V.rows)
|
||||
err = mp.mnorm(D)
|
||||
if verbose:
|
||||
print("D:\n", str(D), "\n", err)
|
||||
assert err < eps
|
||||
|
||||
E = U.transpose() * U - mp.eye(U.cols)
|
||||
err = mp.mnorm(E)
|
||||
if verbose:
|
||||
print("E:\n", str(E), "\n", err)
|
||||
assert err < eps
|
||||
|
||||
def run_svd_c(A, full_matrices = False, verbose = True):
|
||||
|
||||
m, n = A.rows, A.cols
|
||||
|
||||
eps = mp.exp(0.8 * mp.log(mp.eps))
|
||||
|
||||
if verbose:
|
||||
print("original matrix:\n", str(A))
|
||||
print("full", full_matrices)
|
||||
|
||||
U, S0, V = mp.svd_c(A, full_matrices = full_matrices)
|
||||
|
||||
S = mp.zeros(U.cols, V.rows)
|
||||
for j in xrange(min(m, n)):
|
||||
S[j,j] = S0[j]
|
||||
|
||||
if verbose:
|
||||
print("U:\n", str(U))
|
||||
print("S:\n", str(S0))
|
||||
print("V:\n", str(V))
|
||||
|
||||
C = U * S * V - A
|
||||
err = mp.mnorm(C)
|
||||
if verbose:
|
||||
print("C\n", str(C), "\n", err)
|
||||
assert err < eps
|
||||
|
||||
D = V * V.transpose_conj() - mp.eye(V.rows)
|
||||
err = mp.mnorm(D)
|
||||
if verbose:
|
||||
print("D:\n", str(D), "\n", err)
|
||||
assert err < eps
|
||||
|
||||
E = U.transpose_conj() * U - mp.eye(U.cols)
|
||||
err = mp.mnorm(E)
|
||||
if verbose:
|
||||
print("E:\n", str(E), "\n", err)
|
||||
assert err < eps
|
||||
|
||||
def run_gauss(qtype, a, b):
|
||||
eps = 1e-5
|
||||
|
||||
d, e = mp.gauss_quadrature(len(a), qtype)
|
||||
d -= mp.matrix(a)
|
||||
e -= mp.matrix(b)
|
||||
|
||||
assert mp.mnorm(d) < eps
|
||||
assert mp.mnorm(e) < eps
|
||||
|
||||
def irandmatrix(n, range = 10):
|
||||
"""
|
||||
random matrix with integer entries
|
||||
"""
|
||||
A = mp.matrix(n, n)
|
||||
for i in xrange(n):
|
||||
for j in xrange(n):
|
||||
A[i,j]=int( (2 * mp.rand() - 1) * range)
|
||||
return A
|
||||
|
||||
#######################
|
||||
|
||||
def test_eighe_fixed_matrix():
|
||||
A = mp.matrix([[2, 3], [3, 5]])
|
||||
run_eigsy(A)
|
||||
run_eighe(A)
|
||||
|
||||
A = mp.matrix([[7, -11], [-11, 13]])
|
||||
run_eigsy(A)
|
||||
run_eighe(A)
|
||||
|
||||
A = mp.matrix([[2, 11, 7], [11, 3, 13], [7, 13, 5]])
|
||||
run_eigsy(A)
|
||||
run_eighe(A)
|
||||
|
||||
A = mp.matrix([[2, 0, 7], [0, 3, 1], [7, 1, 5]])
|
||||
run_eigsy(A)
|
||||
run_eighe(A)
|
||||
|
||||
#
|
||||
|
||||
A = mp.matrix([[2, 3+7j], [3-7j, 5]])
|
||||
run_eighe(A)
|
||||
|
||||
A = mp.matrix([[2, -11j, 0], [+11j, 3, 29j], [0, -29j, 5]])
|
||||
run_eighe(A)
|
||||
|
||||
A = mp.matrix([[2, 11 + 17j, 7 + 19j], [11 - 17j, 3, -13 + 23j], [7 - 19j, -13 - 23j, 5]])
|
||||
run_eighe(A)
|
||||
|
||||
def test_eigsy_randmatrix():
|
||||
N = 5
|
||||
|
||||
for a in xrange(10):
|
||||
A = 2 * mp.randmatrix(N, N) - 1
|
||||
|
||||
for i in xrange(0, N):
|
||||
for j in xrange(i + 1, N):
|
||||
A[j,i] = A[i,j]
|
||||
|
||||
run_eigsy(A)
|
||||
|
||||
def test_eighe_randmatrix():
|
||||
N = 5
|
||||
|
||||
for a in xrange(10):
|
||||
A = (2 * mp.randmatrix(N, N) - 1) + 1j * (2 * mp.randmatrix(N, N) - 1)
|
||||
|
||||
for i in xrange(0, N):
|
||||
A[i,i] = mp.re(A[i,i])
|
||||
for j in xrange(i + 1, N):
|
||||
A[j,i] = mp.conj(A[i,j])
|
||||
|
||||
run_eighe(A)
|
||||
|
||||
def test_eigsy_irandmatrix():
|
||||
N = 4
|
||||
R = 4
|
||||
|
||||
for a in xrange(10):
|
||||
A=irandmatrix(N, R)
|
||||
|
||||
for i in xrange(0, N):
|
||||
for j in xrange(i + 1, N):
|
||||
A[j,i] = A[i,j]
|
||||
|
||||
run_eigsy(A)
|
||||
|
||||
def test_eighe_irandmatrix():
|
||||
N = 4
|
||||
R = 4
|
||||
|
||||
for a in xrange(10):
|
||||
A=irandmatrix(N, R) + 1j * irandmatrix(N, R)
|
||||
|
||||
for i in xrange(0, N):
|
||||
A[i,i] = mp.re(A[i,i])
|
||||
for j in xrange(i + 1, N):
|
||||
A[j,i] = mp.conj(A[i,j])
|
||||
|
||||
run_eighe(A)
|
||||
|
||||
def test_svd_r_rand():
|
||||
for i in xrange(5):
|
||||
full = mp.rand() > 0.5
|
||||
m = 1 + int(mp.rand() * 10)
|
||||
n = 1 + int(mp.rand() * 10)
|
||||
A = 2 * mp.randmatrix(m, n) - 1
|
||||
if mp.rand() > 0.5:
|
||||
A *= 10
|
||||
for x in xrange(m):
|
||||
for y in xrange(n):
|
||||
A[x,y]=int(A[x,y])
|
||||
|
||||
run_svd_r(A, full_matrices = full, verbose = False)
|
||||
|
||||
def test_svd_c_rand():
|
||||
for i in xrange(5):
|
||||
full = mp.rand() > 0.5
|
||||
m = 1 + int(mp.rand() * 10)
|
||||
n = 1 + int(mp.rand() * 10)
|
||||
A = (2 * mp.randmatrix(m, n) - 1) + 1j * (2 * mp.randmatrix(m, n) - 1)
|
||||
if mp.rand() > 0.5:
|
||||
A *= 10
|
||||
for x in xrange(m):
|
||||
for y in xrange(n):
|
||||
A[x,y]=int(mp.re(A[x,y])) + 1j * int(mp.im(A[x,y]))
|
||||
|
||||
run_svd_c(A, full_matrices=full, verbose=False)
|
||||
|
||||
def test_svd_test_case():
|
||||
# a test case from Golub and Reinsch
|
||||
# (see wilkinson/reinsch: handbook for auto. comp., vol ii-linear algebra, 134-151(1971).)
|
||||
|
||||
eps = mp.exp(0.8 * mp.log(mp.eps))
|
||||
|
||||
a = [[22, 10, 2, 3, 7],
|
||||
[14, 7, 10, 0, 8],
|
||||
[-1, 13, -1, -11, 3],
|
||||
[-3, -2, 13, -2, 4],
|
||||
[ 9, 8, 1, -2, 4],
|
||||
[ 9, 1, -7, 5, -1],
|
||||
[ 2, -6, 6, 5, 1],
|
||||
[ 4, 5, 0, -2, 2]]
|
||||
|
||||
a = mp.matrix(a)
|
||||
b = mp.matrix([mp.sqrt(1248), 20, mp.sqrt(384), 0, 0])
|
||||
|
||||
S = mp.svd_r(a, compute_uv = False)
|
||||
S -= b
|
||||
assert mp.mnorm(S) < eps
|
||||
|
||||
S = mp.svd_c(a, compute_uv = False)
|
||||
S -= b
|
||||
assert mp.mnorm(S) < eps
|
||||
|
||||
|
||||
def test_gauss_quadrature_static():
|
||||
a = [-0.57735027, 0.57735027]
|
||||
b = [ 1, 1]
|
||||
run_gauss("legendre", a , b)
|
||||
|
||||
a = [ -0.906179846, -0.538469310, 0, 0.538469310, 0.906179846]
|
||||
b = [ 0.23692689, 0.47862867, 0.56888889, 0.47862867, 0.23692689]
|
||||
run_gauss("legendre", a , b)
|
||||
|
||||
a = [ 0.06943184, 0.33000948, 0.66999052, 0.93056816]
|
||||
b = [ 0.17392742, 0.32607258, 0.32607258, 0.17392742]
|
||||
run_gauss("legendre01", a , b)
|
||||
|
||||
a = [-0.70710678, 0.70710678]
|
||||
b = [ 0.88622693, 0.88622693]
|
||||
run_gauss("hermite", a , b)
|
||||
|
||||
a = [ -2.02018287, -0.958572465, 0, 0.958572465, 2.02018287]
|
||||
b = [ 0.01995324, 0.39361932, 0.94530872, 0.39361932, 0.01995324]
|
||||
run_gauss("hermite", a , b)
|
||||
|
||||
a = [ 0.41577456, 2.29428036, 6.28994508]
|
||||
b = [ 0.71109301, 0.27851773, 0.01038926]
|
||||
run_gauss("laguerre", a , b)
|
||||
|
||||
def test_gauss_quadrature_dynamic(verbose = False):
|
||||
n = 5
|
||||
|
||||
A = mp.randmatrix(2 * n, 1)
|
||||
|
||||
def F(x):
|
||||
r = 0
|
||||
for i in xrange(len(A) - 1, -1, -1):
|
||||
r = r * x + A[i]
|
||||
return r
|
||||
|
||||
def run(qtype, FW, R, alpha = 0, beta = 0):
|
||||
X, W = mp.gauss_quadrature(n, qtype, alpha = alpha, beta = beta)
|
||||
|
||||
a = 0
|
||||
for i in xrange(len(X)):
|
||||
a += W[i] * F(X[i])
|
||||
|
||||
b = mp.quad(lambda x: FW(x) * F(x), R)
|
||||
|
||||
c = mp.fabs(a - b)
|
||||
|
||||
if verbose:
|
||||
print(qtype, c, a, b)
|
||||
|
||||
assert c < 1e-5
|
||||
|
||||
run("legendre", lambda x: 1, [-1, 1])
|
||||
run("legendre01", lambda x: 1, [0, 1])
|
||||
run("hermite", lambda x: mp.exp(-x*x), [-mp.inf, mp.inf])
|
||||
run("laguerre", lambda x: mp.exp(-x), [0, mp.inf])
|
||||
run("glaguerre", lambda x: mp.sqrt(x)*mp.exp(-x), [0, mp.inf], alpha = 1 / mp.mpf(2))
|
||||
run("chebyshev1", lambda x: 1/mp.sqrt(1-x*x), [-1, 1])
|
||||
run("chebyshev2", lambda x: mp.sqrt(1-x*x), [-1, 1])
|
||||
run("jacobi", lambda x: (1-x)**(1/mp.mpf(3)) * (1+x)**(1/mp.mpf(5)), [-1, 1], alpha = 1 / mp.mpf(3), beta = 1 / mp.mpf(5) )
|
||||
@@ -0,0 +1,670 @@
|
||||
"""
|
||||
Limited tests of the elliptic functions module. A full suite of
|
||||
extensive testing can be found in elliptic_torture_tests.py
|
||||
|
||||
Author of the first version: M.T. Taschuk
|
||||
|
||||
References:
|
||||
|
||||
[1] Abramowitz & Stegun. 'Handbook of Mathematical Functions, 9th Ed.',
|
||||
(Dover duplicate of 1972 edition)
|
||||
[2] Whittaker 'A Course of Modern Analysis, 4th Ed.', 1946,
|
||||
Cambridge University Press
|
||||
|
||||
"""
|
||||
|
||||
import mpmath
|
||||
import random
|
||||
import pytest
|
||||
|
||||
from mpmath import *
|
||||
|
||||
def mpc_ae(a, b, eps=eps):
|
||||
res = True
|
||||
res = res and a.real.ae(b.real, eps)
|
||||
res = res and a.imag.ae(b.imag, eps)
|
||||
return res
|
||||
|
||||
zero = mpf(0)
|
||||
one = mpf(1)
|
||||
|
||||
jsn = ellipfun('sn')
|
||||
jcn = ellipfun('cn')
|
||||
jdn = ellipfun('dn')
|
||||
|
||||
calculate_nome = lambda k: qfrom(k=k)
|
||||
|
||||
def test_ellipfun():
|
||||
mp.dps = 15
|
||||
assert ellipfun('ss', 0, 0) == 1
|
||||
assert ellipfun('cc', 0, 0) == 1
|
||||
assert ellipfun('dd', 0, 0) == 1
|
||||
assert ellipfun('nn', 0, 0) == 1
|
||||
assert ellipfun('sn', 0.25, 0).ae(sin(0.25))
|
||||
assert ellipfun('cn', 0.25, 0).ae(cos(0.25))
|
||||
assert ellipfun('dn', 0.25, 0).ae(1)
|
||||
assert ellipfun('ns', 0.25, 0).ae(csc(0.25))
|
||||
assert ellipfun('nc', 0.25, 0).ae(sec(0.25))
|
||||
assert ellipfun('nd', 0.25, 0).ae(1)
|
||||
assert ellipfun('sc', 0.25, 0).ae(tan(0.25))
|
||||
assert ellipfun('sd', 0.25, 0).ae(sin(0.25))
|
||||
assert ellipfun('cd', 0.25, 0).ae(cos(0.25))
|
||||
assert ellipfun('cs', 0.25, 0).ae(cot(0.25))
|
||||
assert ellipfun('dc', 0.25, 0).ae(sec(0.25))
|
||||
assert ellipfun('ds', 0.25, 0).ae(csc(0.25))
|
||||
assert ellipfun('sn', 0.25, 1).ae(tanh(0.25))
|
||||
assert ellipfun('cn', 0.25, 1).ae(sech(0.25))
|
||||
assert ellipfun('dn', 0.25, 1).ae(sech(0.25))
|
||||
assert ellipfun('ns', 0.25, 1).ae(coth(0.25))
|
||||
assert ellipfun('nc', 0.25, 1).ae(cosh(0.25))
|
||||
assert ellipfun('nd', 0.25, 1).ae(cosh(0.25))
|
||||
assert ellipfun('sc', 0.25, 1).ae(sinh(0.25))
|
||||
assert ellipfun('sd', 0.25, 1).ae(sinh(0.25))
|
||||
assert ellipfun('cd', 0.25, 1).ae(1)
|
||||
assert ellipfun('cs', 0.25, 1).ae(csch(0.25))
|
||||
assert ellipfun('dc', 0.25, 1).ae(1)
|
||||
assert ellipfun('ds', 0.25, 1).ae(csch(0.25))
|
||||
assert ellipfun('sn', 0.25, 0.5).ae(0.24615967096986145833)
|
||||
assert ellipfun('cn', 0.25, 0.5).ae(0.96922928989378439337)
|
||||
assert ellipfun('dn', 0.25, 0.5).ae(0.98473484156599474563)
|
||||
assert ellipfun('ns', 0.25, 0.5).ae(4.0624038700573130369)
|
||||
assert ellipfun('nc', 0.25, 0.5).ae(1.0317476065024692949)
|
||||
assert ellipfun('nd', 0.25, 0.5).ae(1.0155017958029488665)
|
||||
assert ellipfun('sc', 0.25, 0.5).ae(0.25397465134058993408)
|
||||
assert ellipfun('sd', 0.25, 0.5).ae(0.24997558792415733063)
|
||||
assert ellipfun('cd', 0.25, 0.5).ae(0.98425408443195497052)
|
||||
assert ellipfun('cs', 0.25, 0.5).ae(3.9374008182374110826)
|
||||
assert ellipfun('dc', 0.25, 0.5).ae(1.0159978158253033913)
|
||||
assert ellipfun('ds', 0.25, 0.5).ae(4.0003906313579720593)
|
||||
|
||||
|
||||
|
||||
|
||||
def test_calculate_nome():
|
||||
mp.dps = 100
|
||||
|
||||
q = calculate_nome(zero)
|
||||
assert(q == zero)
|
||||
|
||||
mp.dps = 25
|
||||
# used Mathematica's EllipticNomeQ[m]
|
||||
math1 = [(mpf(1)/10, mpf('0.006584651553858370274473060')),
|
||||
(mpf(2)/10, mpf('0.01394285727531826872146409')),
|
||||
(mpf(3)/10, mpf('0.02227743615715350822901627')),
|
||||
(mpf(4)/10, mpf('0.03188334731336317755064299')),
|
||||
(mpf(5)/10, mpf('0.04321391826377224977441774')),
|
||||
(mpf(6)/10, mpf('0.05702025781460967637754953')),
|
||||
(mpf(7)/10, mpf('0.07468994353717944761143751')),
|
||||
(mpf(8)/10, mpf('0.09927369733882489703607378')),
|
||||
(mpf(9)/10, mpf('0.1401731269542615524091055')),
|
||||
(mpf(9)/10, mpf('0.1401731269542615524091055'))]
|
||||
|
||||
for i in math1:
|
||||
m = i[0]
|
||||
q = calculate_nome(sqrt(m))
|
||||
assert q.ae(i[1])
|
||||
|
||||
mp.dps = 15
|
||||
|
||||
def test_jtheta():
|
||||
mp.dps = 25
|
||||
|
||||
z = q = zero
|
||||
for n in range(1,5):
|
||||
value = jtheta(n, z, q)
|
||||
assert(value == (n-1)//2)
|
||||
|
||||
for q in [one, mpf(2)]:
|
||||
for n in range(1,5):
|
||||
pytest.raises(ValueError, lambda: jtheta(n, z, q))
|
||||
|
||||
z = one/10
|
||||
q = one/11
|
||||
|
||||
# Mathematical N[EllipticTheta[1, 1/10, 1/11], 25]
|
||||
res = mpf('0.1069552990104042681962096')
|
||||
result = jtheta(1, z, q)
|
||||
assert(result.ae(res))
|
||||
|
||||
# Mathematica N[EllipticTheta[2, 1/10, 1/11], 25]
|
||||
res = mpf('1.101385760258855791140606')
|
||||
result = jtheta(2, z, q)
|
||||
assert(result.ae(res))
|
||||
|
||||
# Mathematica N[EllipticTheta[3, 1/10, 1/11], 25]
|
||||
res = mpf('1.178319743354331061795905')
|
||||
result = jtheta(3, z, q)
|
||||
assert(result.ae(res))
|
||||
|
||||
# Mathematica N[EllipticTheta[4, 1/10, 1/11], 25]
|
||||
res = mpf('0.8219318954665153577314573')
|
||||
result = jtheta(4, z, q)
|
||||
assert(result.ae(res))
|
||||
|
||||
# test for sin zeros for jtheta(1, z, q)
|
||||
# test for cos zeros for jtheta(2, z, q)
|
||||
z1 = pi
|
||||
z2 = pi/2
|
||||
for i in range(10):
|
||||
qstring = str(random.random())
|
||||
q = mpf(qstring)
|
||||
result = jtheta(1, z1, q)
|
||||
assert(result.ae(0))
|
||||
result = jtheta(2, z2, q)
|
||||
assert(result.ae(0))
|
||||
mp.dps = 15
|
||||
|
||||
|
||||
def test_jtheta_issue_79():
|
||||
# near the circle of covergence |q| = 1 the convergence slows
|
||||
# down; for |q| > Q_LIM the theta functions raise ValueError
|
||||
mp.dps = 30
|
||||
mp.dps += 30
|
||||
q = mpf(6)/10 - one/10**6 - mpf(8)/10 * j
|
||||
mp.dps -= 30
|
||||
# Mathematica run first
|
||||
# N[EllipticTheta[3, 1, 6/10 - 10^-6 - 8/10*I], 2000]
|
||||
# then it works:
|
||||
# N[EllipticTheta[3, 1, 6/10 - 10^-6 - 8/10*I], 30]
|
||||
res = mpf('32.0031009628901652627099524264') + \
|
||||
mpf('16.6153027998236087899308935624') * j
|
||||
result = jtheta(3, 1, q)
|
||||
# check that for abs(q) > Q_LIM a ValueError exception is raised
|
||||
mp.dps += 30
|
||||
q = mpf(6)/10 - one/10**7 - mpf(8)/10 * j
|
||||
mp.dps -= 30
|
||||
pytest.raises(ValueError, lambda: jtheta(3, 1, q))
|
||||
|
||||
# bug reported in issue 79
|
||||
mp.dps = 100
|
||||
z = (1+j)/3
|
||||
q = mpf(368983957219251)/10**15 + mpf(636363636363636)/10**15 * j
|
||||
# Mathematica N[EllipticTheta[1, z, q], 35]
|
||||
res = mpf('2.4439389177990737589761828991467471') + \
|
||||
mpf('0.5446453005688226915290954851851490') *j
|
||||
mp.dps = 30
|
||||
result = jtheta(1, z, q)
|
||||
assert(result.ae(res))
|
||||
mp.dps = 80
|
||||
z = 3 + 4*j
|
||||
q = 0.5 + 0.5*j
|
||||
r1 = jtheta(1, z, q)
|
||||
mp.dps = 15
|
||||
r2 = jtheta(1, z, q)
|
||||
assert r1.ae(r2)
|
||||
mp.dps = 80
|
||||
z = 3 + j
|
||||
q1 = exp(j*3)
|
||||
# longer test
|
||||
# for n in range(1, 6)
|
||||
for n in range(1, 2):
|
||||
mp.dps = 80
|
||||
q = q1*(1 - mpf(1)/10**n)
|
||||
r1 = jtheta(1, z, q)
|
||||
mp.dps = 15
|
||||
r2 = jtheta(1, z, q)
|
||||
assert r1.ae(r2)
|
||||
mp.dps = 15
|
||||
# issue 79 about high derivatives
|
||||
assert jtheta(3, 4.5, 0.25, 9).ae(1359.04892680683)
|
||||
assert jtheta(3, 4.5, 0.25, 50).ae(-6.14832772630905e+33)
|
||||
mp.dps = 50
|
||||
r = jtheta(3, 4.5, 0.25, 9)
|
||||
assert r.ae('1359.048926806828939547859396600218966947753213803')
|
||||
r = jtheta(3, 4.5, 0.25, 50)
|
||||
assert r.ae('-6148327726309051673317975084654262.4119215720343656')
|
||||
|
||||
def test_jtheta_identities():
|
||||
"""
|
||||
Tests the some of the jacobi identidies found in Abramowitz,
|
||||
Sec. 16.28, Pg. 576. The identities are tested to 1 part in 10^98.
|
||||
"""
|
||||
mp.dps = 110
|
||||
eps1 = ldexp(eps, 30)
|
||||
|
||||
for i in range(10):
|
||||
qstring = str(random.random())
|
||||
q = mpf(qstring)
|
||||
|
||||
zstring = str(10*random.random())
|
||||
z = mpf(zstring)
|
||||
# Abramowitz 16.28.1
|
||||
# v_1(z, q)**2 * v_4(0, q)**2 = v_3(z, q)**2 * v_2(0, q)**2
|
||||
# - v_2(z, q)**2 * v_3(0, q)**2
|
||||
term1 = (jtheta(1, z, q)**2) * (jtheta(4, zero, q)**2)
|
||||
term2 = (jtheta(3, z, q)**2) * (jtheta(2, zero, q)**2)
|
||||
term3 = (jtheta(2, z, q)**2) * (jtheta(3, zero, q)**2)
|
||||
equality = term1 - term2 + term3
|
||||
assert(equality.ae(0, eps1))
|
||||
|
||||
zstring = str(100*random.random())
|
||||
z = mpf(zstring)
|
||||
# Abramowitz 16.28.2
|
||||
# v_2(z, q)**2 * v_4(0, q)**2 = v_4(z, q)**2 * v_2(0, q)**2
|
||||
# - v_1(z, q)**2 * v_3(0, q)**2
|
||||
term1 = (jtheta(2, z, q)**2) * (jtheta(4, zero, q)**2)
|
||||
term2 = (jtheta(4, z, q)**2) * (jtheta(2, zero, q)**2)
|
||||
term3 = (jtheta(1, z, q)**2) * (jtheta(3, zero, q)**2)
|
||||
equality = term1 - term2 + term3
|
||||
assert(equality.ae(0, eps1))
|
||||
|
||||
# Abramowitz 16.28.3
|
||||
# v_3(z, q)**2 * v_4(0, q)**2 = v_4(z, q)**2 * v_3(0, q)**2
|
||||
# - v_1(z, q)**2 * v_2(0, q)**2
|
||||
term1 = (jtheta(3, z, q)**2) * (jtheta(4, zero, q)**2)
|
||||
term2 = (jtheta(4, z, q)**2) * (jtheta(3, zero, q)**2)
|
||||
term3 = (jtheta(1, z, q)**2) * (jtheta(2, zero, q)**2)
|
||||
equality = term1 - term2 + term3
|
||||
assert(equality.ae(0, eps1))
|
||||
|
||||
# Abramowitz 16.28.4
|
||||
# v_4(z, q)**2 * v_4(0, q)**2 = v_3(z, q)**2 * v_3(0, q)**2
|
||||
# - v_2(z, q)**2 * v_2(0, q)**2
|
||||
term1 = (jtheta(4, z, q)**2) * (jtheta(4, zero, q)**2)
|
||||
term2 = (jtheta(3, z, q)**2) * (jtheta(3, zero, q)**2)
|
||||
term3 = (jtheta(2, z, q)**2) * (jtheta(2, zero, q)**2)
|
||||
equality = term1 - term2 + term3
|
||||
assert(equality.ae(0, eps1))
|
||||
|
||||
# Abramowitz 16.28.5
|
||||
# v_2(0, q)**4 + v_4(0, q)**4 == v_3(0, q)**4
|
||||
term1 = (jtheta(2, zero, q))**4
|
||||
term2 = (jtheta(4, zero, q))**4
|
||||
term3 = (jtheta(3, zero, q))**4
|
||||
equality = term1 + term2 - term3
|
||||
assert(equality.ae(0, eps1))
|
||||
mp.dps = 15
|
||||
|
||||
def test_jtheta_complex():
|
||||
mp.dps = 30
|
||||
z = mpf(1)/4 + j/8
|
||||
q = mpf(1)/3 + j/7
|
||||
# Mathematica N[EllipticTheta[1, 1/4 + I/8, 1/3 + I/7], 35]
|
||||
res = mpf('0.31618034835986160705729105731678285') + \
|
||||
mpf('0.07542013825835103435142515194358975') * j
|
||||
r = jtheta(1, z, q)
|
||||
assert(mpc_ae(r, res))
|
||||
|
||||
# Mathematica N[EllipticTheta[2, 1/4 + I/8, 1/3 + I/7], 35]
|
||||
res = mpf('1.6530986428239765928634711417951828') + \
|
||||
mpf('0.2015344864707197230526742145361455') * j
|
||||
r = jtheta(2, z, q)
|
||||
assert(mpc_ae(r, res))
|
||||
|
||||
# Mathematica N[EllipticTheta[3, 1/4 + I/8, 1/3 + I/7], 35]
|
||||
res = mpf('1.6520564411784228184326012700348340') + \
|
||||
mpf('0.1998129119671271328684690067401823') * j
|
||||
r = jtheta(3, z, q)
|
||||
assert(mpc_ae(r, res))
|
||||
|
||||
# Mathematica N[EllipticTheta[4, 1/4 + I/8, 1/3 + I/7], 35]
|
||||
res = mpf('0.37619082382228348252047624089973824') - \
|
||||
mpf('0.15623022130983652972686227200681074') * j
|
||||
r = jtheta(4, z, q)
|
||||
assert(mpc_ae(r, res))
|
||||
|
||||
# check some theta function identities
|
||||
mp.dos = 100
|
||||
z = mpf(1)/4 + j/8
|
||||
q = mpf(1)/3 + j/7
|
||||
mp.dps += 10
|
||||
a = [0,0, jtheta(2, 0, q), jtheta(3, 0, q), jtheta(4, 0, q)]
|
||||
t = [0, jtheta(1, z, q), jtheta(2, z, q), jtheta(3, z, q), jtheta(4, z, q)]
|
||||
r = [(t[2]*a[4])**2 - (t[4]*a[2])**2 + (t[1] *a[3])**2,
|
||||
(t[3]*a[4])**2 - (t[4]*a[3])**2 + (t[1] *a[2])**2,
|
||||
(t[1]*a[4])**2 - (t[3]*a[2])**2 + (t[2] *a[3])**2,
|
||||
(t[4]*a[4])**2 - (t[3]*a[3])**2 + (t[2] *a[2])**2,
|
||||
a[2]**4 + a[4]**4 - a[3]**4]
|
||||
mp.dps -= 10
|
||||
for x in r:
|
||||
assert(mpc_ae(x, mpc(0)))
|
||||
mp.dps = 15
|
||||
|
||||
def test_djtheta():
|
||||
mp.dps = 30
|
||||
|
||||
z = one/7 + j/3
|
||||
q = one/8 + j/5
|
||||
# Mathematica N[EllipticThetaPrime[1, 1/7 + I/3, 1/8 + I/5], 35]
|
||||
res = mpf('1.5555195883277196036090928995803201') - \
|
||||
mpf('0.02439761276895463494054149673076275') * j
|
||||
result = jtheta(1, z, q, 1)
|
||||
assert(mpc_ae(result, res))
|
||||
|
||||
# Mathematica N[EllipticThetaPrime[2, 1/7 + I/3, 1/8 + I/5], 35]
|
||||
res = mpf('0.19825296689470982332701283509685662') - \
|
||||
mpf('0.46038135182282106983251742935250009') * j
|
||||
result = jtheta(2, z, q, 1)
|
||||
assert(mpc_ae(result, res))
|
||||
|
||||
# Mathematica N[EllipticThetaPrime[3, 1/7 + I/3, 1/8 + I/5], 35]
|
||||
res = mpf('0.36492498415476212680896699407390026') - \
|
||||
mpf('0.57743812698666990209897034525640369') * j
|
||||
result = jtheta(3, z, q, 1)
|
||||
assert(mpc_ae(result, res))
|
||||
|
||||
# Mathematica N[EllipticThetaPrime[4, 1/7 + I/3, 1/8 + I/5], 35]
|
||||
res = mpf('-0.38936892528126996010818803742007352') + \
|
||||
mpf('0.66549886179739128256269617407313625') * j
|
||||
result = jtheta(4, z, q, 1)
|
||||
assert(mpc_ae(result, res))
|
||||
|
||||
for i in range(10):
|
||||
q = (one*random.random() + j*random.random())/2
|
||||
# identity in Wittaker, Watson &21.41
|
||||
a = jtheta(1, 0, q, 1)
|
||||
b = jtheta(2, 0, q)*jtheta(3, 0, q)*jtheta(4, 0, q)
|
||||
assert(a.ae(b))
|
||||
|
||||
# test higher derivatives
|
||||
mp.dps = 20
|
||||
for q,z in [(one/3, one/5), (one/3 + j/8, one/5),
|
||||
(one/3, one/5 + j/8), (one/3 + j/7, one/5 + j/8)]:
|
||||
for n in [1, 2, 3, 4]:
|
||||
r = jtheta(n, z, q, 2)
|
||||
r1 = diff(lambda zz: jtheta(n, zz, q), z, n=2)
|
||||
assert r.ae(r1)
|
||||
r = jtheta(n, z, q, 3)
|
||||
r1 = diff(lambda zz: jtheta(n, zz, q), z, n=3)
|
||||
assert r.ae(r1)
|
||||
|
||||
# identity in Wittaker, Watson &21.41
|
||||
q = one/3
|
||||
z = zero
|
||||
a = [0]*5
|
||||
a[1] = jtheta(1, z, q, 3)/jtheta(1, z, q, 1)
|
||||
for n in [2,3,4]:
|
||||
a[n] = jtheta(n, z, q, 2)/jtheta(n, z, q)
|
||||
equality = a[2] + a[3] + a[4] - a[1]
|
||||
assert(equality.ae(0))
|
||||
mp.dps = 15
|
||||
|
||||
def test_jsn():
|
||||
"""
|
||||
Test some special cases of the sn(z, q) function.
|
||||
"""
|
||||
mp.dps = 100
|
||||
|
||||
# trival case
|
||||
result = jsn(zero, zero)
|
||||
assert(result == zero)
|
||||
|
||||
# Abramowitz Table 16.5
|
||||
#
|
||||
# sn(0, m) = 0
|
||||
|
||||
for i in range(10):
|
||||
qstring = str(random.random())
|
||||
q = mpf(qstring)
|
||||
|
||||
equality = jsn(zero, q)
|
||||
assert(equality.ae(0))
|
||||
|
||||
# Abramowitz Table 16.6.1
|
||||
#
|
||||
# sn(z, 0) = sin(z), m == 0
|
||||
#
|
||||
# sn(z, 1) = tanh(z), m == 1
|
||||
#
|
||||
# It would be nice to test these, but I find that they run
|
||||
# in to numerical trouble. I'm currently treating as a boundary
|
||||
# case for sn function.
|
||||
|
||||
mp.dps = 25
|
||||
arg = one/10
|
||||
#N[JacobiSN[1/10, 2^-100], 25]
|
||||
res = mpf('0.09983341664682815230681420')
|
||||
m = ldexp(one, -100)
|
||||
result = jsn(arg, m)
|
||||
assert(result.ae(res))
|
||||
|
||||
# N[JacobiSN[1/10, 1/10], 25]
|
||||
res = mpf('0.09981686718599080096451168')
|
||||
result = jsn(arg, arg)
|
||||
assert(result.ae(res))
|
||||
mp.dps = 15
|
||||
|
||||
def test_jcn():
|
||||
"""
|
||||
Test some special cases of the cn(z, q) function.
|
||||
"""
|
||||
mp.dps = 100
|
||||
|
||||
# Abramowitz Table 16.5
|
||||
# cn(0, q) = 1
|
||||
qstring = str(random.random())
|
||||
q = mpf(qstring)
|
||||
cn = jcn(zero, q)
|
||||
assert(cn.ae(one))
|
||||
|
||||
# Abramowitz Table 16.6.2
|
||||
#
|
||||
# cn(u, 0) = cos(u), m == 0
|
||||
#
|
||||
# cn(u, 1) = sech(z), m == 1
|
||||
#
|
||||
# It would be nice to test these, but I find that they run
|
||||
# in to numerical trouble. I'm currently treating as a boundary
|
||||
# case for cn function.
|
||||
|
||||
mp.dps = 25
|
||||
arg = one/10
|
||||
m = ldexp(one, -100)
|
||||
#N[JacobiCN[1/10, 2^-100], 25]
|
||||
res = mpf('0.9950041652780257660955620')
|
||||
result = jcn(arg, m)
|
||||
assert(result.ae(res))
|
||||
|
||||
# N[JacobiCN[1/10, 1/10], 25]
|
||||
res = mpf('0.9950058256237368748520459')
|
||||
result = jcn(arg, arg)
|
||||
assert(result.ae(res))
|
||||
mp.dps = 15
|
||||
|
||||
def test_jdn():
|
||||
"""
|
||||
Test some special cases of the dn(z, q) function.
|
||||
"""
|
||||
mp.dps = 100
|
||||
|
||||
# Abramowitz Table 16.5
|
||||
# dn(0, q) = 1
|
||||
mstring = str(random.random())
|
||||
m = mpf(mstring)
|
||||
|
||||
dn = jdn(zero, m)
|
||||
assert(dn.ae(one))
|
||||
|
||||
mp.dps = 25
|
||||
# N[JacobiDN[1/10, 1/10], 25]
|
||||
res = mpf('0.9995017055025556219713297')
|
||||
arg = one/10
|
||||
result = jdn(arg, arg)
|
||||
assert(result.ae(res))
|
||||
mp.dps = 15
|
||||
|
||||
|
||||
def test_sn_cn_dn_identities():
|
||||
"""
|
||||
Tests the some of the jacobi elliptic function identities found
|
||||
on Mathworld. Haven't found in Abramowitz.
|
||||
"""
|
||||
mp.dps = 100
|
||||
N = 5
|
||||
for i in range(N):
|
||||
qstring = str(random.random())
|
||||
q = mpf(qstring)
|
||||
zstring = str(100*random.random())
|
||||
z = mpf(zstring)
|
||||
|
||||
# MathWorld
|
||||
# sn(z, q)**2 + cn(z, q)**2 == 1
|
||||
term1 = jsn(z, q)**2
|
||||
term2 = jcn(z, q)**2
|
||||
equality = one - term1 - term2
|
||||
assert(equality.ae(0))
|
||||
|
||||
# MathWorld
|
||||
# k**2 * sn(z, m)**2 + dn(z, m)**2 == 1
|
||||
for i in range(N):
|
||||
mstring = str(random.random())
|
||||
m = mpf(qstring)
|
||||
k = m.sqrt()
|
||||
zstring = str(10*random.random())
|
||||
z = mpf(zstring)
|
||||
term1 = k**2 * jsn(z, m)**2
|
||||
term2 = jdn(z, m)**2
|
||||
equality = one - term1 - term2
|
||||
assert(equality.ae(0))
|
||||
|
||||
|
||||
for i in range(N):
|
||||
mstring = str(random.random())
|
||||
m = mpf(mstring)
|
||||
k = m.sqrt()
|
||||
zstring = str(random.random())
|
||||
z = mpf(zstring)
|
||||
|
||||
# MathWorld
|
||||
# k**2 * cn(z, m)**2 + (1 - k**2) = dn(z, m)**2
|
||||
term1 = k**2 * jcn(z, m)**2
|
||||
term2 = 1 - k**2
|
||||
term3 = jdn(z, m)**2
|
||||
equality = term3 - term1 - term2
|
||||
assert(equality.ae(0))
|
||||
|
||||
K = ellipk(k**2)
|
||||
# Abramowitz Table 16.5
|
||||
# sn(K, m) = 1; K is K(k), first complete elliptic integral
|
||||
r = jsn(K, m)
|
||||
assert(r.ae(one))
|
||||
|
||||
# Abramowitz Table 16.5
|
||||
# cn(K, q) = 0; K is K(k), first complete elliptic integral
|
||||
equality = jcn(K, m)
|
||||
assert(equality.ae(0))
|
||||
|
||||
# Abramowitz Table 16.6.3
|
||||
# dn(z, 0) = 1, m == 0
|
||||
z = m
|
||||
value = jdn(z, zero)
|
||||
assert(value.ae(one))
|
||||
|
||||
mp.dps = 15
|
||||
|
||||
def test_sn_cn_dn_complex():
|
||||
mp.dps = 30
|
||||
# N[JacobiSN[1/4 + I/8, 1/3 + I/7], 35] in Mathematica
|
||||
res = mpf('0.2495674401066275492326652143537') + \
|
||||
mpf('0.12017344422863833381301051702823') * j
|
||||
u = mpf(1)/4 + j/8
|
||||
m = mpf(1)/3 + j/7
|
||||
r = jsn(u, m)
|
||||
assert(mpc_ae(r, res))
|
||||
|
||||
#N[JacobiCN[1/4 + I/8, 1/3 + I/7], 35]
|
||||
res = mpf('0.9762691700944007312693721148331') - \
|
||||
mpf('0.0307203994181623243583169154824')*j
|
||||
r = jcn(u, m)
|
||||
#assert r.real.ae(res.real)
|
||||
#assert r.imag.ae(res.imag)
|
||||
assert(mpc_ae(r, res))
|
||||
|
||||
#N[JacobiDN[1/4 + I/8, 1/3 + I/7], 35]
|
||||
res = mpf('0.99639490163039577560547478589753039') - \
|
||||
mpf('0.01346296520008176393432491077244994')*j
|
||||
r = jdn(u, m)
|
||||
assert(mpc_ae(r, res))
|
||||
mp.dps = 15
|
||||
|
||||
def test_elliptic_integrals():
|
||||
# Test cases from Carlson's paper
|
||||
mp.dps = 15
|
||||
assert elliprd(0,2,1).ae(1.7972103521033883112)
|
||||
assert elliprd(2,3,4).ae(0.16510527294261053349)
|
||||
assert elliprd(j,-j,2).ae(0.65933854154219768919)
|
||||
assert elliprd(0,j,-j).ae(1.2708196271909686299 + 2.7811120159520578777j)
|
||||
assert elliprd(0,j-1,j).ae(-1.8577235439239060056 - 0.96193450888838559989j)
|
||||
assert elliprd(-2-j,-j,-1+j).ae(1.8249027393703805305 - 1.2218475784827035855j)
|
||||
# extra test cases
|
||||
assert elliprg(0,0,0) == 0
|
||||
assert elliprg(0,0,16).ae(2)
|
||||
assert elliprg(0,16,0).ae(2)
|
||||
assert elliprg(16,0,0).ae(2)
|
||||
assert elliprg(1,4,0).ae(1.2110560275684595248036)
|
||||
assert elliprg(1,0,4).ae(1.2110560275684595248036)
|
||||
assert elliprg(0,4,1).ae(1.2110560275684595248036)
|
||||
# should be symmetric -- fixes a bug present in the paper
|
||||
x,y,z = 1,1j,-1+1j
|
||||
assert elliprg(x,y,z).ae(0.64139146875812627545 + 0.58085463774808290907j)
|
||||
assert elliprg(x,z,y).ae(0.64139146875812627545 + 0.58085463774808290907j)
|
||||
assert elliprg(y,x,z).ae(0.64139146875812627545 + 0.58085463774808290907j)
|
||||
assert elliprg(y,z,x).ae(0.64139146875812627545 + 0.58085463774808290907j)
|
||||
assert elliprg(z,x,y).ae(0.64139146875812627545 + 0.58085463774808290907j)
|
||||
assert elliprg(z,y,x).ae(0.64139146875812627545 + 0.58085463774808290907j)
|
||||
|
||||
for n in [5, 15, 30, 60, 100]:
|
||||
mp.dps = n
|
||||
assert elliprf(1,2,0).ae('1.3110287771460599052324197949455597068413774757158115814084108519003952935352071251151477664807145467230678763')
|
||||
assert elliprf(0.5,1,0).ae('1.854074677301371918433850347195260046217598823521766905585928045056021776838119978357271861650371897277771871')
|
||||
assert elliprf(j,-j,0).ae('1.854074677301371918433850347195260046217598823521766905585928045056021776838119978357271861650371897277771871')
|
||||
assert elliprf(j-1,j,0).ae(mpc('0.79612586584233913293056938229563057846592264089185680214929401744498956943287031832657642790719940442165621412',
|
||||
'-1.2138566698364959864300942567386038975419875860741507618279563735753073152507112254567291141460317931258599889'))
|
||||
assert elliprf(2,3,4).ae('0.58408284167715170669284916892566789240351359699303216166309375305508295130412919665541330837704050454472379308')
|
||||
assert elliprf(j,-j,2).ae('1.0441445654064360931078658361850779139591660747973017593275012615517220315993723776182276555339288363064476126')
|
||||
assert elliprf(j-1,j,1-j).ae(mpc('0.93912050218619371196624617169781141161485651998254431830645241993282941057500174238125105410055253623847335313',
|
||||
'-0.53296252018635269264859303449447908970360344322834582313172115220559316331271520508208025270300138589669326136'))
|
||||
assert elliprc(0,0.25).ae(+pi)
|
||||
assert elliprc(2.25,2).ae(+ln2)
|
||||
assert elliprc(0,j).ae(mpc('1.1107207345395915617539702475151734246536554223439225557713489017391086982748684776438317336911913093408525532',
|
||||
'-1.1107207345395915617539702475151734246536554223439225557713489017391086982748684776438317336911913093408525532'))
|
||||
assert elliprc(-j,j).ae(mpc('1.2260849569072198222319655083097718755633725139745941606203839524036426936825652935738621522906572884239069297',
|
||||
'-0.34471136988767679699935618332997956653521218571295874986708834375026550946053920574015526038040124556716711353'))
|
||||
assert elliprc(0.25,-2).ae(ln2/3)
|
||||
assert elliprc(j,-1).ae(mpc('0.77778596920447389875196055840799837589537035343923012237628610795937014001905822029050288316217145443865649819',
|
||||
'0.1983248499342877364755170948292130095921681309577950696116251029742793455964385947473103628983664877025779304'))
|
||||
assert elliprj(0,1,2,3).ae('0.77688623778582332014190282640545501102298064276022952731669118325952563819813258230708177398475643634103990878')
|
||||
assert elliprj(2,3,4,5).ae('0.14297579667156753833233879421985774801466647854232626336218889885463800128817976132826443904216546421431528308')
|
||||
assert elliprj(2,3,4,-1+j).ae(mpc('0.13613945827770535203521374457913768360237593025944342652613569368333226052158214183059386307242563164036672709',
|
||||
'-0.38207561624427164249600936454845112611060375760094156571007648297226090050927156176977091273224510621553615189'))
|
||||
assert elliprj(j,-j,0,2).ae('1.6490011662710884518243257224860232300246792717163891216346170272567376981346412066066050103935109581019055806')
|
||||
assert elliprj(-1+j,-1-j,1,2).ae('0.94148358841220238083044612133767270187474673547917988681610772381758628963408843935027667916713866133196845063')
|
||||
assert elliprj(j,-j,0,1-j).ae(mpc('1.8260115229009316249372594065790946657011067182850435297162034335356430755397401849070610280860044610878657501',
|
||||
'1.2290661908643471500163617732957042849283739403009556715926326841959667290840290081010472716420690899886276961'))
|
||||
assert elliprj(-1+j,-1-j,1,-3+j).ae(mpc('-0.61127970812028172123588152373622636829986597243716610650831553882054127570542477508023027578037045504958619422',
|
||||
'-1.0684038390006807880182112972232562745485871763154040245065581157751693730095703406209466903752930797510491155'))
|
||||
assert elliprj(-1+j,-2-j,-j,-1+j).ae(mpc('1.8249027393703805304622013339009022294368078659619988943515764258335975852685224202567854526307030593012768954',
|
||||
'-1.2218475784827035854568450371590419833166777535029296025352291308244564398645467465067845461070602841312456831'))
|
||||
|
||||
assert elliprg(0,16,16).ae(+pi)
|
||||
assert elliprg(2,3,4).ae('1.7255030280692277601061148835701141842692457170470456590515892070736643637303053506944907685301315299153040991')
|
||||
assert elliprg(0,j,-j).ae('0.42360654239698954330324956174109581824072295516347109253028968632986700241706737986160014699730561497106114281')
|
||||
assert elliprg(j-1,j,0).ae(mpc('0.44660591677018372656731970402124510811555212083508861036067729944477855594654762496407405328607219895053798354',
|
||||
'0.70768352357515390073102719507612395221369717586839400605901402910893345301718731499237159587077682267374159282'))
|
||||
assert elliprg(-j,j-1,j).ae(mpc('0.36023392184473309033675652092928695596803358846377334894215349632203382573844427952830064383286995172598964266',
|
||||
'0.40348623401722113740956336997761033878615232917480045914551915169013722542827052849476969199578321834819903921'))
|
||||
assert elliprg(0, mpf('0.0796'), 4).ae('1.0284758090288040009838871385180217366569777284430590125081211090574701293154645750017813190805144572673802094')
|
||||
mp.dps = 15
|
||||
|
||||
# more test cases for the branch of ellippi / elliprj
|
||||
assert elliprj(-1-0.5j, -10-6j, -10-3j, -5+10j).ae(0.128470516743927699 + 0.102175950778504625j, abs_eps=1e-8)
|
||||
assert elliprj(1.987, 4.463 - 1.614j, 0, -3.965).ae(-0.341575118513811305 - 0.394703757004268486j, abs_eps=1e-8)
|
||||
assert elliprj(0.3068, -4.037+0.632j, 1.654, -0.9609).ae(-1.14735199581485639 - 0.134450158867472264j, abs_eps=1e-8)
|
||||
assert elliprj(0.3068, -4.037-0.632j, 1.654, -0.9609).ae(1.758765901861727 - 0.161002343366626892j, abs_eps=1e-5)
|
||||
assert elliprj(0.3068, -4.037+0.0632j, 1.654, -0.9609).ae(-1.17157627949475577 - 0.069182614173988811j, abs_eps=1e-8)
|
||||
assert elliprj(0.3068, -4.037+0.00632j, 1.654, -0.9609).ae(-1.17337595670549633 - 0.0623069224526925j, abs_eps=1e-8)
|
||||
|
||||
# these require accurate integration
|
||||
assert elliprj(0.3068, -4.037-0.0632j, 1.654, -0.9609).ae(1.77940452391261626 + 0.0388711305592447234j)
|
||||
assert elliprj(0.3068, -4.037-0.00632j, 1.654, -0.9609).ae(1.77806722756403055 + 0.0592749824572262329j)
|
||||
# issue #571
|
||||
assert ellippi(2.1 + 0.94j, 2.3 + 0.98j, 2.5 + 0.01j).ae(-0.40652414240811963438 + 2.1547659461404749309j)
|
||||
|
||||
assert ellippi(2.0-1.0j, 2.0+1.0j).ae(1.8578723151271115 - 1.18642180609983531j)
|
||||
assert ellippi(2.0-0.5j, 0.5+1.0j).ae(0.936761970766645807 - 1.61876787838890786j)
|
||||
assert ellippi(2.0, 1.0+1.0j).ae(0.999881420735506708 - 2.4139272867045391j)
|
||||
assert ellippi(2.0+1.0j, 2.0-1.0j).ae(1.8578723151271115 + 1.18642180609983531j)
|
||||
assert ellippi(2.0+1.0j, 2.0).ae(2.78474654927885845 + 2.02204728966993314j)
|
||||
|
||||
def test_issue_238():
|
||||
assert isnan(qfrom(m=nan))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,920 @@
|
||||
from mpmath.libmp import *
|
||||
from mpmath import *
|
||||
import random
|
||||
import time
|
||||
import math
|
||||
import cmath
|
||||
|
||||
def mpc_ae(a, b, eps=eps):
|
||||
res = True
|
||||
res = res and a.real.ae(b.real, eps)
|
||||
res = res and a.imag.ae(b.imag, eps)
|
||||
return res
|
||||
|
||||
#----------------------------------------------------------------------------
|
||||
# Constants and functions
|
||||
#
|
||||
|
||||
tpi = "3.1415926535897932384626433832795028841971693993751058209749445923078\
|
||||
1640628620899862803482534211706798"
|
||||
te = "2.71828182845904523536028747135266249775724709369995957496696762772407\
|
||||
663035354759457138217852516642743"
|
||||
tdegree = "0.017453292519943295769236907684886127134428718885417254560971914\
|
||||
4017100911460344944368224156963450948221"
|
||||
teuler = "0.5772156649015328606065120900824024310421593359399235988057672348\
|
||||
84867726777664670936947063291746749516"
|
||||
tln2 = "0.693147180559945309417232121458176568075500134360255254120680009493\
|
||||
393621969694715605863326996418687542"
|
||||
tln10 = "2.30258509299404568401799145468436420760110148862877297603332790096\
|
||||
757260967735248023599720508959829834"
|
||||
tcatalan = "0.91596559417721901505460351493238411077414937428167213426649811\
|
||||
9621763019776254769479356512926115106249"
|
||||
tkhinchin = "2.6854520010653064453097148354817956938203822939944629530511523\
|
||||
4555721885953715200280114117493184769800"
|
||||
tglaisher = "1.2824271291006226368753425688697917277676889273250011920637400\
|
||||
2174040630885882646112973649195820237439420646"
|
||||
tapery = "1.2020569031595942853997381615114499907649862923404988817922715553\
|
||||
4183820578631309018645587360933525815"
|
||||
tphi = "1.618033988749894848204586834365638117720309179805762862135448622705\
|
||||
26046281890244970720720418939113748475"
|
||||
tmertens = "0.26149721284764278375542683860869585905156664826119920619206421\
|
||||
3924924510897368209714142631434246651052"
|
||||
ttwinprime = "0.660161815846869573927812110014555778432623360284733413319448\
|
||||
423335405642304495277143760031413839867912"
|
||||
|
||||
def test_constants():
|
||||
for prec in [3, 7, 10, 15, 20, 37, 80, 100, 29]:
|
||||
mp.dps = prec
|
||||
assert pi == mpf(tpi)
|
||||
assert e == mpf(te)
|
||||
assert degree == mpf(tdegree)
|
||||
assert euler == mpf(teuler)
|
||||
assert ln2 == mpf(tln2)
|
||||
assert ln10 == mpf(tln10)
|
||||
assert catalan == mpf(tcatalan)
|
||||
assert khinchin == mpf(tkhinchin)
|
||||
assert glaisher == mpf(tglaisher)
|
||||
assert phi == mpf(tphi)
|
||||
if prec < 50:
|
||||
assert mertens == mpf(tmertens)
|
||||
assert twinprime == mpf(ttwinprime)
|
||||
mp.dps = 15
|
||||
assert pi >= -1
|
||||
assert pi > 2
|
||||
assert pi > 3
|
||||
assert pi < 4
|
||||
|
||||
def test_exact_sqrts():
|
||||
for i in range(20000):
|
||||
assert sqrt(mpf(i*i)) == i
|
||||
random.seed(1)
|
||||
for prec in [100, 300, 1000, 10000]:
|
||||
mp.dps = prec
|
||||
for i in range(20):
|
||||
A = random.randint(10**(prec//2-2), 10**(prec//2-1))
|
||||
assert sqrt(mpf(A*A)) == A
|
||||
mp.dps = 15
|
||||
for i in range(100):
|
||||
for a in [1, 8, 25, 112307]:
|
||||
assert sqrt(mpf((a*a, 2*i))) == mpf((a, i))
|
||||
assert sqrt(mpf((a*a, -2*i))) == mpf((a, -i))
|
||||
|
||||
def test_sqrt_rounding():
|
||||
for i in [2, 3, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15]:
|
||||
i = from_int(i)
|
||||
for dps in [7, 15, 83, 106, 2000]:
|
||||
mp.dps = dps
|
||||
a = mpf_pow_int(mpf_sqrt(i, mp.prec, round_down), 2, mp.prec, round_down)
|
||||
b = mpf_pow_int(mpf_sqrt(i, mp.prec, round_up), 2, mp.prec, round_up)
|
||||
assert mpf_lt(a, i)
|
||||
assert mpf_gt(b, i)
|
||||
random.seed(1234)
|
||||
prec = 100
|
||||
for rnd in [round_down, round_nearest, round_ceiling]:
|
||||
for i in range(100):
|
||||
a = mpf_rand(prec)
|
||||
b = mpf_mul(a, a)
|
||||
assert mpf_sqrt(b, prec, rnd) == a
|
||||
# Test some extreme cases
|
||||
mp.dps = 100
|
||||
a = mpf(9) + 1e-90
|
||||
b = mpf(9) - 1e-90
|
||||
mp.dps = 15
|
||||
assert sqrt(a, rounding='d') == 3
|
||||
assert sqrt(a, rounding='n') == 3
|
||||
assert sqrt(a, rounding='u') > 3
|
||||
assert sqrt(b, rounding='d') < 3
|
||||
assert sqrt(b, rounding='n') == 3
|
||||
assert sqrt(b, rounding='u') == 3
|
||||
# A worst case, from the MPFR test suite
|
||||
assert sqrt(mpf('7.0503726185518891')) == mpf('2.655253776675949')
|
||||
|
||||
def test_float_sqrt():
|
||||
mp.dps = 15
|
||||
# These should round identically
|
||||
for x in [0, 1e-7, 0.1, 0.5, 1, 2, 3, 4, 5, 0.333, 76.19]:
|
||||
assert sqrt(mpf(x)) == float(x)**0.5
|
||||
assert sqrt(-1) == 1j
|
||||
assert sqrt(-2).ae(cmath.sqrt(-2))
|
||||
assert sqrt(-3).ae(cmath.sqrt(-3))
|
||||
assert sqrt(-100).ae(cmath.sqrt(-100))
|
||||
assert sqrt(1j).ae(cmath.sqrt(1j))
|
||||
assert sqrt(-1j).ae(cmath.sqrt(-1j))
|
||||
assert sqrt(math.pi + math.e*1j).ae(cmath.sqrt(math.pi + math.e*1j))
|
||||
assert sqrt(math.pi - math.e*1j).ae(cmath.sqrt(math.pi - math.e*1j))
|
||||
|
||||
def test_hypot():
|
||||
assert hypot(0, 0) == 0
|
||||
assert hypot(0, 0.33) == mpf(0.33)
|
||||
assert hypot(0.33, 0) == mpf(0.33)
|
||||
assert hypot(-0.33, 0) == mpf(0.33)
|
||||
assert hypot(3, 4) == mpf(5)
|
||||
|
||||
def test_exact_cbrt():
|
||||
for i in range(0, 20000, 200):
|
||||
assert cbrt(mpf(i*i*i)) == i
|
||||
random.seed(1)
|
||||
for prec in [100, 300, 1000, 10000]:
|
||||
mp.dps = prec
|
||||
A = random.randint(10**(prec//2-2), 10**(prec//2-1))
|
||||
assert cbrt(mpf(A*A*A)) == A
|
||||
mp.dps = 15
|
||||
|
||||
def test_exp():
|
||||
assert exp(0) == 1
|
||||
assert exp(10000).ae(mpf('8.8068182256629215873e4342'))
|
||||
assert exp(-10000).ae(mpf('1.1354838653147360985e-4343'))
|
||||
a = exp(mpf((1, 8198646019315405, -53, 53)))
|
||||
assert(a.bc == bitcount(a.man))
|
||||
mp.prec = 67
|
||||
a = exp(mpf((1, 1781864658064754565, -60, 61)))
|
||||
assert(a.bc == bitcount(a.man))
|
||||
mp.prec = 53
|
||||
assert exp(ln2 * 10).ae(1024)
|
||||
assert exp(2+2j).ae(cmath.exp(2+2j))
|
||||
|
||||
def test_issue_73():
|
||||
mp.dps = 512
|
||||
a = exp(-1)
|
||||
b = exp(1)
|
||||
mp.dps = 15
|
||||
assert (+a).ae(0.36787944117144233)
|
||||
assert (+b).ae(2.7182818284590451)
|
||||
|
||||
def test_log():
|
||||
mp.dps = 15
|
||||
assert log(1) == 0
|
||||
for x in [0.5, 1.5, 2.0, 3.0, 100, 10**50, 1e-50]:
|
||||
assert log(x).ae(math.log(x))
|
||||
assert log(x, x) == 1
|
||||
assert log(1024, 2) == 10
|
||||
assert log(10**1234, 10) == 1234
|
||||
assert log(2+2j).ae(cmath.log(2+2j))
|
||||
# Accuracy near 1
|
||||
assert (log(0.6+0.8j).real*10**17).ae(2.2204460492503131)
|
||||
assert (log(0.6-0.8j).real*10**17).ae(2.2204460492503131)
|
||||
assert (log(0.8-0.6j).real*10**17).ae(2.2204460492503131)
|
||||
assert (log(1+1e-8j).real*10**16).ae(0.5)
|
||||
assert (log(1-1e-8j).real*10**16).ae(0.5)
|
||||
assert (log(-1+1e-8j).real*10**16).ae(0.5)
|
||||
assert (log(-1-1e-8j).real*10**16).ae(0.5)
|
||||
assert (log(1j+1e-8).real*10**16).ae(0.5)
|
||||
assert (log(1j-1e-8).real*10**16).ae(0.5)
|
||||
assert (log(-1j+1e-8).real*10**16).ae(0.5)
|
||||
assert (log(-1j-1e-8).real*10**16).ae(0.5)
|
||||
assert (log(1+1e-40j).real*10**80).ae(0.5)
|
||||
assert (log(1j+1e-40).real*10**80).ae(0.5)
|
||||
# Huge
|
||||
assert log(ldexp(1.234,10**20)).ae(log(2)*1e20)
|
||||
assert log(ldexp(1.234,10**200)).ae(log(2)*1e200)
|
||||
# Some special values
|
||||
assert log(mpc(0,0)) == mpc(-inf,0)
|
||||
assert isnan(log(mpc(nan,0)).real)
|
||||
assert isnan(log(mpc(nan,0)).imag)
|
||||
assert isnan(log(mpc(0,nan)).real)
|
||||
assert isnan(log(mpc(0,nan)).imag)
|
||||
assert isnan(log(mpc(nan,1)).real)
|
||||
assert isnan(log(mpc(nan,1)).imag)
|
||||
assert isnan(log(mpc(1,nan)).real)
|
||||
assert isnan(log(mpc(1,nan)).imag)
|
||||
|
||||
def test_trig_hyperb_basic():
|
||||
for x in (list(range(100)) + list(range(-100,0))):
|
||||
t = x / 4.1
|
||||
assert cos(mpf(t)).ae(math.cos(t))
|
||||
assert sin(mpf(t)).ae(math.sin(t))
|
||||
assert tan(mpf(t)).ae(math.tan(t))
|
||||
assert cosh(mpf(t)).ae(math.cosh(t))
|
||||
assert sinh(mpf(t)).ae(math.sinh(t))
|
||||
assert tanh(mpf(t)).ae(math.tanh(t))
|
||||
assert sin(1+1j).ae(cmath.sin(1+1j))
|
||||
assert sin(-4-3.6j).ae(cmath.sin(-4-3.6j))
|
||||
assert cos(1+1j).ae(cmath.cos(1+1j))
|
||||
assert cos(-4-3.6j).ae(cmath.cos(-4-3.6j))
|
||||
|
||||
def test_degrees():
|
||||
assert cos(0*degree) == 1
|
||||
assert cos(90*degree).ae(0)
|
||||
assert cos(180*degree).ae(-1)
|
||||
assert cos(270*degree).ae(0)
|
||||
assert cos(360*degree).ae(1)
|
||||
assert sin(0*degree) == 0
|
||||
assert sin(90*degree).ae(1)
|
||||
assert sin(180*degree).ae(0)
|
||||
assert sin(270*degree).ae(-1)
|
||||
assert sin(360*degree).ae(0)
|
||||
|
||||
def random_complexes(N):
|
||||
random.seed(1)
|
||||
a = []
|
||||
for i in range(N):
|
||||
x1 = random.uniform(-10, 10)
|
||||
y1 = random.uniform(-10, 10)
|
||||
x2 = random.uniform(-10, 10)
|
||||
y2 = random.uniform(-10, 10)
|
||||
z1 = complex(x1, y1)
|
||||
z2 = complex(x2, y2)
|
||||
a.append((z1, z2))
|
||||
return a
|
||||
|
||||
def test_complex_powers():
|
||||
for dps in [15, 30, 100]:
|
||||
# Check accuracy for complex square root
|
||||
mp.dps = dps
|
||||
a = mpc(1j)**0.5
|
||||
assert a.real == a.imag == mpf(2)**0.5 / 2
|
||||
mp.dps = 15
|
||||
random.seed(1)
|
||||
for (z1, z2) in random_complexes(100):
|
||||
assert (mpc(z1)**mpc(z2)).ae(z1**z2, 1e-12)
|
||||
assert (e**(-pi*1j)).ae(-1)
|
||||
mp.dps = 50
|
||||
assert (e**(-pi*1j)).ae(-1)
|
||||
mp.dps = 15
|
||||
|
||||
def test_complex_sqrt_accuracy():
|
||||
def test_mpc_sqrt(lst):
|
||||
for a, b in lst:
|
||||
z = mpc(a + j*b)
|
||||
assert mpc_ae(sqrt(z*z), z)
|
||||
z = mpc(-a + j*b)
|
||||
assert mpc_ae(sqrt(z*z), -z)
|
||||
z = mpc(a - j*b)
|
||||
assert mpc_ae(sqrt(z*z), z)
|
||||
z = mpc(-a - j*b)
|
||||
assert mpc_ae(sqrt(z*z), -z)
|
||||
random.seed(2)
|
||||
N = 10
|
||||
mp.dps = 30
|
||||
dps = mp.dps
|
||||
test_mpc_sqrt([(random.uniform(0, 10),random.uniform(0, 10)) for i in range(N)])
|
||||
test_mpc_sqrt([(i + 0.1, (i + 0.2)*10**i) for i in range(N)])
|
||||
mp.dps = 15
|
||||
|
||||
def test_atan():
|
||||
mp.dps = 15
|
||||
assert atan(-2.3).ae(math.atan(-2.3))
|
||||
assert atan(1e-50) == 1e-50
|
||||
assert atan(1e50).ae(pi/2)
|
||||
assert atan(-1e-50) == -1e-50
|
||||
assert atan(-1e50).ae(-pi/2)
|
||||
assert atan(10**1000).ae(pi/2)
|
||||
for dps in [25, 70, 100, 300, 1000]:
|
||||
mp.dps = dps
|
||||
assert (4*atan(1)).ae(pi)
|
||||
mp.dps = 15
|
||||
pi2 = pi/2
|
||||
assert atan(mpc(inf,-1)).ae(pi2)
|
||||
assert atan(mpc(inf,0)).ae(pi2)
|
||||
assert atan(mpc(inf,1)).ae(pi2)
|
||||
assert atan(mpc(1,inf)).ae(pi2)
|
||||
assert atan(mpc(0,inf)).ae(pi2)
|
||||
assert atan(mpc(-1,inf)).ae(-pi2)
|
||||
assert atan(mpc(-inf,1)).ae(-pi2)
|
||||
assert atan(mpc(-inf,0)).ae(-pi2)
|
||||
assert atan(mpc(-inf,-1)).ae(-pi2)
|
||||
assert atan(mpc(-1,-inf)).ae(-pi2)
|
||||
assert atan(mpc(0,-inf)).ae(-pi2)
|
||||
assert atan(mpc(1,-inf)).ae(pi2)
|
||||
|
||||
def test_atan2():
|
||||
mp.dps = 15
|
||||
assert atan2(1,1).ae(pi/4)
|
||||
assert atan2(1,-1).ae(3*pi/4)
|
||||
assert atan2(-1,-1).ae(-3*pi/4)
|
||||
assert atan2(-1,1).ae(-pi/4)
|
||||
assert atan2(-1,0).ae(-pi/2)
|
||||
assert atan2(1,0).ae(pi/2)
|
||||
assert atan2(0,0) == 0
|
||||
assert atan2(inf,0).ae(pi/2)
|
||||
assert atan2(-inf,0).ae(-pi/2)
|
||||
assert isnan(atan2(inf,inf))
|
||||
assert isnan(atan2(-inf,inf))
|
||||
assert isnan(atan2(inf,-inf))
|
||||
assert isnan(atan2(3,nan))
|
||||
assert isnan(atan2(nan,3))
|
||||
assert isnan(atan2(0,nan))
|
||||
assert isnan(atan2(nan,0))
|
||||
assert atan2(0,inf) == 0
|
||||
assert atan2(0,-inf).ae(pi)
|
||||
assert atan2(10,inf) == 0
|
||||
assert atan2(-10,inf) == 0
|
||||
assert atan2(-10,-inf).ae(-pi)
|
||||
assert atan2(10,-inf).ae(pi)
|
||||
assert atan2(inf,10).ae(pi/2)
|
||||
assert atan2(inf,-10).ae(pi/2)
|
||||
assert atan2(-inf,10).ae(-pi/2)
|
||||
assert atan2(-inf,-10).ae(-pi/2)
|
||||
|
||||
def test_areal_inverses():
|
||||
assert asin(mpf(0)) == 0
|
||||
assert asinh(mpf(0)) == 0
|
||||
assert acosh(mpf(1)) == 0
|
||||
assert isinstance(asin(mpf(0.5)), mpf)
|
||||
assert isinstance(asin(mpf(2.0)), mpc)
|
||||
assert isinstance(acos(mpf(0.5)), mpf)
|
||||
assert isinstance(acos(mpf(2.0)), mpc)
|
||||
assert isinstance(atanh(mpf(0.1)), mpf)
|
||||
assert isinstance(atanh(mpf(1.1)), mpc)
|
||||
|
||||
random.seed(1)
|
||||
for i in range(50):
|
||||
x = random.uniform(0, 1)
|
||||
assert asin(mpf(x)).ae(math.asin(x))
|
||||
assert acos(mpf(x)).ae(math.acos(x))
|
||||
|
||||
x = random.uniform(-10, 10)
|
||||
assert asinh(mpf(x)).ae(cmath.asinh(x).real)
|
||||
assert isinstance(asinh(mpf(x)), mpf)
|
||||
x = random.uniform(1, 10)
|
||||
assert acosh(mpf(x)).ae(cmath.acosh(x).real)
|
||||
assert isinstance(acosh(mpf(x)), mpf)
|
||||
x = random.uniform(-10, 0.999)
|
||||
assert isinstance(acosh(mpf(x)), mpc)
|
||||
|
||||
x = random.uniform(-1, 1)
|
||||
assert atanh(mpf(x)).ae(cmath.atanh(x).real)
|
||||
assert isinstance(atanh(mpf(x)), mpf)
|
||||
|
||||
dps = mp.dps
|
||||
mp.dps = 300
|
||||
assert isinstance(asin(0.5), mpf)
|
||||
mp.dps = 1000
|
||||
assert asin(1).ae(pi/2)
|
||||
assert asin(-1).ae(-pi/2)
|
||||
mp.dps = dps
|
||||
|
||||
def test_invhyperb_inaccuracy():
|
||||
mp.dps = 15
|
||||
assert (asinh(1e-5)*10**5).ae(0.99999999998333333)
|
||||
assert (asinh(1e-10)*10**10).ae(1)
|
||||
assert (asinh(1e-50)*10**50).ae(1)
|
||||
assert (asinh(-1e-5)*10**5).ae(-0.99999999998333333)
|
||||
assert (asinh(-1e-10)*10**10).ae(-1)
|
||||
assert (asinh(-1e-50)*10**50).ae(-1)
|
||||
assert asinh(10**20).ae(46.744849040440862)
|
||||
assert asinh(-10**20).ae(-46.744849040440862)
|
||||
assert (tanh(1e-10)*10**10).ae(1)
|
||||
assert (tanh(-1e-10)*10**10).ae(-1)
|
||||
assert (atanh(1e-10)*10**10).ae(1)
|
||||
assert (atanh(-1e-10)*10**10).ae(-1)
|
||||
|
||||
def test_complex_functions():
|
||||
for x in (list(range(10)) + list(range(-10,0))):
|
||||
for y in (list(range(10)) + list(range(-10,0))):
|
||||
z = complex(x, y)/4.3 + 0.01j
|
||||
assert exp(mpc(z)).ae(cmath.exp(z))
|
||||
assert log(mpc(z)).ae(cmath.log(z))
|
||||
assert cos(mpc(z)).ae(cmath.cos(z))
|
||||
assert sin(mpc(z)).ae(cmath.sin(z))
|
||||
assert tan(mpc(z)).ae(cmath.tan(z))
|
||||
assert sinh(mpc(z)).ae(cmath.sinh(z))
|
||||
assert cosh(mpc(z)).ae(cmath.cosh(z))
|
||||
assert tanh(mpc(z)).ae(cmath.tanh(z))
|
||||
|
||||
def test_complex_inverse_functions():
|
||||
mp.dps = 15
|
||||
iv.dps = 15
|
||||
for (z1, z2) in random_complexes(30):
|
||||
# apparently cmath uses a different branch, so we
|
||||
# can't use it for comparison
|
||||
assert sinh(asinh(z1)).ae(z1)
|
||||
#
|
||||
assert acosh(z1).ae(cmath.acosh(z1))
|
||||
assert atanh(z1).ae(cmath.atanh(z1))
|
||||
assert atan(z1).ae(cmath.atan(z1))
|
||||
# the reason we set a big eps here is that the cmath
|
||||
# functions are inaccurate
|
||||
assert asin(z1).ae(cmath.asin(z1), rel_eps=1e-12)
|
||||
assert acos(z1).ae(cmath.acos(z1), rel_eps=1e-12)
|
||||
one = mpf(1)
|
||||
for i in range(-9, 10, 3):
|
||||
for k in range(-9, 10, 3):
|
||||
a = 0.9*j*10**k + 0.8*one*10**i
|
||||
b = cos(acos(a))
|
||||
assert b.ae(a)
|
||||
b = sin(asin(a))
|
||||
assert b.ae(a)
|
||||
one = mpf(1)
|
||||
err = 2*10**-15
|
||||
for i in range(-9, 9, 3):
|
||||
for k in range(-9, 9, 3):
|
||||
a = -0.9*10**k + j*0.8*one*10**i
|
||||
b = cosh(acosh(a))
|
||||
assert b.ae(a, err)
|
||||
b = sinh(asinh(a))
|
||||
assert b.ae(a, err)
|
||||
|
||||
def test_reciprocal_functions():
|
||||
assert sec(3).ae(-1.01010866590799375)
|
||||
assert csc(3).ae(7.08616739573718592)
|
||||
assert cot(3).ae(-7.01525255143453347)
|
||||
assert sech(3).ae(0.0993279274194332078)
|
||||
assert csch(3).ae(0.0998215696688227329)
|
||||
assert coth(3).ae(1.00496982331368917)
|
||||
assert asec(3).ae(1.23095941734077468)
|
||||
assert acsc(3).ae(0.339836909454121937)
|
||||
assert acot(3).ae(0.321750554396642193)
|
||||
assert asech(0.5).ae(1.31695789692481671)
|
||||
assert acsch(3).ae(0.327450150237258443)
|
||||
assert acoth(3).ae(0.346573590279972655)
|
||||
assert acot(0).ae(1.5707963267948966192)
|
||||
assert acoth(0).ae(1.5707963267948966192j)
|
||||
|
||||
def test_ldexp():
|
||||
mp.dps = 15
|
||||
assert ldexp(mpf(2.5), 0) == 2.5
|
||||
assert ldexp(mpf(2.5), -1) == 1.25
|
||||
assert ldexp(mpf(2.5), 2) == 10
|
||||
assert ldexp(mpf('inf'), 3) == mpf('inf')
|
||||
|
||||
def test_frexp():
|
||||
mp.dps = 15
|
||||
assert frexp(0) == (0.0, 0)
|
||||
assert frexp(9) == (0.5625, 4)
|
||||
assert frexp(1) == (0.5, 1)
|
||||
assert frexp(0.2) == (0.8, -2)
|
||||
assert frexp(1000) == (0.9765625, 10)
|
||||
|
||||
def test_aliases():
|
||||
assert ln(7) == log(7)
|
||||
assert log10(3.75) == log(3.75,10)
|
||||
assert degrees(5.6) == 5.6 / degree
|
||||
assert radians(5.6) == 5.6 * degree
|
||||
assert power(-1,0.5) == j
|
||||
assert fmod(25,7) == 4.0 and isinstance(fmod(25,7), mpf)
|
||||
|
||||
def test_arg_sign():
|
||||
assert arg(3) == 0
|
||||
assert arg(-3).ae(pi)
|
||||
assert arg(j).ae(pi/2)
|
||||
assert arg(-j).ae(-pi/2)
|
||||
assert arg(0) == 0
|
||||
assert isnan(atan2(3,nan))
|
||||
assert isnan(atan2(nan,3))
|
||||
assert isnan(atan2(0,nan))
|
||||
assert isnan(atan2(nan,0))
|
||||
assert isnan(atan2(nan,nan))
|
||||
assert arg(inf) == 0
|
||||
assert arg(-inf).ae(pi)
|
||||
assert isnan(arg(nan))
|
||||
#assert arg(inf*j).ae(pi/2)
|
||||
assert sign(0) == 0
|
||||
assert sign(3) == 1
|
||||
assert sign(-3) == -1
|
||||
assert sign(inf) == 1
|
||||
assert sign(-inf) == -1
|
||||
assert isnan(sign(nan))
|
||||
assert sign(j) == j
|
||||
assert sign(-3*j) == -j
|
||||
assert sign(1+j).ae((1+j)/sqrt(2))
|
||||
|
||||
def test_misc_bugs():
|
||||
# test that this doesn't raise an exception
|
||||
mp.dps = 1000
|
||||
log(1302)
|
||||
mp.dps = 15
|
||||
|
||||
def test_arange():
|
||||
assert arange(10) == [mpf('0.0'), mpf('1.0'), mpf('2.0'), mpf('3.0'),
|
||||
mpf('4.0'), mpf('5.0'), mpf('6.0'), mpf('7.0'),
|
||||
mpf('8.0'), mpf('9.0')]
|
||||
assert arange(-5, 5) == [mpf('-5.0'), mpf('-4.0'), mpf('-3.0'),
|
||||
mpf('-2.0'), mpf('-1.0'), mpf('0.0'),
|
||||
mpf('1.0'), mpf('2.0'), mpf('3.0'), mpf('4.0')]
|
||||
assert arange(0, 1, 0.1) == [mpf('0.0'), mpf('0.10000000000000001'),
|
||||
mpf('0.20000000000000001'),
|
||||
mpf('0.30000000000000004'),
|
||||
mpf('0.40000000000000002'),
|
||||
mpf('0.5'), mpf('0.60000000000000009'),
|
||||
mpf('0.70000000000000007'),
|
||||
mpf('0.80000000000000004'),
|
||||
mpf('0.90000000000000002')]
|
||||
assert arange(17, -9, -3) == [mpf('17.0'), mpf('14.0'), mpf('11.0'),
|
||||
mpf('8.0'), mpf('5.0'), mpf('2.0'),
|
||||
mpf('-1.0'), mpf('-4.0'), mpf('-7.0')]
|
||||
assert arange(0.2, 0.1, -0.1) == [mpf('0.20000000000000001')]
|
||||
assert arange(0) == []
|
||||
assert arange(1000, -1) == []
|
||||
assert arange(-1.23, 3.21, -0.0000001) == []
|
||||
|
||||
def test_linspace():
|
||||
assert linspace(2, 9, 7) == [mpf('2.0'), mpf('3.166666666666667'),
|
||||
mpf('4.3333333333333339'), mpf('5.5'), mpf('6.666666666666667'),
|
||||
mpf('7.8333333333333339'), mpf('9.0')]
|
||||
assert linspace(2, 9, 7, endpoint=0) == [mpf('2.0'), mpf('3.0'), mpf('4.0'),
|
||||
mpf('5.0'), mpf('6.0'), mpf('7.0'), mpf('8.0')]
|
||||
assert linspace(2, 7, 1) == [mpf(2)]
|
||||
|
||||
def test_float_cbrt():
|
||||
mp.dps = 30
|
||||
for a in arange(0,10,0.1):
|
||||
assert cbrt(a*a*a).ae(a, eps)
|
||||
assert cbrt(-1).ae(0.5 + j*sqrt(3)/2)
|
||||
one_third = mpf(1)/3
|
||||
for a in arange(0,10,2.7) + [0.1 + 10**5]:
|
||||
a = mpc(a + 1.1j)
|
||||
r1 = cbrt(a)
|
||||
mp.dps += 10
|
||||
r2 = pow(a, one_third)
|
||||
mp.dps -= 10
|
||||
assert r1.ae(r2, eps)
|
||||
mp.dps = 100
|
||||
for n in range(100, 301, 100):
|
||||
w = 10**n + j*10**-3
|
||||
z = w*w*w
|
||||
r = cbrt(z)
|
||||
assert mpc_ae(r, w, eps)
|
||||
mp.dps = 15
|
||||
|
||||
def test_root():
|
||||
mp.dps = 30
|
||||
random.seed(1)
|
||||
a = random.randint(0, 10000)
|
||||
p = a*a*a
|
||||
r = nthroot(mpf(p), 3)
|
||||
assert r == a
|
||||
for n in range(4, 10):
|
||||
p = p*a
|
||||
assert nthroot(mpf(p), n) == a
|
||||
mp.dps = 40
|
||||
for n in range(10, 5000, 100):
|
||||
for a in [random.random()*10000, random.random()*10**100]:
|
||||
r = nthroot(a, n)
|
||||
r1 = pow(a, mpf(1)/n)
|
||||
assert r.ae(r1)
|
||||
r = nthroot(a, -n)
|
||||
r1 = pow(a, -mpf(1)/n)
|
||||
assert r.ae(r1)
|
||||
# XXX: this is broken right now
|
||||
# tests for nthroot rounding
|
||||
for rnd in ['nearest', 'up', 'down']:
|
||||
mp.rounding = rnd
|
||||
for n in [-5, -3, 3, 5]:
|
||||
prec = 50
|
||||
for i in range(10):
|
||||
mp.prec = prec
|
||||
a = rand()
|
||||
mp.prec = 2*prec
|
||||
b = a**n
|
||||
mp.prec = prec
|
||||
r = nthroot(b, n)
|
||||
assert r == a
|
||||
mp.dps = 30
|
||||
for n in range(3, 21):
|
||||
a = (random.random() + j*random.random())
|
||||
assert nthroot(a, n).ae(pow(a, mpf(1)/n))
|
||||
assert mpc_ae(nthroot(a, n), pow(a, mpf(1)/n))
|
||||
a = (random.random()*10**100 + j*random.random())
|
||||
r = nthroot(a, n)
|
||||
mp.dps += 4
|
||||
r1 = pow(a, mpf(1)/n)
|
||||
mp.dps -= 4
|
||||
assert r.ae(r1)
|
||||
assert mpc_ae(r, r1, eps)
|
||||
r = nthroot(a, -n)
|
||||
mp.dps += 4
|
||||
r1 = pow(a, -mpf(1)/n)
|
||||
mp.dps -= 4
|
||||
assert r.ae(r1)
|
||||
assert mpc_ae(r, r1, eps)
|
||||
mp.dps = 15
|
||||
assert nthroot(4, 1) == 4
|
||||
assert nthroot(4, 0) == 1
|
||||
assert nthroot(4, -1) == 0.25
|
||||
assert nthroot(inf, 1) == inf
|
||||
assert nthroot(inf, 2) == inf
|
||||
assert nthroot(inf, 3) == inf
|
||||
assert nthroot(inf, -1) == 0
|
||||
assert nthroot(inf, -2) == 0
|
||||
assert nthroot(inf, -3) == 0
|
||||
assert nthroot(j, 1) == j
|
||||
assert nthroot(j, 0) == 1
|
||||
assert nthroot(j, -1) == -j
|
||||
assert isnan(nthroot(nan, 1))
|
||||
assert isnan(nthroot(nan, 0))
|
||||
assert isnan(nthroot(nan, -1))
|
||||
assert isnan(nthroot(inf, 0))
|
||||
assert root(2,3) == nthroot(2,3)
|
||||
assert root(16,4,0) == 2
|
||||
assert root(16,4,1) == 2j
|
||||
assert root(16,4,2) == -2
|
||||
assert root(16,4,3) == -2j
|
||||
assert root(16,4,4) == 2
|
||||
assert root(-125,3,1) == -5
|
||||
|
||||
def test_issue_136():
|
||||
for dps in [20, 80]:
|
||||
mp.dps = dps
|
||||
r = nthroot(mpf('-1e-20'), 4)
|
||||
assert r.ae(mpf(10)**(-5) * (1 + j) * mpf(2)**(-0.5))
|
||||
mp.dps = 80
|
||||
assert nthroot('-1e-3', 4).ae(mpf(10)**(-3./4) * (1 + j)/sqrt(2))
|
||||
assert nthroot('-1e-6', 4).ae((1 + j)/(10 * sqrt(20)))
|
||||
# Check that this doesn't take eternity to compute
|
||||
mp.dps = 20
|
||||
assert nthroot('-1e100000000', 4).ae((1+j)*mpf('1e25000000')/sqrt(2))
|
||||
mp.dps = 15
|
||||
|
||||
def test_mpcfun_real_imag():
|
||||
mp.dps = 15
|
||||
x = mpf(0.3)
|
||||
y = mpf(0.4)
|
||||
assert exp(mpc(x,0)) == exp(x)
|
||||
assert exp(mpc(0,y)) == mpc(cos(y),sin(y))
|
||||
assert cos(mpc(x,0)) == cos(x)
|
||||
assert sin(mpc(x,0)) == sin(x)
|
||||
assert cos(mpc(0,y)) == cosh(y)
|
||||
assert sin(mpc(0,y)) == mpc(0,sinh(y))
|
||||
assert cospi(mpc(x,0)) == cospi(x)
|
||||
assert sinpi(mpc(x,0)) == sinpi(x)
|
||||
assert cospi(mpc(0,y)).ae(cosh(pi*y))
|
||||
assert sinpi(mpc(0,y)).ae(mpc(0,sinh(pi*y)))
|
||||
c, s = cospi_sinpi(mpc(x,0))
|
||||
assert c == cospi(x)
|
||||
assert s == sinpi(x)
|
||||
c, s = cospi_sinpi(mpc(0,y))
|
||||
assert c.ae(cosh(pi*y))
|
||||
assert s.ae(mpc(0,sinh(pi*y)))
|
||||
c, s = cos_sin(mpc(x,0))
|
||||
assert c == cos(x)
|
||||
assert s == sin(x)
|
||||
c, s = cos_sin(mpc(0,y))
|
||||
assert c == cosh(y)
|
||||
assert s == mpc(0,sinh(y))
|
||||
|
||||
def test_perturbation_rounding():
|
||||
mp.dps = 100
|
||||
a = pi/10**50
|
||||
b = -pi/10**50
|
||||
c = 1 + a
|
||||
d = 1 + b
|
||||
mp.dps = 15
|
||||
assert exp(a) == 1
|
||||
assert exp(a, rounding='c') > 1
|
||||
assert exp(b, rounding='c') == 1
|
||||
assert exp(a, rounding='f') == 1
|
||||
assert exp(b, rounding='f') < 1
|
||||
assert cos(a) == 1
|
||||
assert cos(a, rounding='c') == 1
|
||||
assert cos(b, rounding='c') == 1
|
||||
assert cos(a, rounding='f') < 1
|
||||
assert cos(b, rounding='f') < 1
|
||||
for f in [sin, atan, asinh, tanh]:
|
||||
assert f(a) == +a
|
||||
assert f(a, rounding='c') > a
|
||||
assert f(a, rounding='f') < a
|
||||
assert f(b) == +b
|
||||
assert f(b, rounding='c') > b
|
||||
assert f(b, rounding='f') < b
|
||||
for f in [asin, tan, sinh, atanh]:
|
||||
assert f(a) == +a
|
||||
assert f(b) == +b
|
||||
assert f(a, rounding='c') > a
|
||||
assert f(b, rounding='c') > b
|
||||
assert f(a, rounding='f') < a
|
||||
assert f(b, rounding='f') < b
|
||||
assert ln(c) == +a
|
||||
assert ln(d) == +b
|
||||
assert ln(c, rounding='c') > a
|
||||
assert ln(c, rounding='f') < a
|
||||
assert ln(d, rounding='c') > b
|
||||
assert ln(d, rounding='f') < b
|
||||
assert cosh(a) == 1
|
||||
assert cosh(b) == 1
|
||||
assert cosh(a, rounding='c') > 1
|
||||
assert cosh(b, rounding='c') > 1
|
||||
assert cosh(a, rounding='f') == 1
|
||||
assert cosh(b, rounding='f') == 1
|
||||
|
||||
def test_integer_parts():
|
||||
assert floor(3.2) == 3
|
||||
assert ceil(3.2) == 4
|
||||
assert floor(3.2+5j) == 3+5j
|
||||
assert ceil(3.2+5j) == 4+5j
|
||||
|
||||
def test_complex_parts():
|
||||
assert fabs('3') == 3
|
||||
assert fabs(3+4j) == 5
|
||||
assert re(3) == 3
|
||||
assert re(1+4j) == 1
|
||||
assert im(3) == 0
|
||||
assert im(1+4j) == 4
|
||||
assert conj(3) == 3
|
||||
assert conj(3+4j) == 3-4j
|
||||
assert mpf(3).conjugate() == 3
|
||||
|
||||
def test_cospi_sinpi():
|
||||
assert sinpi(0) == 0
|
||||
assert sinpi(0.5) == 1
|
||||
assert sinpi(1) == 0
|
||||
assert sinpi(1.5) == -1
|
||||
assert sinpi(2) == 0
|
||||
assert sinpi(2.5) == 1
|
||||
assert sinpi(-0.5) == -1
|
||||
assert cospi(0) == 1
|
||||
assert cospi(0.5) == 0
|
||||
assert cospi(1) == -1
|
||||
assert cospi(1.5) == 0
|
||||
assert cospi(2) == 1
|
||||
assert cospi(2.5) == 0
|
||||
assert cospi(-0.5) == 0
|
||||
assert cospi(100000000000.25).ae(sqrt(2)/2)
|
||||
a = cospi(2+3j)
|
||||
assert a.real.ae(cos((2+3j)*pi).real)
|
||||
assert a.imag == 0
|
||||
b = sinpi(2+3j)
|
||||
assert b.imag.ae(sin((2+3j)*pi).imag)
|
||||
assert b.real == 0
|
||||
mp.dps = 35
|
||||
x1 = mpf(10000) - mpf('1e-15')
|
||||
x2 = mpf(10000) + mpf('1e-15')
|
||||
x3 = mpf(10000.5) - mpf('1e-15')
|
||||
x4 = mpf(10000.5) + mpf('1e-15')
|
||||
x5 = mpf(10001) - mpf('1e-15')
|
||||
x6 = mpf(10001) + mpf('1e-15')
|
||||
x7 = mpf(10001.5) - mpf('1e-15')
|
||||
x8 = mpf(10001.5) + mpf('1e-15')
|
||||
mp.dps = 15
|
||||
M = 10**15
|
||||
assert (sinpi(x1)*M).ae(-pi)
|
||||
assert (sinpi(x2)*M).ae(pi)
|
||||
assert (cospi(x3)*M).ae(pi)
|
||||
assert (cospi(x4)*M).ae(-pi)
|
||||
assert (sinpi(x5)*M).ae(pi)
|
||||
assert (sinpi(x6)*M).ae(-pi)
|
||||
assert (cospi(x7)*M).ae(-pi)
|
||||
assert (cospi(x8)*M).ae(pi)
|
||||
assert 0.999 < cospi(x1, rounding='d') < 1
|
||||
assert 0.999 < cospi(x2, rounding='d') < 1
|
||||
assert 0.999 < sinpi(x3, rounding='d') < 1
|
||||
assert 0.999 < sinpi(x4, rounding='d') < 1
|
||||
assert -1 < cospi(x5, rounding='d') < -0.999
|
||||
assert -1 < cospi(x6, rounding='d') < -0.999
|
||||
assert -1 < sinpi(x7, rounding='d') < -0.999
|
||||
assert -1 < sinpi(x8, rounding='d') < -0.999
|
||||
assert (sinpi(1e-15)*M).ae(pi)
|
||||
assert (sinpi(-1e-15)*M).ae(-pi)
|
||||
assert cospi(1e-15) == 1
|
||||
assert cospi(1e-15, rounding='d') < 1
|
||||
|
||||
def test_expj():
|
||||
assert expj(0) == 1
|
||||
assert expj(1).ae(exp(j))
|
||||
assert expj(j).ae(exp(-1))
|
||||
assert expj(1+j).ae(exp(j*(1+j)))
|
||||
assert expjpi(0) == 1
|
||||
assert expjpi(1).ae(exp(j*pi))
|
||||
assert expjpi(j).ae(exp(-pi))
|
||||
assert expjpi(1+j).ae(exp(j*pi*(1+j)))
|
||||
assert expjpi(-10**15 * j).ae('2.22579818340535731e+1364376353841841')
|
||||
|
||||
def test_sinc():
|
||||
assert sinc(0) == sincpi(0) == 1
|
||||
assert sinc(inf) == sincpi(inf) == 0
|
||||
assert sinc(-inf) == sincpi(-inf) == 0
|
||||
assert sinc(2).ae(0.45464871341284084770)
|
||||
assert sinc(2+3j).ae(0.4463290318402435457-2.7539470277436474940j)
|
||||
assert sincpi(2) == 0
|
||||
assert sincpi(1.5).ae(-0.212206590789193781)
|
||||
|
||||
def test_fibonacci():
|
||||
mp.dps = 15
|
||||
assert [fibonacci(n) for n in range(-5, 10)] == \
|
||||
[5, -3, 2, -1, 1, 0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
|
||||
assert fib(2.5).ae(1.4893065462657091)
|
||||
assert fib(3+4j).ae(-5248.51130728372 - 14195.962288353j)
|
||||
assert fib(1000).ae(4.3466557686937455e+208)
|
||||
assert str(fib(10**100)) == '6.24499112864607e+2089876402499787337692720892375554168224592399182109535392875613974104853496745963277658556235103534'
|
||||
mp.dps = 2100
|
||||
a = fib(10000)
|
||||
assert a % 10**10 == 9947366875
|
||||
mp.dps = 15
|
||||
assert fibonacci(inf) == inf
|
||||
assert fib(3+0j) == 2
|
||||
|
||||
def test_call_with_dps():
|
||||
mp.dps = 15
|
||||
assert abs(exp(1, dps=30)-e(dps=35)) < 1e-29
|
||||
|
||||
def test_tanh():
|
||||
mp.dps = 15
|
||||
assert tanh(0) == 0
|
||||
assert tanh(inf) == 1
|
||||
assert tanh(-inf) == -1
|
||||
assert isnan(tanh(nan))
|
||||
assert tanh(mpc('inf', '0')) == 1
|
||||
|
||||
def test_atanh():
|
||||
mp.dps = 15
|
||||
assert atanh(0) == 0
|
||||
assert atanh(0.5).ae(0.54930614433405484570)
|
||||
assert atanh(-0.5).ae(-0.54930614433405484570)
|
||||
assert atanh(1) == inf
|
||||
assert atanh(-1) == -inf
|
||||
assert isnan(atanh(nan))
|
||||
assert isinstance(atanh(1), mpf)
|
||||
assert isinstance(atanh(-1), mpf)
|
||||
# Limits at infinity
|
||||
jpi2 = j*pi/2
|
||||
assert atanh(inf).ae(-jpi2)
|
||||
assert atanh(-inf).ae(jpi2)
|
||||
assert atanh(mpc(inf,-1)).ae(-jpi2)
|
||||
assert atanh(mpc(inf,0)).ae(-jpi2)
|
||||
assert atanh(mpc(inf,1)).ae(jpi2)
|
||||
assert atanh(mpc(1,inf)).ae(jpi2)
|
||||
assert atanh(mpc(0,inf)).ae(jpi2)
|
||||
assert atanh(mpc(-1,inf)).ae(jpi2)
|
||||
assert atanh(mpc(-inf,1)).ae(jpi2)
|
||||
assert atanh(mpc(-inf,0)).ae(jpi2)
|
||||
assert atanh(mpc(-inf,-1)).ae(-jpi2)
|
||||
assert atanh(mpc(-1,-inf)).ae(-jpi2)
|
||||
assert atanh(mpc(0,-inf)).ae(-jpi2)
|
||||
assert atanh(mpc(1,-inf)).ae(-jpi2)
|
||||
|
||||
def test_expm1():
|
||||
mp.dps = 15
|
||||
assert expm1(0) == 0
|
||||
assert expm1(3).ae(exp(3)-1)
|
||||
assert expm1(inf) == inf
|
||||
assert expm1(1e-50).ae(1e-50)
|
||||
assert (expm1(1e-10)*1e10).ae(1.00000000005)
|
||||
|
||||
def test_log1p():
|
||||
mp.dps = 15
|
||||
assert log1p(0) == 0
|
||||
assert log1p(3).ae(log(1+3))
|
||||
assert log1p(inf) == inf
|
||||
assert log1p(1e-50).ae(1e-50)
|
||||
assert (log1p(1e-10)*1e10).ae(0.99999999995)
|
||||
|
||||
def test_powm1():
|
||||
mp.dps = 15
|
||||
assert powm1(2,3) == 7
|
||||
assert powm1(-1,2) == 0
|
||||
assert powm1(-1,0) == 0
|
||||
assert powm1(-2,0) == 0
|
||||
assert powm1(3+4j,0) == 0
|
||||
assert powm1(0,1) == -1
|
||||
assert powm1(0,0) == 0
|
||||
assert powm1(1,0) == 0
|
||||
assert powm1(1,2) == 0
|
||||
assert powm1(1,3+4j) == 0
|
||||
assert powm1(1,5) == 0
|
||||
assert powm1(j,4) == 0
|
||||
assert powm1(-j,4) == 0
|
||||
assert (powm1(2,1e-100)*1e100).ae(ln2)
|
||||
assert powm1(2,'1e-100000000000') != 0
|
||||
assert (powm1(fadd(1,1e-100,exact=True), 5)*1e100).ae(5)
|
||||
|
||||
def test_unitroots():
|
||||
assert unitroots(1) == [1]
|
||||
assert unitroots(2) == [1, -1]
|
||||
a, b, c = unitroots(3)
|
||||
assert a == 1
|
||||
assert b.ae(-0.5 + 0.86602540378443864676j)
|
||||
assert c.ae(-0.5 - 0.86602540378443864676j)
|
||||
assert unitroots(1, primitive=True) == [1]
|
||||
assert unitroots(2, primitive=True) == [-1]
|
||||
assert unitroots(3, primitive=True) == unitroots(3)[1:]
|
||||
assert unitroots(4, primitive=True) == [j, -j]
|
||||
assert len(unitroots(17, primitive=True)) == 16
|
||||
assert len(unitroots(16, primitive=True)) == 8
|
||||
|
||||
def test_cyclotomic():
|
||||
mp.dps = 15
|
||||
assert [cyclotomic(n,1) for n in range(31)] == [1,0,2,3,2,5,1,7,2,3,1,11,1,13,1,1,2,17,1,19,1,1,1,23,1,5,1,3,1,29,1]
|
||||
assert [cyclotomic(n,-1) for n in range(31)] == [1,-2,0,1,2,1,3,1,2,1,5,1,1,1,7,1,2,1,3,1,1,1,11,1,1,1,13,1,1,1,1]
|
||||
assert [cyclotomic(n,j) for n in range(21)] == [1,-1+j,1+j,j,0,1,-j,j,2,-j,1,j,3,1,-j,1,2,1,j,j,5]
|
||||
assert [cyclotomic(n,-j) for n in range(21)] == [1,-1-j,1-j,-j,0,1,j,-j,2,j,1,-j,3,1,j,1,2,1,-j,-j,5]
|
||||
assert cyclotomic(1624,j) == 1
|
||||
assert cyclotomic(33600,j) == 1
|
||||
u = sqrt(j, prec=500)
|
||||
assert cyclotomic(8, u).ae(0)
|
||||
assert cyclotomic(30, u).ae(5.8284271247461900976)
|
||||
assert cyclotomic(2040, u).ae(1)
|
||||
assert cyclotomic(0,2.5) == 1
|
||||
assert cyclotomic(1,2.5) == 2.5-1
|
||||
assert cyclotomic(2,2.5) == 2.5+1
|
||||
assert cyclotomic(3,2.5) == 2.5**2 + 2.5 + 1
|
||||
assert cyclotomic(7,2.5) == 406.234375
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,698 @@
|
||||
from mpmath import *
|
||||
from mpmath.libmp import round_up, from_float, mpf_zeta_int
|
||||
|
||||
def test_zeta_int_bug():
|
||||
assert mpf_zeta_int(0, 10) == from_float(-0.5)
|
||||
|
||||
def test_bernoulli():
|
||||
assert bernfrac(0) == (1,1)
|
||||
assert bernfrac(1) == (-1,2)
|
||||
assert bernfrac(2) == (1,6)
|
||||
assert bernfrac(3) == (0,1)
|
||||
assert bernfrac(4) == (-1,30)
|
||||
assert bernfrac(5) == (0,1)
|
||||
assert bernfrac(6) == (1,42)
|
||||
assert bernfrac(8) == (-1,30)
|
||||
assert bernfrac(10) == (5,66)
|
||||
assert bernfrac(12) == (-691,2730)
|
||||
assert bernfrac(18) == (43867,798)
|
||||
p, q = bernfrac(228)
|
||||
assert p % 10**10 == 164918161
|
||||
assert q == 625170
|
||||
p, q = bernfrac(1000)
|
||||
assert p % 10**10 == 7950421099
|
||||
assert q == 342999030
|
||||
mp.dps = 15
|
||||
assert bernoulli(0) == 1
|
||||
assert bernoulli(1) == -0.5
|
||||
assert bernoulli(2).ae(1./6)
|
||||
assert bernoulli(3) == 0
|
||||
assert bernoulli(4).ae(-1./30)
|
||||
assert bernoulli(5) == 0
|
||||
assert bernoulli(6).ae(1./42)
|
||||
assert str(bernoulli(10)) == '0.0757575757575758'
|
||||
assert str(bernoulli(234)) == '7.62772793964344e+267'
|
||||
assert str(bernoulli(10**5)) == '-5.82229431461335e+376755'
|
||||
assert str(bernoulli(10**8+2)) == '1.19570355039953e+676752584'
|
||||
|
||||
mp.dps = 50
|
||||
assert str(bernoulli(10)) == '0.075757575757575757575757575757575757575757575757576'
|
||||
assert str(bernoulli(234)) == '7.6277279396434392486994969020496121553385863373331e+267'
|
||||
assert str(bernoulli(10**5)) == '-5.8222943146133508236497045360612887555320691004308e+376755'
|
||||
assert str(bernoulli(10**8+2)) == '1.1957035503995297272263047884604346914602088317782e+676752584'
|
||||
|
||||
mp.dps = 1000
|
||||
assert bernoulli(10).ae(mpf(5)/66)
|
||||
|
||||
mp.dps = 50000
|
||||
assert bernoulli(10).ae(mpf(5)/66)
|
||||
|
||||
mp.dps = 15
|
||||
|
||||
def test_bernpoly_eulerpoly():
|
||||
mp.dps = 15
|
||||
assert bernpoly(0,-1).ae(1)
|
||||
assert bernpoly(0,0).ae(1)
|
||||
assert bernpoly(0,'1/2').ae(1)
|
||||
assert bernpoly(0,'3/4').ae(1)
|
||||
assert bernpoly(0,1).ae(1)
|
||||
assert bernpoly(0,2).ae(1)
|
||||
assert bernpoly(1,-1).ae('-3/2')
|
||||
assert bernpoly(1,0).ae('-1/2')
|
||||
assert bernpoly(1,'1/2').ae(0)
|
||||
assert bernpoly(1,'3/4').ae('1/4')
|
||||
assert bernpoly(1,1).ae('1/2')
|
||||
assert bernpoly(1,2).ae('3/2')
|
||||
assert bernpoly(2,-1).ae('13/6')
|
||||
assert bernpoly(2,0).ae('1/6')
|
||||
assert bernpoly(2,'1/2').ae('-1/12')
|
||||
assert bernpoly(2,'3/4').ae('-1/48')
|
||||
assert bernpoly(2,1).ae('1/6')
|
||||
assert bernpoly(2,2).ae('13/6')
|
||||
assert bernpoly(3,-1).ae(-3)
|
||||
assert bernpoly(3,0).ae(0)
|
||||
assert bernpoly(3,'1/2').ae(0)
|
||||
assert bernpoly(3,'3/4').ae('-3/64')
|
||||
assert bernpoly(3,1).ae(0)
|
||||
assert bernpoly(3,2).ae(3)
|
||||
assert bernpoly(4,-1).ae('119/30')
|
||||
assert bernpoly(4,0).ae('-1/30')
|
||||
assert bernpoly(4,'1/2').ae('7/240')
|
||||
assert bernpoly(4,'3/4').ae('7/3840')
|
||||
assert bernpoly(4,1).ae('-1/30')
|
||||
assert bernpoly(4,2).ae('119/30')
|
||||
assert bernpoly(5,-1).ae(-5)
|
||||
assert bernpoly(5,0).ae(0)
|
||||
assert bernpoly(5,'1/2').ae(0)
|
||||
assert bernpoly(5,'3/4').ae('25/1024')
|
||||
assert bernpoly(5,1).ae(0)
|
||||
assert bernpoly(5,2).ae(5)
|
||||
assert bernpoly(10,-1).ae('665/66')
|
||||
assert bernpoly(10,0).ae('5/66')
|
||||
assert bernpoly(10,'1/2').ae('-2555/33792')
|
||||
assert bernpoly(10,'3/4').ae('-2555/34603008')
|
||||
assert bernpoly(10,1).ae('5/66')
|
||||
assert bernpoly(10,2).ae('665/66')
|
||||
assert bernpoly(11,-1).ae(-11)
|
||||
assert bernpoly(11,0).ae(0)
|
||||
assert bernpoly(11,'1/2').ae(0)
|
||||
assert bernpoly(11,'3/4').ae('-555731/4194304')
|
||||
assert bernpoly(11,1).ae(0)
|
||||
assert bernpoly(11,2).ae(11)
|
||||
assert eulerpoly(0,-1).ae(1)
|
||||
assert eulerpoly(0,0).ae(1)
|
||||
assert eulerpoly(0,'1/2').ae(1)
|
||||
assert eulerpoly(0,'3/4').ae(1)
|
||||
assert eulerpoly(0,1).ae(1)
|
||||
assert eulerpoly(0,2).ae(1)
|
||||
assert eulerpoly(1,-1).ae('-3/2')
|
||||
assert eulerpoly(1,0).ae('-1/2')
|
||||
assert eulerpoly(1,'1/2').ae(0)
|
||||
assert eulerpoly(1,'3/4').ae('1/4')
|
||||
assert eulerpoly(1,1).ae('1/2')
|
||||
assert eulerpoly(1,2).ae('3/2')
|
||||
assert eulerpoly(2,-1).ae(2)
|
||||
assert eulerpoly(2,0).ae(0)
|
||||
assert eulerpoly(2,'1/2').ae('-1/4')
|
||||
assert eulerpoly(2,'3/4').ae('-3/16')
|
||||
assert eulerpoly(2,1).ae(0)
|
||||
assert eulerpoly(2,2).ae(2)
|
||||
assert eulerpoly(3,-1).ae('-9/4')
|
||||
assert eulerpoly(3,0).ae('1/4')
|
||||
assert eulerpoly(3,'1/2').ae(0)
|
||||
assert eulerpoly(3,'3/4').ae('-11/64')
|
||||
assert eulerpoly(3,1).ae('-1/4')
|
||||
assert eulerpoly(3,2).ae('9/4')
|
||||
assert eulerpoly(4,-1).ae(2)
|
||||
assert eulerpoly(4,0).ae(0)
|
||||
assert eulerpoly(4,'1/2').ae('5/16')
|
||||
assert eulerpoly(4,'3/4').ae('57/256')
|
||||
assert eulerpoly(4,1).ae(0)
|
||||
assert eulerpoly(4,2).ae(2)
|
||||
assert eulerpoly(5,-1).ae('-3/2')
|
||||
assert eulerpoly(5,0).ae('-1/2')
|
||||
assert eulerpoly(5,'1/2').ae(0)
|
||||
assert eulerpoly(5,'3/4').ae('361/1024')
|
||||
assert eulerpoly(5,1).ae('1/2')
|
||||
assert eulerpoly(5,2).ae('3/2')
|
||||
assert eulerpoly(10,-1).ae(2)
|
||||
assert eulerpoly(10,0).ae(0)
|
||||
assert eulerpoly(10,'1/2').ae('-50521/1024')
|
||||
assert eulerpoly(10,'3/4').ae('-36581523/1048576')
|
||||
assert eulerpoly(10,1).ae(0)
|
||||
assert eulerpoly(10,2).ae(2)
|
||||
assert eulerpoly(11,-1).ae('-699/4')
|
||||
assert eulerpoly(11,0).ae('691/4')
|
||||
assert eulerpoly(11,'1/2').ae(0)
|
||||
assert eulerpoly(11,'3/4').ae('-512343611/4194304')
|
||||
assert eulerpoly(11,1).ae('-691/4')
|
||||
assert eulerpoly(11,2).ae('699/4')
|
||||
# Potential accuracy issues
|
||||
assert bernpoly(10000,10000).ae('5.8196915936323387117e+39999')
|
||||
assert bernpoly(200,17.5).ae(3.8048418524583064909e244)
|
||||
assert eulerpoly(200,17.5).ae(-3.7309911582655785929e275)
|
||||
|
||||
def test_gamma():
|
||||
mp.dps = 15
|
||||
assert gamma(0.25).ae(3.6256099082219083119)
|
||||
assert gamma(0.0001).ae(9999.4228832316241908)
|
||||
assert gamma(300).ae('1.0201917073881354535e612')
|
||||
assert gamma(-0.5).ae(-3.5449077018110320546)
|
||||
assert gamma(-7.43).ae(0.00026524416464197007186)
|
||||
#assert gamma(Rational(1,2)) == gamma(0.5)
|
||||
#assert gamma(Rational(-7,3)).ae(gamma(mpf(-7)/3))
|
||||
assert gamma(1+1j).ae(0.49801566811835604271 - 0.15494982830181068512j)
|
||||
assert gamma(-1+0.01j).ae(-0.422733904013474115 + 99.985883082635367436j)
|
||||
assert gamma(20+30j).ae(-1453876687.5534810 + 1163777777.8031573j)
|
||||
# Should always give exact factorials when they can
|
||||
# be represented as mpfs under the current working precision
|
||||
fact = 1
|
||||
for i in range(1, 18):
|
||||
assert gamma(i) == fact
|
||||
fact *= i
|
||||
for dps in [170, 600]:
|
||||
fact = 1
|
||||
mp.dps = dps
|
||||
for i in range(1, 105):
|
||||
assert gamma(i) == fact
|
||||
fact *= i
|
||||
mp.dps = 100
|
||||
assert gamma(0.5).ae(sqrt(pi))
|
||||
mp.dps = 15
|
||||
assert factorial(0) == fac(0) == 1
|
||||
assert factorial(3) == 6
|
||||
assert isnan(gamma(nan))
|
||||
assert gamma(1100).ae('4.8579168073569433667e2866')
|
||||
assert rgamma(0) == 0
|
||||
assert rgamma(-1) == 0
|
||||
assert rgamma(2) == 1.0
|
||||
assert rgamma(3) == 0.5
|
||||
assert loggamma(2+8j).ae(-8.5205176753667636926 + 10.8569497125597429366j)
|
||||
assert loggamma('1e10000').ae('2.302485092994045684017991e10004')
|
||||
assert loggamma('1e10000j').ae(mpc('-1.570796326794896619231322e10000','2.302485092994045684017991e10004'))
|
||||
|
||||
def test_fac2():
|
||||
mp.dps = 15
|
||||
assert [fac2(n) for n in range(10)] == [1,1,2,3,8,15,48,105,384,945]
|
||||
assert fac2(-5).ae(1./3)
|
||||
assert fac2(-11).ae(-1./945)
|
||||
assert fac2(50).ae(5.20469842636666623e32)
|
||||
assert fac2(0.5+0.75j).ae(0.81546769394688069176-0.34901016085573266889j)
|
||||
assert fac2(inf) == inf
|
||||
assert isnan(fac2(-inf))
|
||||
|
||||
def test_gamma_quotients():
|
||||
mp.dps = 15
|
||||
h = 1e-8
|
||||
ep = 1e-4
|
||||
G = gamma
|
||||
assert gammaprod([-1],[-3,-4]) == 0
|
||||
assert gammaprod([-1,0],[-5]) == inf
|
||||
assert abs(gammaprod([-1],[-2]) - G(-1+h)/G(-2+h)) < 1e-4
|
||||
assert abs(gammaprod([-4,-3],[-2,0]) - G(-4+h)*G(-3+h)/G(-2+h)/G(0+h)) < 1e-4
|
||||
assert rf(3,0) == 1
|
||||
assert rf(2.5,1) == 2.5
|
||||
assert rf(-5,2) == 20
|
||||
assert rf(j,j).ae(gamma(2*j)/gamma(j))
|
||||
assert rf('-255.5815971722918','-0.5119253100282322').ae('-0.1952720278805729485') # issue 421
|
||||
assert ff(-2,0) == 1
|
||||
assert ff(-2,1) == -2
|
||||
assert ff(4,3) == 24
|
||||
assert ff(3,4) == 0
|
||||
assert binomial(0,0) == 1
|
||||
assert binomial(1,0) == 1
|
||||
assert binomial(0,-1) == 0
|
||||
assert binomial(3,2) == 3
|
||||
assert binomial(5,2) == 10
|
||||
assert binomial(5,3) == 10
|
||||
assert binomial(5,5) == 1
|
||||
assert binomial(-1,0) == 1
|
||||
assert binomial(-2,-4) == 3
|
||||
assert binomial(4.5, 1.5) == 6.5625
|
||||
assert binomial(1100,1) == 1100
|
||||
assert binomial(1100,2) == 604450
|
||||
assert beta(1,1) == 1
|
||||
assert beta(0,0) == inf
|
||||
assert beta(3,0) == inf
|
||||
assert beta(-1,-1) == inf
|
||||
assert beta(1.5,1).ae(2/3.)
|
||||
assert beta(1.5,2.5).ae(pi/16)
|
||||
assert (10**15*beta(10,100)).ae(2.3455339739604649879)
|
||||
assert beta(inf,inf) == 0
|
||||
assert isnan(beta(-inf,inf))
|
||||
assert isnan(beta(-3,inf))
|
||||
assert isnan(beta(0,inf))
|
||||
assert beta(inf,0.5) == beta(0.5,inf) == 0
|
||||
assert beta(inf,-1.5) == inf
|
||||
assert beta(inf,-0.5) == -inf
|
||||
assert beta(1+2j,-1-j/2).ae(1.16396542451069943086+0.08511695947832914640j)
|
||||
assert beta(-0.5,0.5) == 0
|
||||
assert beta(-3,3).ae(-1/3.)
|
||||
assert beta('-255.5815971722918','-0.5119253100282322').ae('18.157330562703710339') # issue 421
|
||||
|
||||
def test_zeta():
|
||||
mp.dps = 15
|
||||
assert zeta(2).ae(pi**2 / 6)
|
||||
assert zeta(2.0).ae(pi**2 / 6)
|
||||
assert zeta(mpc(2)).ae(pi**2 / 6)
|
||||
assert zeta(100).ae(1)
|
||||
assert zeta(0).ae(-0.5)
|
||||
assert zeta(0.5).ae(-1.46035450880958681)
|
||||
assert zeta(-1).ae(-mpf(1)/12)
|
||||
assert zeta(-2) == 0
|
||||
assert zeta(-3).ae(mpf(1)/120)
|
||||
assert zeta(-4) == 0
|
||||
assert zeta(-100) == 0
|
||||
assert isnan(zeta(nan))
|
||||
assert zeta(1e-30).ae(-0.5)
|
||||
assert zeta(-1e-30).ae(-0.5)
|
||||
# Zeros in the critical strip
|
||||
assert zeta(mpc(0.5, 14.1347251417346937904)).ae(0)
|
||||
assert zeta(mpc(0.5, 21.0220396387715549926)).ae(0)
|
||||
assert zeta(mpc(0.5, 25.0108575801456887632)).ae(0)
|
||||
assert zeta(mpc(1e-30,1e-40)).ae(-0.5)
|
||||
assert zeta(mpc(-1e-30,1e-40)).ae(-0.5)
|
||||
mp.dps = 50
|
||||
im = '236.5242296658162058024755079556629786895294952121891237'
|
||||
assert zeta(mpc(0.5, im)).ae(0, 1e-46)
|
||||
mp.dps = 15
|
||||
# Complex reflection formula
|
||||
assert (zeta(-60+3j) / 10**34).ae(8.6270183987866146+15.337398548226238j)
|
||||
# issue #358
|
||||
assert zeta(0,0.5) == 0
|
||||
assert zeta(0,0) == 0.5
|
||||
assert zeta(0,0.5,1).ae(-0.34657359027997265)
|
||||
# see issue #390
|
||||
assert zeta(-1.5,0.5j).ae(-0.13671400162512768475 + 0.11411333638426559139j)
|
||||
|
||||
def test_altzeta():
|
||||
mp.dps = 15
|
||||
assert altzeta(-2) == 0
|
||||
assert altzeta(-4) == 0
|
||||
assert altzeta(-100) == 0
|
||||
assert altzeta(0) == 0.5
|
||||
assert altzeta(-1) == 0.25
|
||||
assert altzeta(-3) == -0.125
|
||||
assert altzeta(-5) == 0.25
|
||||
assert altzeta(-21) == 1180529130.25
|
||||
assert altzeta(1).ae(log(2))
|
||||
assert altzeta(2).ae(pi**2/12)
|
||||
assert altzeta(10).ae(73*pi**10/6842880)
|
||||
assert altzeta(50) < 1
|
||||
assert altzeta(60, rounding='d') < 1
|
||||
assert altzeta(60, rounding='u') == 1
|
||||
assert altzeta(10000, rounding='d') < 1
|
||||
assert altzeta(10000, rounding='u') == 1
|
||||
assert altzeta(3+0j) == altzeta(3)
|
||||
s = 3+4j
|
||||
assert altzeta(s).ae((1-2**(1-s))*zeta(s))
|
||||
s = -3+4j
|
||||
assert altzeta(s).ae((1-2**(1-s))*zeta(s))
|
||||
assert altzeta(-100.5).ae(4.58595480083585913e+108)
|
||||
assert altzeta(1.3).ae(0.73821404216623045)
|
||||
assert altzeta(1e-30).ae(0.5)
|
||||
assert altzeta(-1e-30).ae(0.5)
|
||||
assert altzeta(mpc(1e-30,1e-40)).ae(0.5)
|
||||
assert altzeta(mpc(-1e-30,1e-40)).ae(0.5)
|
||||
|
||||
def test_zeta_huge():
|
||||
mp.dps = 15
|
||||
assert zeta(inf) == 1
|
||||
mp.dps = 50
|
||||
assert zeta(100).ae('1.0000000000000000000000000000007888609052210118073522')
|
||||
assert zeta(40*pi).ae('1.0000000000000000000000000000000000000148407238666182')
|
||||
mp.dps = 10000
|
||||
v = zeta(33000)
|
||||
mp.dps = 15
|
||||
assert str(v-1) == '1.02363019598118e-9934'
|
||||
assert zeta(pi*1000, rounding=round_up) > 1
|
||||
assert zeta(3000, rounding=round_up) > 1
|
||||
assert zeta(pi*1000) == 1
|
||||
assert zeta(3000) == 1
|
||||
|
||||
def test_zeta_negative():
|
||||
mp.dps = 150
|
||||
a = -pi*10**40
|
||||
mp.dps = 15
|
||||
assert str(zeta(a)) == '2.55880492708712e+1233536161668617575553892558646631323374078'
|
||||
mp.dps = 50
|
||||
assert str(zeta(a)) == '2.5588049270871154960875033337384432038436330847333e+1233536161668617575553892558646631323374078'
|
||||
mp.dps = 15
|
||||
|
||||
def test_polygamma():
|
||||
mp.dps = 15
|
||||
psi0 = lambda z: psi(0,z)
|
||||
psi1 = lambda z: psi(1,z)
|
||||
assert psi0(3) == psi(0,3) == digamma(3)
|
||||
#assert psi2(3) == psi(2,3) == tetragamma(3)
|
||||
#assert psi3(3) == psi(3,3) == pentagamma(3)
|
||||
assert psi0(pi).ae(0.97721330794200673)
|
||||
assert psi0(-pi).ae(7.8859523853854902)
|
||||
assert psi0(-pi+1).ae(7.5676424992016996)
|
||||
assert psi0(pi+j).ae(1.04224048313859376 + 0.35853686544063749j)
|
||||
assert psi0(-pi-j).ae(1.3404026194821986 - 2.8824392476809402j)
|
||||
assert findroot(psi0, 1).ae(1.4616321449683622)
|
||||
assert psi0(1e-10).ae(-10000000000.57722)
|
||||
assert psi0(1e-40).ae(-1.000000000000000e+40)
|
||||
assert psi0(1e-10+1e-10j).ae(-5000000000.577215 + 5000000000.000000j)
|
||||
assert psi0(1e-40+1e-40j).ae(-5.000000000000000e+39 + 5.000000000000000e+39j)
|
||||
assert psi0(inf) == inf
|
||||
assert psi1(inf) == 0
|
||||
assert psi(2,inf) == 0
|
||||
assert psi1(pi).ae(0.37424376965420049)
|
||||
assert psi1(-pi).ae(53.030438740085385)
|
||||
assert psi1(pi+j).ae(0.32935710377142464 - 0.12222163911221135j)
|
||||
assert psi1(-pi-j).ae(-0.30065008356019703 + 0.01149892486928227j)
|
||||
assert (10**6*psi(4,1+10*pi*j)).ae(-6.1491803479004446 - 0.3921316371664063j)
|
||||
assert psi0(1+10*pi*j).ae(3.4473994217222650 + 1.5548808324857071j)
|
||||
assert isnan(psi0(nan))
|
||||
assert isnan(psi0(-inf))
|
||||
assert psi0(-100.5).ae(4.615124601338064)
|
||||
assert psi0(3+0j).ae(psi0(3))
|
||||
assert psi0(-100+3j).ae(4.6106071768714086321+3.1117510556817394626j)
|
||||
assert isnan(psi(2,mpc(0,inf)))
|
||||
assert isnan(psi(2,mpc(0,nan)))
|
||||
assert isnan(psi(2,mpc(0,-inf)))
|
||||
assert isnan(psi(2,mpc(1,inf)))
|
||||
assert isnan(psi(2,mpc(1,nan)))
|
||||
assert isnan(psi(2,mpc(1,-inf)))
|
||||
assert isnan(psi(2,mpc(inf,inf)))
|
||||
assert isnan(psi(2,mpc(nan,nan)))
|
||||
assert isnan(psi(2,mpc(-inf,-inf)))
|
||||
mp.dps = 30
|
||||
# issue #534
|
||||
assert digamma(-0.75+1j).ae(mpc('0.46317279488182026118963809283042317', '2.4821070143037957102007677817351115'))
|
||||
mp.dps = 15
|
||||
|
||||
def test_polygamma_high_prec():
|
||||
mp.dps = 100
|
||||
assert str(psi(0,pi)) == "0.9772133079420067332920694864061823436408346099943256380095232865318105924777141317302075654362928734"
|
||||
assert str(psi(10,pi)) == "-12.98876181434889529310283769414222588307175962213707170773803550518307617769657562747174101900659238"
|
||||
|
||||
def test_polygamma_identities():
|
||||
mp.dps = 15
|
||||
psi0 = lambda z: psi(0,z)
|
||||
psi1 = lambda z: psi(1,z)
|
||||
psi2 = lambda z: psi(2,z)
|
||||
assert psi0(0.5).ae(-euler-2*log(2))
|
||||
assert psi0(1).ae(-euler)
|
||||
assert psi1(0.5).ae(0.5*pi**2)
|
||||
assert psi1(1).ae(pi**2/6)
|
||||
assert psi1(0.25).ae(pi**2 + 8*catalan)
|
||||
assert psi2(1).ae(-2*apery)
|
||||
mp.dps = 20
|
||||
u = -182*apery+4*sqrt(3)*pi**3
|
||||
mp.dps = 15
|
||||
assert psi(2,5/6.).ae(u)
|
||||
assert psi(3,0.5).ae(pi**4)
|
||||
|
||||
def test_foxtrot_identity():
|
||||
# A test of the complex digamma function.
|
||||
# See http://mathworld.wolfram.com/FoxTrotSeries.html and
|
||||
# http://mathworld.wolfram.com/DigammaFunction.html
|
||||
psi0 = lambda z: psi(0,z)
|
||||
mp.dps = 50
|
||||
a = (-1)**fraction(1,3)
|
||||
b = (-1)**fraction(2,3)
|
||||
x = -psi0(0.5*a) - psi0(-0.5*b) + psi0(0.5*(1+a)) + psi0(0.5*(1-b))
|
||||
y = 2*pi*sech(0.5*sqrt(3)*pi)
|
||||
assert x.ae(y)
|
||||
mp.dps = 15
|
||||
|
||||
def test_polygamma_high_order():
|
||||
mp.dps = 100
|
||||
assert str(psi(50, pi)) == "-1344100348958402765749252447726432491812.641985273160531055707095989227897753035823152397679626136483"
|
||||
assert str(psi(50, pi + 14*e)) == "-0.00000000000000000189793739550804321623512073101895801993019919886375952881053090844591920308111549337295143780341396"
|
||||
assert str(psi(50, pi + 14*e*j)) == ("(-0.0000000000000000522516941152169248975225472155683565752375889510631513244785"
|
||||
"9377385233700094871256507814151956624433 - 0.00000000000000001813157041407010184"
|
||||
"702414110218205348527862196327980417757665282244728963891298080199341480881811613j)")
|
||||
mp.dps = 15
|
||||
assert str(psi(50, pi)) == "-1.34410034895841e+39"
|
||||
assert str(psi(50, pi + 14*e)) == "-1.89793739550804e-18"
|
||||
assert str(psi(50, pi + 14*e*j)) == "(-5.2251694115217e-17 - 1.81315704140701e-17j)"
|
||||
|
||||
def test_harmonic():
|
||||
mp.dps = 15
|
||||
assert harmonic(0) == 0
|
||||
assert harmonic(1) == 1
|
||||
assert harmonic(2) == 1.5
|
||||
assert harmonic(3).ae(1. + 1./2 + 1./3)
|
||||
assert harmonic(10**10).ae(23.603066594891989701)
|
||||
assert harmonic(10**1000).ae(2303.162308658947)
|
||||
assert harmonic(0.5).ae(2-2*log(2))
|
||||
assert harmonic(inf) == inf
|
||||
assert harmonic(2+0j) == 1.5+0j
|
||||
assert harmonic(1+2j).ae(1.4918071802755104+0.92080728264223022j)
|
||||
|
||||
def test_gamma_huge_1():
|
||||
mp.dps = 500
|
||||
x = mpf(10**10) / 7
|
||||
mp.dps = 15
|
||||
assert str(gamma(x)) == "6.26075321389519e+12458010678"
|
||||
mp.dps = 50
|
||||
assert str(gamma(x)) == "6.2607532138951929201303779291707455874010420783933e+12458010678"
|
||||
mp.dps = 15
|
||||
|
||||
def test_gamma_huge_2():
|
||||
mp.dps = 500
|
||||
x = mpf(10**100) / 19
|
||||
mp.dps = 15
|
||||
assert str(gamma(x)) == (\
|
||||
"1.82341134776679e+5172997469323364168990133558175077136829182824042201886051511"
|
||||
"9656908623426021308685461258226190190661")
|
||||
mp.dps = 50
|
||||
assert str(gamma(x)) == (\
|
||||
"1.82341134776678875374414910350027596939980412984e+5172997469323364168990133558"
|
||||
"1750771368291828240422018860515119656908623426021308685461258226190190661")
|
||||
|
||||
def test_gamma_huge_3():
|
||||
mp.dps = 500
|
||||
x = 10**80 // 3 + 10**70*j / 7
|
||||
mp.dps = 15
|
||||
y = gamma(x)
|
||||
assert str(y.real) == (\
|
||||
"-6.82925203918106e+2636286142112569524501781477865238132302397236429627932441916"
|
||||
"056964386399485392600")
|
||||
assert str(y.imag) == (\
|
||||
"8.54647143678418e+26362861421125695245017814778652381323023972364296279324419160"
|
||||
"56964386399485392600")
|
||||
mp.dps = 50
|
||||
y = gamma(x)
|
||||
assert str(y.real) == (\
|
||||
"-6.8292520391810548460682736226799637356016538421817e+26362861421125695245017814"
|
||||
"77865238132302397236429627932441916056964386399485392600")
|
||||
assert str(y.imag) == (\
|
||||
"8.5464714367841748507479306948130687511711420234015e+263628614211256952450178147"
|
||||
"7865238132302397236429627932441916056964386399485392600")
|
||||
|
||||
def test_gamma_huge_4():
|
||||
x = 3200+11500j
|
||||
mp.dps = 15
|
||||
assert str(gamma(x)) == \
|
||||
"(8.95783268539713e+5164 - 1.94678798329735e+5164j)"
|
||||
mp.dps = 50
|
||||
assert str(gamma(x)) == (\
|
||||
"(8.9578326853971339570292952697675570822206567327092e+5164"
|
||||
" - 1.9467879832973509568895402139429643650329524144794e+51"
|
||||
"64j)")
|
||||
mp.dps = 15
|
||||
|
||||
def test_gamma_huge_5():
|
||||
mp.dps = 500
|
||||
x = 10**60 * j / 3
|
||||
mp.dps = 15
|
||||
y = gamma(x)
|
||||
assert str(y.real) == "-3.27753899634941e-227396058973640224580963937571892628368354580620654233316839"
|
||||
assert str(y.imag) == "-7.1519888950416e-227396058973640224580963937571892628368354580620654233316841"
|
||||
mp.dps = 50
|
||||
y = gamma(x)
|
||||
assert str(y.real) == (\
|
||||
"-3.2775389963494132168950056995974690946983219123935e-22739605897364022458096393"
|
||||
"7571892628368354580620654233316839")
|
||||
assert str(y.imag) == (\
|
||||
"-7.1519888950415979749736749222530209713136588885897e-22739605897364022458096393"
|
||||
"7571892628368354580620654233316841")
|
||||
mp.dps = 15
|
||||
|
||||
def test_gamma_huge_7():
|
||||
mp.dps = 100
|
||||
a = 3 + j/mpf(10)**1000
|
||||
mp.dps = 15
|
||||
y = gamma(a)
|
||||
assert str(y.real) == "2.0"
|
||||
# wrong
|
||||
#assert str(y.imag) == "2.16735365342606e-1000"
|
||||
assert str(y.imag) == "1.84556867019693e-1000"
|
||||
mp.dps = 50
|
||||
y = gamma(a)
|
||||
assert str(y.real) == "2.0"
|
||||
#assert str(y.imag) == "2.1673536534260596065418805612488708028522563689298e-1000"
|
||||
assert str(y.imag) == "1.8455686701969342787869758198351951379156813281202e-1000"
|
||||
|
||||
def test_stieltjes():
|
||||
mp.dps = 15
|
||||
assert stieltjes(0).ae(+euler)
|
||||
mp.dps = 25
|
||||
assert stieltjes(1).ae('-0.07281584548367672486058637587')
|
||||
assert stieltjes(2).ae('-0.009690363192872318484530386035')
|
||||
assert stieltjes(3).ae('0.002053834420303345866160046543')
|
||||
assert stieltjes(4).ae('0.002325370065467300057468170178')
|
||||
mp.dps = 15
|
||||
assert stieltjes(1).ae(-0.07281584548367672486058637587)
|
||||
assert stieltjes(2).ae(-0.009690363192872318484530386035)
|
||||
assert stieltjes(3).ae(0.002053834420303345866160046543)
|
||||
assert stieltjes(4).ae(0.0023253700654673000574681701775)
|
||||
|
||||
def test_barnesg():
|
||||
mp.dps = 15
|
||||
assert barnesg(0) == barnesg(-1) == 0
|
||||
assert [superfac(i) for i in range(8)] == [1, 1, 2, 12, 288, 34560, 24883200, 125411328000]
|
||||
assert str(superfac(1000)) == '3.24570818422368e+1177245'
|
||||
assert isnan(barnesg(nan))
|
||||
assert isnan(superfac(nan))
|
||||
assert isnan(hyperfac(nan))
|
||||
assert barnesg(inf) == inf
|
||||
assert superfac(inf) == inf
|
||||
assert hyperfac(inf) == inf
|
||||
assert isnan(superfac(-inf))
|
||||
assert barnesg(0.7).ae(0.8068722730141471)
|
||||
assert barnesg(2+3j).ae(-0.17810213864082169+0.04504542715447838j)
|
||||
assert [hyperfac(n) for n in range(7)] == [1, 1, 4, 108, 27648, 86400000, 4031078400000]
|
||||
assert [hyperfac(n) for n in range(0,-7,-1)] == [1,1,-1,-4,108,27648,-86400000]
|
||||
a = barnesg(-3+0j)
|
||||
assert a == 0 and isinstance(a, mpc)
|
||||
a = hyperfac(-3+0j)
|
||||
assert a == -4 and isinstance(a, mpc)
|
||||
|
||||
def test_polylog():
|
||||
mp.dps = 15
|
||||
zs = [mpmathify(z) for z in [0, 0.5, 0.99, 4, -0.5, -4, 1j, 3+4j]]
|
||||
for z in zs: assert polylog(1, z).ae(-log(1-z))
|
||||
for z in zs: assert polylog(0, z).ae(z/(1-z))
|
||||
for z in zs: assert polylog(-1, z).ae(z/(1-z)**2)
|
||||
for z in zs: assert polylog(-2, z).ae(z*(1+z)/(1-z)**3)
|
||||
for z in zs: assert polylog(-3, z).ae(z*(1+4*z+z**2)/(1-z)**4)
|
||||
assert polylog(3, 7).ae(5.3192579921456754382-5.9479244480803301023j)
|
||||
assert polylog(3, -7).ae(-4.5693548977219423182)
|
||||
assert polylog(2, 0.9).ae(1.2997147230049587252)
|
||||
assert polylog(2, -0.9).ae(-0.75216317921726162037)
|
||||
assert polylog(2, 0.9j).ae(-0.17177943786580149299+0.83598828572550503226j)
|
||||
assert polylog(2, 1.1).ae(1.9619991013055685931-0.2994257606855892575j)
|
||||
assert polylog(2, -1.1).ae(-0.89083809026228260587)
|
||||
assert polylog(2, 1.1*sqrt(j)).ae(0.58841571107611387722+1.09962542118827026011j)
|
||||
assert polylog(-2, 0.9).ae(1710)
|
||||
assert polylog(-2, -0.9).ae(-90/6859.)
|
||||
assert polylog(3, 0.9).ae(1.0496589501864398696)
|
||||
assert polylog(-3, 0.9).ae(48690)
|
||||
assert polylog(-3, -4).ae(-0.0064)
|
||||
assert polylog(0.5+j/3, 0.5+j/2).ae(0.31739144796565650535 + 0.99255390416556261437j)
|
||||
assert polylog(3+4j,1).ae(zeta(3+4j))
|
||||
assert polylog(3+4j,-1).ae(-altzeta(3+4j))
|
||||
# issue 390
|
||||
assert polylog(1.5, -48.910886523731889).ae(-6.272992229311817)
|
||||
assert polylog(1.5, 200).ae(-8.349608319033686529 - 8.159694826434266042j)
|
||||
assert polylog(-2+0j, -2).ae(mpf(1)/13.5)
|
||||
assert polylog(-2+0j, 1.25).ae(-180)
|
||||
|
||||
def test_bell_polyexp():
|
||||
mp.dps = 15
|
||||
# TODO: more tests for polyexp
|
||||
assert (polyexp(0,1e-10)*10**10).ae(1.00000000005)
|
||||
assert (polyexp(1,1e-10)*10**10).ae(1.0000000001)
|
||||
assert polyexp(5,3j).ae(-607.7044517476176454+519.962786482001476087j)
|
||||
assert polyexp(-1,3.5).ae(12.09537536175543444)
|
||||
# bell(0,x) = 1
|
||||
assert bell(0,0) == 1
|
||||
assert bell(0,1) == 1
|
||||
assert bell(0,2) == 1
|
||||
assert bell(0,inf) == 1
|
||||
assert bell(0,-inf) == 1
|
||||
assert isnan(bell(0,nan))
|
||||
# bell(1,x) = x
|
||||
assert bell(1,4) == 4
|
||||
assert bell(1,0) == 0
|
||||
assert bell(1,inf) == inf
|
||||
assert bell(1,-inf) == -inf
|
||||
assert isnan(bell(1,nan))
|
||||
# bell(2,x) = x*(1+x)
|
||||
assert bell(2,-1) == 0
|
||||
assert bell(2,0) == 0
|
||||
# large orders / arguments
|
||||
assert bell(10) == 115975
|
||||
assert bell(10,1) == 115975
|
||||
assert bell(10, -8) == 11054008
|
||||
assert bell(5,-50) == -253087550
|
||||
assert bell(50,-50).ae('3.4746902914629720259e74')
|
||||
mp.dps = 80
|
||||
assert bell(50,-50) == 347469029146297202586097646631767227177164818163463279814268368579055777450
|
||||
assert bell(40,50) == 5575520134721105844739265207408344706846955281965031698187656176321717550
|
||||
assert bell(74) == 5006908024247925379707076470957722220463116781409659160159536981161298714301202
|
||||
mp.dps = 15
|
||||
assert bell(10,20j) == 7504528595600+15649605360020j
|
||||
# continuity of the generalization
|
||||
assert bell(0.5,0).ae(sinc(pi*0.5))
|
||||
|
||||
def test_primezeta():
|
||||
mp.dps = 15
|
||||
assert primezeta(0.9).ae(1.8388316154446882243 + 3.1415926535897932385j)
|
||||
assert primezeta(4).ae(0.076993139764246844943)
|
||||
assert primezeta(1) == inf
|
||||
assert primezeta(inf) == 0
|
||||
assert isnan(primezeta(nan))
|
||||
|
||||
def test_rs_zeta():
|
||||
mp.dps = 15
|
||||
assert zeta(0.5+100000j).ae(1.0730320148577531321 + 5.7808485443635039843j)
|
||||
assert zeta(0.75+100000j).ae(1.837852337251873704 + 1.9988492668661145358j)
|
||||
assert zeta(0.5+1000000j, derivative=3).ae(1647.7744105852674733 - 1423.1270943036622097j)
|
||||
assert zeta(1+1000000j, derivative=3).ae(3.4085866124523582894 - 18.179184721525947301j)
|
||||
assert zeta(1+1000000j, derivative=1).ae(-0.10423479366985452134 - 0.74728992803359056244j)
|
||||
assert zeta(0.5-1000000j, derivative=1).ae(11.636804066002521459 + 17.127254072212996004j)
|
||||
# Additional sanity tests using fp arithmetic.
|
||||
# Some more high-precision tests are found in the docstrings
|
||||
def ae(x, y, tol=1e-6):
|
||||
return abs(x-y) < tol*abs(y)
|
||||
assert ae(fp.zeta(0.5-100000j), 1.0730320148577531321 - 5.7808485443635039843j)
|
||||
assert ae(fp.zeta(0.75-100000j), 1.837852337251873704 - 1.9988492668661145358j)
|
||||
assert ae(fp.zeta(0.5+1e6j), 0.076089069738227100006 + 2.8051021010192989554j)
|
||||
assert ae(fp.zeta(0.5+1e6j, derivative=1), 11.636804066002521459 - 17.127254072212996004j)
|
||||
assert ae(fp.zeta(1+1e6j), 0.94738726251047891048 + 0.59421999312091832833j)
|
||||
assert ae(fp.zeta(1+1e6j, derivative=1), -0.10423479366985452134 - 0.74728992803359056244j)
|
||||
assert ae(fp.zeta(0.5+100000j, derivative=1), 10.766962036817482375 - 30.92705282105996714j)
|
||||
assert ae(fp.zeta(0.5+100000j, derivative=2), -119.40515625740538429 + 217.14780631141830251j)
|
||||
assert ae(fp.zeta(0.5+100000j, derivative=3), 1129.7550282628460881 - 1685.4736895169690346j)
|
||||
assert ae(fp.zeta(0.5+100000j, derivative=4), -10407.160819314958615 + 13777.786698628045085j)
|
||||
assert ae(fp.zeta(0.75+100000j, derivative=1), -0.41742276699594321475 - 6.4453816275049955949j)
|
||||
assert ae(fp.zeta(0.75+100000j, derivative=2), -9.214314279161977266 + 35.07290795337967899j)
|
||||
assert ae(fp.zeta(0.75+100000j, derivative=3), 110.61331857820103469 - 236.87847130518129926j)
|
||||
assert ae(fp.zeta(0.75+100000j, derivative=4), -1054.334275898559401 + 1769.9177890161596383j)
|
||||
|
||||
def test_siegelz():
|
||||
mp.dps = 15
|
||||
assert siegelz(100000).ae(5.87959246868176504171)
|
||||
assert siegelz(100000, derivative=2).ae(-54.1172711010126452832)
|
||||
assert siegelz(100000, derivative=3).ae(-278.930831343966552538)
|
||||
assert siegelz(100000+j,derivative=1).ae(678.214511857070283307-379.742160779916375413j)
|
||||
|
||||
|
||||
|
||||
def test_zeta_near_1():
|
||||
# Test for a former bug in mpf_zeta and mpc_zeta
|
||||
mp.dps = 15
|
||||
s1 = fadd(1, '1e-10', exact=True)
|
||||
s2 = fadd(1, '-1e-10', exact=True)
|
||||
s3 = fadd(1, '1e-10j', exact=True)
|
||||
assert zeta(s1).ae(1.000000000057721566490881444e10)
|
||||
assert zeta(s2).ae(-9.99999999942278433510574872e9)
|
||||
z = zeta(s3)
|
||||
assert z.real.ae(0.57721566490153286060)
|
||||
assert z.imag.ae(-9.9999999999999999999927184e9)
|
||||
mp.dps = 30
|
||||
s1 = fadd(1, '1e-50', exact=True)
|
||||
s2 = fadd(1, '-1e-50', exact=True)
|
||||
s3 = fadd(1, '1e-50j', exact=True)
|
||||
assert zeta(s1).ae('1e50')
|
||||
assert zeta(s2).ae('-1e50')
|
||||
z = zeta(s3)
|
||||
assert z.real.ae('0.57721566490153286060651209008240243104215933593992')
|
||||
assert z.imag.ae('-1e50')
|
||||
@@ -0,0 +1,291 @@
|
||||
"""
|
||||
Check that the output from irrational functions is accurate for
|
||||
high-precision input, from 5 to 200 digits. The reference values were
|
||||
verified with Mathematica.
|
||||
"""
|
||||
|
||||
import time
|
||||
from mpmath import *
|
||||
|
||||
precs = [5, 15, 28, 35, 57, 80, 100, 150, 200]
|
||||
|
||||
# sqrt(3) + pi/2
|
||||
a = \
|
||||
"3.302847134363773912758768033145623809041389953497933538543279275605"\
|
||||
"841220051904536395163599428307109666700184672047856353516867399774243594"\
|
||||
"67433521615861420725323528325327484262075464241255915238845599752675"
|
||||
|
||||
# e + 1/euler**2
|
||||
b = \
|
||||
"5.719681166601007617111261398629939965860873957353320734275716220045750"\
|
||||
"31474116300529519620938123730851145473473708966080207482581266469342214"\
|
||||
"824842256999042984813905047895479210702109260221361437411947323431"
|
||||
|
||||
# sqrt(a)
|
||||
sqrt_a = \
|
||||
"1.817373691447021556327498239690365674922395036495564333152483422755"\
|
||||
"144321726165582817927383239308173567921345318453306994746434073691275094"\
|
||||
"484777905906961689902608644112196725896908619756404253109722911487"
|
||||
|
||||
# sqrt(a+b*i).real
|
||||
sqrt_abi_real = \
|
||||
"2.225720098415113027729407777066107959851146508557282707197601407276"\
|
||||
"89160998185797504198062911768240808839104987021515555650875977724230130"\
|
||||
"3584116233925658621288393930286871862273400475179312570274423840384"
|
||||
|
||||
# sqrt(a+b*i).imag
|
||||
sqrt_abi_imag = \
|
||||
"1.2849057639084690902371581529110949983261182430040898147672052833653668"\
|
||||
"0629534491275114877090834296831373498336559849050755848611854282001250"\
|
||||
"1924311019152914021365263161630765255610885489295778894976075186"
|
||||
|
||||
# log(a)
|
||||
log_a = \
|
||||
"1.194784864491089550288313512105715261520511949410072046160598707069"\
|
||||
"4336653155025770546309137440687056366757650909754708302115204338077595203"\
|
||||
"83005773986664564927027147084436553262269459110211221152925732612"
|
||||
|
||||
# log(a+b*i).real
|
||||
log_abi_real = \
|
||||
"1.8877985921697018111624077550443297276844736840853590212962006811663"\
|
||||
"04949387789489704203167470111267581371396245317618589339274243008242708"\
|
||||
"014251531496104028712866224020066439049377679709216784954509456421"
|
||||
|
||||
# log(a+b*i).imag
|
||||
log_abi_imag = \
|
||||
"1.0471204952840802663567714297078763189256357109769672185219334169734948"\
|
||||
"4265809854092437285294686651806426649541504240470168212723133326542181"\
|
||||
"8300136462287639956713914482701017346851009323172531601894918640"
|
||||
|
||||
# exp(a)
|
||||
exp_a = \
|
||||
"27.18994224087168661137253262213293847994194869430518354305430976149"\
|
||||
"382792035050358791398632888885200049857986258414049540376323785711941636"\
|
||||
"100358982497583832083513086941635049329804685212200507288797531143"
|
||||
|
||||
# exp(a+b*i).real
|
||||
exp_abi_real = \
|
||||
"22.98606617170543596386921087657586890620262522816912505151109385026"\
|
||||
"40160179326569526152851983847133513990281518417211964710397233157168852"\
|
||||
"4963130831190142571659948419307628119985383887599493378056639916701"
|
||||
|
||||
# exp(a+b*i).imag
|
||||
exp_abi_imag = \
|
||||
"-14.523557450291489727214750571590272774669907424478129280902375851196283"\
|
||||
"3377162379031724734050088565710975758824441845278120105728824497308303"\
|
||||
"6065619788140201636218705414429933685889542661364184694108251449"
|
||||
|
||||
# a**b
|
||||
pow_a_b = \
|
||||
"928.7025342285568142947391505837660251004990092821305668257284426997"\
|
||||
"361966028275685583421197860603126498884545336686124793155581311527995550"\
|
||||
"580229264427202446131740932666832138634013168125809402143796691154"
|
||||
|
||||
# (a**(a+b*i)).real
|
||||
pow_a_abi_real = \
|
||||
"44.09156071394489511956058111704382592976814280267142206420038656267"\
|
||||
"67707916510652790502399193109819563864568986234654864462095231138500505"\
|
||||
"8197456514795059492120303477512711977915544927440682508821426093455"
|
||||
|
||||
# (a**(a+b*i)).imag
|
||||
pow_a_abi_imag = \
|
||||
"27.069371511573224750478105146737852141664955461266218367212527612279886"\
|
||||
"9322304536553254659049205414427707675802193810711302947536332040474573"\
|
||||
"8166261217563960235014674118610092944307893857862518964990092301"
|
||||
|
||||
# ((a+b*i)**(a+b*i)).real
|
||||
pow_abi_abi_real = \
|
||||
"-0.15171310677859590091001057734676423076527145052787388589334350524"\
|
||||
"8084195882019497779202452975350579073716811284169068082670778986235179"\
|
||||
"0813026562962084477640470612184016755250592698408112493759742219150452"\
|
||||
|
||||
# ((a+b*i)**(a+b*i)).imag
|
||||
pow_abi_abi_imag = \
|
||||
"1.2697592504953448936553147870155987153192995316950583150964099070426"\
|
||||
"4736837932577176947632535475040521749162383347758827307504526525647759"\
|
||||
"97547638617201824468382194146854367480471892602963428122896045019902"
|
||||
|
||||
# sin(a)
|
||||
sin_a = \
|
||||
"-0.16055653857469062740274792907968048154164433772938156243509084009"\
|
||||
"38437090841460493108570147191289893388608611542655654723437248152535114"\
|
||||
"528368009465836614227575701220612124204622383149391870684288862269631"
|
||||
|
||||
# sin(1000*a)
|
||||
sin_1000a = \
|
||||
"-0.85897040577443833776358106803777589664322997794126153477060795801"\
|
||||
"09151695416961724733492511852267067419573754315098042850381158563024337"\
|
||||
"216458577140500488715469780315833217177634490142748614625281171216863"
|
||||
|
||||
# sin(a+b*i)
|
||||
sin_abi_real = \
|
||||
"-24.4696999681556977743346798696005278716053366404081910969773939630"\
|
||||
"7149215135459794473448465734589287491880563183624997435193637389884206"\
|
||||
"02151395451271809790360963144464736839412254746645151672423256977064"
|
||||
|
||||
sin_abi_imag = \
|
||||
"-150.42505378241784671801405965872972765595073690984080160750785565810981"\
|
||||
"8314482499135443827055399655645954830931316357243750839088113122816583"\
|
||||
"7169201254329464271121058839499197583056427233866320456505060735"
|
||||
|
||||
# cos
|
||||
cos_a = \
|
||||
"-0.98702664499035378399332439243967038895709261414476495730788864004"\
|
||||
"05406821549361039745258003422386169330787395654908532996287293003581554"\
|
||||
"257037193284199198069707141161341820684198547572456183525659969145501"
|
||||
|
||||
cos_1000a = \
|
||||
"-0.51202523570982001856195696460663971099692261342827540426136215533"\
|
||||
"52686662667660613179619804463250686852463876088694806607652218586060613"\
|
||||
"951310588158830695735537073667299449753951774916401887657320950496820"
|
||||
|
||||
# tan
|
||||
tan_a = \
|
||||
"0.162666873675188117341401059858835168007137819495998960250142156848"\
|
||||
"639654718809412181543343168174807985559916643549174530459883826451064966"\
|
||||
"7996119428949951351938178809444268785629011625179962457123195557310"
|
||||
|
||||
tan_abi_real = \
|
||||
"6.822696615947538488826586186310162599974827139564433912601918442911"\
|
||||
"1026830824380070400102213741875804368044342309515353631134074491271890"\
|
||||
"467615882710035471686578162073677173148647065131872116479947620E-6"
|
||||
|
||||
tan_abi_imag = \
|
||||
"0.9999795833048243692245661011298447587046967777739649018690797625964167"\
|
||||
"1446419978852235960862841608081413169601038230073129482874832053357571"\
|
||||
"62702259309150715669026865777947502665936317953101462202542168429"
|
||||
|
||||
|
||||
def test_hp():
|
||||
for dps in precs:
|
||||
mp.dps = dps + 8
|
||||
aa = mpf(a)
|
||||
bb = mpf(b)
|
||||
a1000 = 1000*mpf(a)
|
||||
abi = mpc(aa, bb)
|
||||
mp.dps = dps
|
||||
assert (sqrt(3) + pi/2).ae(aa)
|
||||
assert (e + 1/euler**2).ae(bb)
|
||||
|
||||
assert sqrt(aa).ae(mpf(sqrt_a))
|
||||
assert sqrt(abi).ae(mpc(sqrt_abi_real, sqrt_abi_imag))
|
||||
|
||||
assert log(aa).ae(mpf(log_a))
|
||||
assert log(abi).ae(mpc(log_abi_real, log_abi_imag))
|
||||
|
||||
assert exp(aa).ae(mpf(exp_a))
|
||||
assert exp(abi).ae(mpc(exp_abi_real, exp_abi_imag))
|
||||
|
||||
assert (aa**bb).ae(mpf(pow_a_b))
|
||||
assert (aa**abi).ae(mpc(pow_a_abi_real, pow_a_abi_imag))
|
||||
assert (abi**abi).ae(mpc(pow_abi_abi_real, pow_abi_abi_imag))
|
||||
|
||||
assert sin(a).ae(mpf(sin_a))
|
||||
assert sin(a1000).ae(mpf(sin_1000a))
|
||||
assert sin(abi).ae(mpc(sin_abi_real, sin_abi_imag))
|
||||
|
||||
assert cos(a).ae(mpf(cos_a))
|
||||
assert cos(a1000).ae(mpf(cos_1000a))
|
||||
|
||||
assert tan(a).ae(mpf(tan_a))
|
||||
assert tan(abi).ae(mpc(tan_abi_real, tan_abi_imag))
|
||||
|
||||
# check that complex cancellation is avoided so that both
|
||||
# real and imaginary parts have high relative accuracy.
|
||||
# abs_eps should be 0, but has to be set to 1e-205 to pass the
|
||||
# 200-digit case, probably due to slight inaccuracy in the
|
||||
# precomputed input
|
||||
assert (tan(abi).real).ae(mpf(tan_abi_real), abs_eps=1e-205)
|
||||
assert (tan(abi).imag).ae(mpf(tan_abi_imag), abs_eps=1e-205)
|
||||
mp.dps = 460
|
||||
assert str(log(3))[-20:] == '02166121184001409826'
|
||||
mp.dps = 15
|
||||
|
||||
# Since str(a) can differ in the last digit from rounded a, and I want
|
||||
# to compare the last digits of big numbers with the results in Mathematica,
|
||||
# I made this hack to get the last 20 digits of rounded a
|
||||
|
||||
def last_digits(a):
|
||||
r = repr(a)
|
||||
s = str(a)
|
||||
#dps = mp.dps
|
||||
#mp.dps += 3
|
||||
m = 10
|
||||
r = r.replace(s[:-m],'')
|
||||
r = r.replace("mpf('",'').replace("')",'')
|
||||
num0 = 0
|
||||
for c in r:
|
||||
if c == '0':
|
||||
num0 += 1
|
||||
else:
|
||||
break
|
||||
b = float(int(r))/10**(len(r) - m)
|
||||
if b >= 10**m - 0.5: # pragma: no cover
|
||||
raise NotImplementedError
|
||||
n = int(round(b))
|
||||
sn = str(n)
|
||||
s = s[:-m] + '0'*num0 + sn
|
||||
return s[-20:]
|
||||
|
||||
# values checked with Mathematica
|
||||
def test_log_hp():
|
||||
mp.dps = 2000
|
||||
a = mpf(10)**15000/3
|
||||
r = log(a)
|
||||
res = last_digits(r)
|
||||
# Mathematica N[Log[10^15000/3], 2000]
|
||||
# ...7443804441768333470331
|
||||
assert res == '43804441768333470331'
|
||||
|
||||
# see issue 145
|
||||
r = log(mpf(3)/2)
|
||||
# Mathematica N[Log[3/2], 2000]
|
||||
# ...69653749808140753263288
|
||||
res = last_digits(r)
|
||||
assert res == '53749808140753263288'
|
||||
|
||||
mp.dps = 10000
|
||||
r = log(2)
|
||||
res = last_digits(r)
|
||||
# Mathematica N[Log[2], 10000]
|
||||
# ...695615913401856601359655561
|
||||
assert res == '13401856601359655561'
|
||||
r = log(mpf(10)**10/3)
|
||||
res = last_digits(r)
|
||||
# Mathematica N[Log[10^10/3], 10000]
|
||||
# ...587087654020631943060007154
|
||||
assert res == '54020631943060007154', res
|
||||
r = log(mpf(10)**100/3)
|
||||
res = last_digits(r)
|
||||
# Mathematica N[Log[10^100/3], 10000]
|
||||
# ,,,59246336539088351652334666
|
||||
assert res == '36539088351652334666', res
|
||||
mp.dps += 10
|
||||
a = 1 - mpf(1)/10**10
|
||||
mp.dps -= 10
|
||||
r = log(a)
|
||||
res = last_digits(r)
|
||||
# ...3310334360482956137216724048322957404
|
||||
# 372167240483229574038733026370
|
||||
# Mathematica N[Log[1 - 10^-10]*10^10, 10000]
|
||||
# ...60482956137216724048322957404
|
||||
assert res == '37216724048322957404', res
|
||||
mp.dps = 10000
|
||||
mp.dps += 100
|
||||
a = 1 + mpf(1)/10**100
|
||||
mp.dps -= 100
|
||||
|
||||
r = log(a)
|
||||
res = last_digits(+r)
|
||||
# Mathematica N[Log[1 + 10^-100]*10^10, 10030]
|
||||
# ...3994733877377412241546890854692521568292338268273 10^-91
|
||||
assert res == '39947338773774122415', res
|
||||
|
||||
mp.dps = 15
|
||||
|
||||
def test_exp_hp():
|
||||
mp.dps = 4000
|
||||
r = exp(mpf(1)/10)
|
||||
# IntegerPart[N[Exp[1/10] * 10^4000, 4000]]
|
||||
# ...92167105162069688129
|
||||
assert int(r * 10**mp.dps) % 10**20 == 92167105162069688129
|
||||
@@ -0,0 +1,19 @@
|
||||
from mpmath import *
|
||||
|
||||
def test_pslq():
|
||||
mp.dps = 15
|
||||
assert pslq([3*pi+4*e/7, pi, e, log(2)]) == [7, -21, -4, 0]
|
||||
assert pslq([4.9999999999999991, 1]) == [1, -5]
|
||||
assert pslq([2,1]) == [1, -2]
|
||||
|
||||
def test_identify():
|
||||
mp.dps = 20
|
||||
assert identify(zeta(4), ['log(2)', 'pi**4']) == '((1/90)*pi**4)'
|
||||
mp.dps = 15
|
||||
assert identify(exp(5)) == 'exp(5)'
|
||||
assert identify(exp(4)) == 'exp(4)'
|
||||
assert identify(log(5)) == 'log(5)'
|
||||
assert identify(exp(3*pi), ['pi']) == 'exp((3*pi))'
|
||||
assert identify(3, full=True) == ['3', '3', '1/(1/3)', 'sqrt(9)',
|
||||
'1/sqrt((1/9))', '(sqrt(12)/2)**2', '1/(sqrt(12)/6)**2']
|
||||
assert identify(pi+1, {'a':+pi}) == '(1 + 1*a)'
|
||||
@@ -0,0 +1,453 @@
|
||||
from mpmath import *
|
||||
|
||||
def test_interval_identity():
|
||||
iv.dps = 15
|
||||
assert mpi(2) == mpi(2, 2)
|
||||
assert mpi(2) != mpi(-2, 2)
|
||||
assert not (mpi(2) != mpi(2, 2))
|
||||
assert mpi(-1, 1) == mpi(-1, 1)
|
||||
assert str(mpi('0.1')) == "[0.099999999999999991673, 0.10000000000000000555]"
|
||||
assert repr(mpi('0.1')) == "mpi('0.099999999999999992', '0.10000000000000001')"
|
||||
u = mpi(-1, 3)
|
||||
assert -1 in u
|
||||
assert 2 in u
|
||||
assert 3 in u
|
||||
assert -1.1 not in u
|
||||
assert 3.1 not in u
|
||||
assert mpi(-1, 3) in u
|
||||
assert mpi(0, 1) in u
|
||||
assert mpi(-1.1, 2) not in u
|
||||
assert mpi(2.5, 3.1) not in u
|
||||
w = mpi(-inf, inf)
|
||||
assert mpi(-5, 5) in w
|
||||
assert mpi(2, inf) in w
|
||||
assert mpi(0, 2) in mpi(0, 10)
|
||||
assert not (3 in mpi(-inf, 0))
|
||||
|
||||
def test_interval_hash():
|
||||
assert hash(mpi(3)) == hash(3)
|
||||
assert hash(mpi(3.25)) == hash(3.25)
|
||||
assert hash(mpi(3,4)) == hash(mpi(3,4))
|
||||
assert hash(iv.mpc(3)) == hash(3)
|
||||
assert hash(iv.mpc(3,4)) == hash(3+4j)
|
||||
assert hash(iv.mpc((1,3),(2,4))) == hash(iv.mpc((1,3),(2,4)))
|
||||
|
||||
def test_interval_arithmetic():
|
||||
iv.dps = 15
|
||||
assert mpi(2) + mpi(3,4) == mpi(5,6)
|
||||
assert mpi(1, 2)**2 == mpi(1, 4)
|
||||
assert mpi(1) + mpi(0, 1e-50) == mpi(1, mpf('1.0000000000000002'))
|
||||
x = 1 / (1 / mpi(3))
|
||||
assert x.a < 3 < x.b
|
||||
x = mpi(2) ** mpi(0.5)
|
||||
iv.dps += 5
|
||||
sq = iv.sqrt(2)
|
||||
iv.dps -= 5
|
||||
assert x.a < sq < x.b
|
||||
assert mpi(1) / mpi(1, inf)
|
||||
assert mpi(2, 3) / inf == mpi(0, 0)
|
||||
assert mpi(0) / inf == 0
|
||||
assert mpi(0) / 0 == mpi(-inf, inf)
|
||||
assert mpi(inf) / 0 == mpi(-inf, inf)
|
||||
assert mpi(0) * inf == mpi(-inf, inf)
|
||||
assert 1 / mpi(2, inf) == mpi(0, 0.5)
|
||||
assert str((mpi(50, 50) * mpi(-10, -10)) / 3) == \
|
||||
'[-166.66666666666668561, -166.66666666666665719]'
|
||||
assert mpi(0, 4) ** 3 == mpi(0, 64)
|
||||
assert mpi(2,4).mid == 3
|
||||
iv.dps = 30
|
||||
a = mpi(iv.pi)
|
||||
iv.dps = 15
|
||||
b = +a
|
||||
assert b.a < a.a
|
||||
assert b.b > a.b
|
||||
a = mpi(iv.pi)
|
||||
assert a == +a
|
||||
assert abs(mpi(-1,2)) == mpi(0,2)
|
||||
assert abs(mpi(0.5,2)) == mpi(0.5,2)
|
||||
assert abs(mpi(-3,2)) == mpi(0,3)
|
||||
assert abs(mpi(-3,-0.5)) == mpi(0.5,3)
|
||||
assert mpi(0) * mpi(2,3) == mpi(0)
|
||||
assert mpi(2,3) * mpi(0) == mpi(0)
|
||||
assert mpi(1,3).delta == 2
|
||||
assert mpi(1,2) - mpi(3,4) == mpi(-3,-1)
|
||||
assert mpi(-inf,0) - mpi(0,inf) == mpi(-inf,0)
|
||||
assert mpi(-inf,0) - mpi(-inf,inf) == mpi(-inf,inf)
|
||||
assert mpi(0,inf) - mpi(-inf,1) == mpi(-1,inf)
|
||||
|
||||
def test_interval_mul():
|
||||
assert mpi(-1, 0) * inf == mpi(-inf, 0)
|
||||
assert mpi(-1, 0) * -inf == mpi(0, inf)
|
||||
assert mpi(0, 1) * inf == mpi(0, inf)
|
||||
assert mpi(0, 1) * mpi(0, inf) == mpi(0, inf)
|
||||
assert mpi(-1, 1) * inf == mpi(-inf, inf)
|
||||
assert mpi(-1, 1) * mpi(0, inf) == mpi(-inf, inf)
|
||||
assert mpi(-1, 1) * mpi(-inf, inf) == mpi(-inf, inf)
|
||||
assert mpi(-inf, 0) * mpi(0, 1) == mpi(-inf, 0)
|
||||
assert mpi(-inf, 0) * mpi(0, 0) * mpi(-inf, 0)
|
||||
assert mpi(-inf, 0) * mpi(-inf, inf) == mpi(-inf, inf)
|
||||
assert mpi(-5,0)*mpi(-32,28) == mpi(-140,160)
|
||||
assert mpi(2,3) * mpi(-1,2) == mpi(-3,6)
|
||||
# Should be undefined?
|
||||
assert mpi(inf, inf) * 0 == mpi(-inf, inf)
|
||||
assert mpi(-inf, -inf) * 0 == mpi(-inf, inf)
|
||||
assert mpi(0) * mpi(-inf,2) == mpi(-inf,inf)
|
||||
assert mpi(0) * mpi(-2,inf) == mpi(-inf,inf)
|
||||
assert mpi(-2,inf) * mpi(0) == mpi(-inf,inf)
|
||||
assert mpi(-inf,2) * mpi(0) == mpi(-inf,inf)
|
||||
|
||||
def test_interval_pow():
|
||||
assert mpi(3)**2 == mpi(9, 9)
|
||||
assert mpi(-3)**2 == mpi(9, 9)
|
||||
assert mpi(-3, 1)**2 == mpi(0, 9)
|
||||
assert mpi(-3, -1)**2 == mpi(1, 9)
|
||||
assert mpi(-3, -1)**3 == mpi(-27, -1)
|
||||
assert mpi(-3, 1)**3 == mpi(-27, 1)
|
||||
assert mpi(-2, 3)**2 == mpi(0, 9)
|
||||
assert mpi(-3, 2)**2 == mpi(0, 9)
|
||||
assert mpi(4) ** -1 == mpi(0.25, 0.25)
|
||||
assert mpi(-4) ** -1 == mpi(-0.25, -0.25)
|
||||
assert mpi(4) ** -2 == mpi(0.0625, 0.0625)
|
||||
assert mpi(-4) ** -2 == mpi(0.0625, 0.0625)
|
||||
assert mpi(0, 1) ** inf == mpi(0, 1)
|
||||
assert mpi(0, 1) ** -inf == mpi(1, inf)
|
||||
assert mpi(0, inf) ** inf == mpi(0, inf)
|
||||
assert mpi(0, inf) ** -inf == mpi(0, inf)
|
||||
assert mpi(1, inf) ** inf == mpi(1, inf)
|
||||
assert mpi(1, inf) ** -inf == mpi(0, 1)
|
||||
assert mpi(2, 3) ** 1 == mpi(2, 3)
|
||||
assert mpi(2, 3) ** 0 == 1
|
||||
assert mpi(1,3) ** mpi(2) == mpi(1,9)
|
||||
|
||||
def test_interval_sqrt():
|
||||
assert mpi(4) ** 0.5 == mpi(2)
|
||||
|
||||
def test_interval_div():
|
||||
assert mpi(0.5, 1) / mpi(-1, 0) == mpi(-inf, -0.5)
|
||||
assert mpi(0, 1) / mpi(0, 1) == mpi(0, inf)
|
||||
assert mpi(inf, inf) / mpi(inf, inf) == mpi(0, inf)
|
||||
assert mpi(inf, inf) / mpi(2, inf) == mpi(0, inf)
|
||||
assert mpi(inf, inf) / mpi(2, 2) == mpi(inf, inf)
|
||||
assert mpi(0, inf) / mpi(2, inf) == mpi(0, inf)
|
||||
assert mpi(0, inf) / mpi(2, 2) == mpi(0, inf)
|
||||
assert mpi(2, inf) / mpi(2, 2) == mpi(1, inf)
|
||||
assert mpi(2, inf) / mpi(2, inf) == mpi(0, inf)
|
||||
assert mpi(-4, 8) / mpi(1, inf) == mpi(-4, 8)
|
||||
assert mpi(-4, 8) / mpi(0.5, inf) == mpi(-8, 16)
|
||||
assert mpi(-inf, 8) / mpi(0.5, inf) == mpi(-inf, 16)
|
||||
assert mpi(-inf, inf) / mpi(0.5, inf) == mpi(-inf, inf)
|
||||
assert mpi(8, inf) / mpi(0.5, inf) == mpi(0, inf)
|
||||
assert mpi(-8, inf) / mpi(0.5, inf) == mpi(-16, inf)
|
||||
assert mpi(-4, 8) / mpi(inf, inf) == mpi(0, 0)
|
||||
assert mpi(0, 8) / mpi(inf, inf) == mpi(0, 0)
|
||||
assert mpi(0, 0) / mpi(inf, inf) == mpi(0, 0)
|
||||
assert mpi(-inf, 0) / mpi(inf, inf) == mpi(-inf, 0)
|
||||
assert mpi(-inf, 8) / mpi(inf, inf) == mpi(-inf, 0)
|
||||
assert mpi(-inf, inf) / mpi(inf, inf) == mpi(-inf, inf)
|
||||
assert mpi(-8, inf) / mpi(inf, inf) == mpi(0, inf)
|
||||
assert mpi(0, inf) / mpi(inf, inf) == mpi(0, inf)
|
||||
assert mpi(8, inf) / mpi(inf, inf) == mpi(0, inf)
|
||||
assert mpi(inf, inf) / mpi(inf, inf) == mpi(0, inf)
|
||||
assert mpi(-1, 2) / mpi(0, 1) == mpi(-inf, +inf)
|
||||
assert mpi(0, 1) / mpi(0, 1) == mpi(0.0, +inf)
|
||||
assert mpi(-1, 0) / mpi(0, 1) == mpi(-inf, 0.0)
|
||||
assert mpi(-0.5, -0.25) / mpi(0, 1) == mpi(-inf, -0.25)
|
||||
assert mpi(0.5, 1) / mpi(0, 1) == mpi(0.5, +inf)
|
||||
assert mpi(0.5, 4) / mpi(0, 1) == mpi(0.5, +inf)
|
||||
assert mpi(-1, -0.5) / mpi(0, 1) == mpi(-inf, -0.5)
|
||||
assert mpi(-4, -0.5) / mpi(0, 1) == mpi(-inf, -0.5)
|
||||
assert mpi(-1, 2) / mpi(-2, 0.5) == mpi(-inf, +inf)
|
||||
assert mpi(0, 1) / mpi(-2, 0.5) == mpi(-inf, +inf)
|
||||
assert mpi(-1, 0) / mpi(-2, 0.5) == mpi(-inf, +inf)
|
||||
assert mpi(-0.5, -0.25) / mpi(-2, 0.5) == mpi(-inf, +inf)
|
||||
assert mpi(0.5, 1) / mpi(-2, 0.5) == mpi(-inf, +inf)
|
||||
assert mpi(0.5, 4) / mpi(-2, 0.5) == mpi(-inf, +inf)
|
||||
assert mpi(-1, -0.5) / mpi(-2, 0.5) == mpi(-inf, +inf)
|
||||
assert mpi(-4, -0.5) / mpi(-2, 0.5) == mpi(-inf, +inf)
|
||||
assert mpi(-1, 2) / mpi(-1, 0) == mpi(-inf, +inf)
|
||||
assert mpi(0, 1) / mpi(-1, 0) == mpi(-inf, 0.0)
|
||||
assert mpi(-1, 0) / mpi(-1, 0) == mpi(0.0, +inf)
|
||||
assert mpi(-0.5, -0.25) / mpi(-1, 0) == mpi(0.25, +inf)
|
||||
assert mpi(0.5, 1) / mpi(-1, 0) == mpi(-inf, -0.5)
|
||||
assert mpi(0.5, 4) / mpi(-1, 0) == mpi(-inf, -0.5)
|
||||
assert mpi(-1, -0.5) / mpi(-1, 0) == mpi(0.5, +inf)
|
||||
assert mpi(-4, -0.5) / mpi(-1, 0) == mpi(0.5, +inf)
|
||||
assert mpi(-1, 2) / mpi(0.5, 1) == mpi(-2.0, 4.0)
|
||||
assert mpi(0, 1) / mpi(0.5, 1) == mpi(0.0, 2.0)
|
||||
assert mpi(-1, 0) / mpi(0.5, 1) == mpi(-2.0, 0.0)
|
||||
assert mpi(-0.5, -0.25) / mpi(0.5, 1) == mpi(-1.0, -0.25)
|
||||
assert mpi(0.5, 1) / mpi(0.5, 1) == mpi(0.5, 2.0)
|
||||
assert mpi(0.5, 4) / mpi(0.5, 1) == mpi(0.5, 8.0)
|
||||
assert mpi(-1, -0.5) / mpi(0.5, 1) == mpi(-2.0, -0.5)
|
||||
assert mpi(-4, -0.5) / mpi(0.5, 1) == mpi(-8.0, -0.5)
|
||||
assert mpi(-1, 2) / mpi(-2, -0.5) == mpi(-4.0, 2.0)
|
||||
assert mpi(0, 1) / mpi(-2, -0.5) == mpi(-2.0, 0.0)
|
||||
assert mpi(-1, 0) / mpi(-2, -0.5) == mpi(0.0, 2.0)
|
||||
assert mpi(-0.5, -0.25) / mpi(-2, -0.5) == mpi(0.125, 1.0)
|
||||
assert mpi(0.5, 1) / mpi(-2, -0.5) == mpi(-2.0, -0.25)
|
||||
assert mpi(0.5, 4) / mpi(-2, -0.5) == mpi(-8.0, -0.25)
|
||||
assert mpi(-1, -0.5) / mpi(-2, -0.5) == mpi(0.25, 2.0)
|
||||
assert mpi(-4, -0.5) / mpi(-2, -0.5) == mpi(0.25, 8.0)
|
||||
# Should be undefined?
|
||||
assert mpi(0, 0) / mpi(0, 0) == mpi(-inf, inf)
|
||||
assert mpi(0, 0) / mpi(0, 1) == mpi(-inf, inf)
|
||||
|
||||
def test_interval_cos_sin():
|
||||
iv.dps = 15
|
||||
cos = iv.cos
|
||||
sin = iv.sin
|
||||
tan = iv.tan
|
||||
pi = iv.pi
|
||||
# Around 0
|
||||
assert cos(mpi(0)) == 1
|
||||
assert sin(mpi(0)) == 0
|
||||
assert cos(mpi(0,1)) == mpi(0.54030230586813965399, 1.0)
|
||||
assert sin(mpi(0,1)) == mpi(0, 0.8414709848078966159)
|
||||
assert cos(mpi(1,2)) == mpi(-0.4161468365471424069, 0.54030230586813976501)
|
||||
assert sin(mpi(1,2)) == mpi(0.84147098480789650488, 1.0)
|
||||
assert sin(mpi(1,2.5)) == mpi(0.59847214410395643824, 1.0)
|
||||
assert cos(mpi(-1, 1)) == mpi(0.54030230586813965399, 1.0)
|
||||
assert cos(mpi(-1, 0.5)) == mpi(0.54030230586813965399, 1.0)
|
||||
assert cos(mpi(-1, 1.5)) == mpi(0.070737201667702906405, 1.0)
|
||||
assert sin(mpi(-1,1)) == mpi(-0.8414709848078966159, 0.8414709848078966159)
|
||||
assert sin(mpi(-1,0.5)) == mpi(-0.8414709848078966159, 0.47942553860420300538)
|
||||
assert mpi(-0.8414709848078966159, 1.00000000000000002e-100) in sin(mpi(-1,1e-100))
|
||||
assert mpi(-2.00000000000000004e-100, 1.00000000000000002e-100) in sin(mpi(-2e-100,1e-100))
|
||||
# Same interval
|
||||
assert cos(mpi(2, 2.5))
|
||||
assert cos(mpi(3.5, 4)) == mpi(-0.93645668729079634129, -0.65364362086361182946)
|
||||
assert cos(mpi(5, 5.5)) == mpi(0.28366218546322624627, 0.70866977429126010168)
|
||||
assert mpi(0.59847214410395654927, 0.90929742682568170942) in sin(mpi(2, 2.5))
|
||||
assert sin(mpi(3.5, 4)) == mpi(-0.75680249530792831347, -0.35078322768961983646)
|
||||
assert sin(mpi(5, 5.5)) == mpi(-0.95892427466313856499, -0.70554032557039181306)
|
||||
# Higher roots
|
||||
iv.dps = 55
|
||||
w = 4*10**50 + mpi(0.5)
|
||||
for p in [15, 40, 80]:
|
||||
iv.dps = p
|
||||
assert 0 in sin(4*mpi(pi))
|
||||
assert 0 in sin(4*10**50*mpi(pi))
|
||||
assert 0 in cos((4+0.5)*mpi(pi))
|
||||
assert 0 in cos(w*mpi(pi))
|
||||
assert 1 in cos(4*mpi(pi))
|
||||
assert 1 in cos(4*10**50*mpi(pi))
|
||||
iv.dps = 15
|
||||
assert cos(mpi(2,inf)) == mpi(-1,1)
|
||||
assert sin(mpi(2,inf)) == mpi(-1,1)
|
||||
assert cos(mpi(-inf,2)) == mpi(-1,1)
|
||||
assert sin(mpi(-inf,2)) == mpi(-1,1)
|
||||
u = tan(mpi(0.5,1))
|
||||
assert mpf(u.a).ae(mp.tan(0.5))
|
||||
assert mpf(u.b).ae(mp.tan(1))
|
||||
v = iv.cot(mpi(0.5,1))
|
||||
assert mpf(v.a).ae(mp.cot(1))
|
||||
assert mpf(v.b).ae(mp.cot(0.5))
|
||||
# Sanity check of evaluation at n*pi and (n+1/2)*pi
|
||||
for n in range(-5,7,2):
|
||||
x = iv.cos(n*iv.pi)
|
||||
assert -1 in x
|
||||
assert x >= -1
|
||||
assert x != -1
|
||||
x = iv.sin((n+0.5)*iv.pi)
|
||||
assert -1 in x
|
||||
assert x >= -1
|
||||
assert x != -1
|
||||
for n in range(-6,8,2):
|
||||
x = iv.cos(n*iv.pi)
|
||||
assert 1 in x
|
||||
assert x <= 1
|
||||
if n:
|
||||
assert x != 1
|
||||
x = iv.sin((n+0.5)*iv.pi)
|
||||
assert 1 in x
|
||||
assert x <= 1
|
||||
assert x != 1
|
||||
for n in range(-6,7):
|
||||
x = iv.cos((n+0.5)*iv.pi)
|
||||
assert x.a < 0 < x.b
|
||||
x = iv.sin(n*iv.pi)
|
||||
if n:
|
||||
assert x.a < 0 < x.b
|
||||
|
||||
def test_interval_complex():
|
||||
# TODO: many more tests
|
||||
iv.dps = 15
|
||||
mp.dps = 15
|
||||
assert iv.mpc(2,3) == 2+3j
|
||||
assert iv.mpc(2,3) != 2+4j
|
||||
assert iv.mpc(2,3) != 1+3j
|
||||
assert 1+3j in iv.mpc([1,2],[3,4])
|
||||
assert 2+5j not in iv.mpc([1,2],[3,4])
|
||||
assert iv.mpc(1,2) + 1j == 1+3j
|
||||
assert iv.mpc([1,2],[2,3]) + 2+3j == iv.mpc([3,4],[5,6])
|
||||
assert iv.mpc([2,4],[4,8]) / 2 == iv.mpc([1,2],[2,4])
|
||||
assert iv.mpc([1,2],[2,4]) * 2j == iv.mpc([-8,-4],[2,4])
|
||||
assert iv.mpc([2,4],[4,8]) / 2j == iv.mpc([2,4],[-2,-1])
|
||||
assert iv.exp(2+3j).ae(mp.exp(2+3j))
|
||||
assert iv.log(2+3j).ae(mp.log(2+3j))
|
||||
assert (iv.mpc(2,3) ** iv.mpc(0.5,2)).ae(mp.mpc(2,3) ** mp.mpc(0.5,2))
|
||||
assert 1j in (iv.mpf(-1) ** 0.5)
|
||||
assert 1j in (iv.mpc(-1) ** 0.5)
|
||||
assert abs(iv.mpc(0)) == 0
|
||||
assert abs(iv.mpc(inf)) == inf
|
||||
assert abs(iv.mpc(3,4)) == 5
|
||||
assert abs(iv.mpc(4)) == 4
|
||||
assert abs(iv.mpc(0,4)) == 4
|
||||
assert abs(iv.mpc(0,[2,3])) == iv.mpf([2,3])
|
||||
assert abs(iv.mpc(0,[-3,2])) == iv.mpf([0,3])
|
||||
assert abs(iv.mpc([3,5],[4,12])) == iv.mpf([5,13])
|
||||
assert abs(iv.mpc([3,5],[-4,12])) == iv.mpf([3,13])
|
||||
assert iv.mpc(2,3) ** 0 == 1
|
||||
assert iv.mpc(2,3) ** 1 == (2+3j)
|
||||
assert iv.mpc(2,3) ** 2 == (2+3j)**2
|
||||
assert iv.mpc(2,3) ** 3 == (2+3j)**3
|
||||
assert iv.mpc(2,3) ** 4 == (2+3j)**4
|
||||
assert iv.mpc(2,3) ** 5 == (2+3j)**5
|
||||
assert iv.mpc(2,2) ** (-1) == (2+2j) ** (-1)
|
||||
assert iv.mpc(2,2) ** (-2) == (2+2j) ** (-2)
|
||||
assert iv.cos(2).ae(mp.cos(2))
|
||||
assert iv.sin(2).ae(mp.sin(2))
|
||||
assert iv.cos(2+3j).ae(mp.cos(2+3j))
|
||||
assert iv.sin(2+3j).ae(mp.sin(2+3j))
|
||||
|
||||
def test_interval_complex_arg():
|
||||
mp.dps = 15
|
||||
iv.dps = 15
|
||||
assert iv.arg(3) == 0
|
||||
assert iv.arg(0) == 0
|
||||
assert iv.arg([0,3]) == 0
|
||||
assert iv.arg(-3).ae(pi)
|
||||
assert iv.arg(2+3j).ae(iv.arg(2+3j))
|
||||
z = iv.mpc([-2,-1],[3,4])
|
||||
t = iv.arg(z)
|
||||
assert t.a.ae(mp.arg(-1+4j))
|
||||
assert t.b.ae(mp.arg(-2+3j))
|
||||
z = iv.mpc([-2,1],[3,4])
|
||||
t = iv.arg(z)
|
||||
assert t.a.ae(mp.arg(1+3j))
|
||||
assert t.b.ae(mp.arg(-2+3j))
|
||||
z = iv.mpc([1,2],[3,4])
|
||||
t = iv.arg(z)
|
||||
assert t.a.ae(mp.arg(2+3j))
|
||||
assert t.b.ae(mp.arg(1+4j))
|
||||
z = iv.mpc([1,2],[-2,3])
|
||||
t = iv.arg(z)
|
||||
assert t.a.ae(mp.arg(1-2j))
|
||||
assert t.b.ae(mp.arg(1+3j))
|
||||
z = iv.mpc([1,2],[-4,-3])
|
||||
t = iv.arg(z)
|
||||
assert t.a.ae(mp.arg(1-4j))
|
||||
assert t.b.ae(mp.arg(2-3j))
|
||||
z = iv.mpc([-1,2],[-4,-3])
|
||||
t = iv.arg(z)
|
||||
assert t.a.ae(mp.arg(-1-3j))
|
||||
assert t.b.ae(mp.arg(2-3j))
|
||||
z = iv.mpc([-2,-1],[-4,-3])
|
||||
t = iv.arg(z)
|
||||
assert t.a.ae(mp.arg(-2-3j))
|
||||
assert t.b.ae(mp.arg(-1-4j))
|
||||
z = iv.mpc([-2,-1],[-3,3])
|
||||
t = iv.arg(z)
|
||||
assert t.a.ae(-mp.pi)
|
||||
assert t.b.ae(mp.pi)
|
||||
z = iv.mpc([-2,2],[-3,3])
|
||||
t = iv.arg(z)
|
||||
assert t.a.ae(-mp.pi)
|
||||
assert t.b.ae(mp.pi)
|
||||
|
||||
def test_interval_ae():
|
||||
iv.dps = 15
|
||||
x = iv.mpf([1,2])
|
||||
assert x.ae(1) is None
|
||||
assert x.ae(1.5) is None
|
||||
assert x.ae(2) is None
|
||||
assert x.ae(2.01) is False
|
||||
assert x.ae(0.99) is False
|
||||
x = iv.mpf(3.5)
|
||||
assert x.ae(3.5) is True
|
||||
assert x.ae(3.5+1e-15) is True
|
||||
assert x.ae(3.5-1e-15) is True
|
||||
assert x.ae(3.501) is False
|
||||
assert x.ae(3.499) is False
|
||||
assert x.ae(iv.mpf([3.5,3.501])) is None
|
||||
assert x.ae(iv.mpf([3.5,4.5+1e-15])) is None
|
||||
|
||||
def test_interval_nstr():
|
||||
iv.dps = n = 30
|
||||
x = mpi(1, 2)
|
||||
# FIXME: error_dps should not be necessary
|
||||
assert iv.nstr(x, n, mode='plusminus', error_dps=6) == '1.5 +- 0.5'
|
||||
assert iv.nstr(x, n, mode='plusminus', use_spaces=False, error_dps=6) == '1.5+-0.5'
|
||||
assert iv.nstr(x, n, mode='percent') == '1.5 (33.33%)'
|
||||
assert iv.nstr(x, n, mode='brackets', use_spaces=False) == '[1.0,2.0]'
|
||||
assert iv.nstr(x, n, mode='brackets' , brackets=('<', '>')) == '<1.0, 2.0>'
|
||||
x = mpi('5.2582327113062393041', '5.2582327113062749951')
|
||||
assert iv.nstr(x, n, mode='diff') == '5.2582327113062[393041, 749951]'
|
||||
assert iv.nstr(iv.cos(mpi(1)), n, mode='diff', use_spaces=False) == '0.54030230586813971740093660744[2955,3053]'
|
||||
assert iv.nstr(mpi('1e123', '1e129'), n, mode='diff') == '[1.0e+123, 1.0e+129]'
|
||||
exp = iv.exp
|
||||
assert iv.nstr(iv.exp(mpi('5000.1')), n, mode='diff') == '3.2797365856787867069110487[0926, 1191]e+2171'
|
||||
iv.dps = 15
|
||||
|
||||
def test_mpi_from_str():
|
||||
iv.dps = 15
|
||||
assert iv.convert('1.5 +- 0.5') == mpi(mpf('1.0'), mpf('2.0'))
|
||||
assert mpi(1, 2) in iv.convert('1.5 (33.33333333333333333333333333333%)')
|
||||
assert iv.convert('[1, 2]') == mpi(1, 2)
|
||||
assert iv.convert('1[2, 3]') == mpi(12, 13)
|
||||
assert iv.convert('1.[23,46]e-8') == mpi('1.23e-8', '1.46e-8')
|
||||
assert iv.convert('12[3.4,5.9]e4') == mpi('123.4e+4', '125.9e4')
|
||||
|
||||
def test_interval_gamma():
|
||||
mp.dps = 15
|
||||
iv.dps = 15
|
||||
# TODO: need many more tests
|
||||
assert iv.rgamma(0) == 0
|
||||
assert iv.fac(0) == 1
|
||||
assert iv.fac(1) == 1
|
||||
assert iv.fac(2) == 2
|
||||
assert iv.fac(3) == 6
|
||||
assert iv.gamma(0) == [-inf,inf]
|
||||
assert iv.gamma(1) == 1
|
||||
assert iv.gamma(2) == 1
|
||||
assert iv.gamma(3) == 2
|
||||
assert -3.5449077018110320546 in iv.gamma(-0.5)
|
||||
assert iv.loggamma(1) == 0
|
||||
assert iv.loggamma(2) == 0
|
||||
assert 0.69314718055994530942 in iv.loggamma(3)
|
||||
# Test tight log-gamma endpoints based on monotonicity
|
||||
xs = [iv.mpc([2,3],[1,4]),
|
||||
iv.mpc([2,3],[-4,-1]),
|
||||
iv.mpc([2,3],[-1,4]),
|
||||
iv.mpc([2,3],[-4,1]),
|
||||
iv.mpc([2,3],[-4,4]),
|
||||
iv.mpc([-3,-2],[2,4]),
|
||||
iv.mpc([-3,-2],[-4,-2])]
|
||||
for x in xs:
|
||||
ys = [mp.loggamma(mp.mpc(x.a,x.c)),
|
||||
mp.loggamma(mp.mpc(x.b,x.c)),
|
||||
mp.loggamma(mp.mpc(x.a,x.d)),
|
||||
mp.loggamma(mp.mpc(x.b,x.d))]
|
||||
if 0 in x.imag:
|
||||
ys += [mp.loggamma(x.a), mp.loggamma(x.b)]
|
||||
min_real = min([y.real for y in ys])
|
||||
max_real = max([y.real for y in ys])
|
||||
min_imag = min([y.imag for y in ys])
|
||||
max_imag = max([y.imag for y in ys])
|
||||
z = iv.loggamma(x)
|
||||
assert z.a.ae(min_real)
|
||||
assert z.b.ae(max_real)
|
||||
assert z.c.ae(min_imag)
|
||||
assert z.d.ae(max_imag)
|
||||
|
||||
def test_interval_conversions():
|
||||
mp.dps = 15
|
||||
iv.dps = 15
|
||||
for a, b in ((-0.0, 0), (0.0, 0.5), (1.0, 1), \
|
||||
('-inf', 20.5), ('-inf', float(sqrt(2)))):
|
||||
r = mpi(a, b)
|
||||
assert int(r.b) == int(b)
|
||||
assert float(r.a) == float(a)
|
||||
assert float(r.b) == float(b)
|
||||
assert complex(r.a) == complex(a)
|
||||
assert complex(r.b) == complex(b)
|
||||
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from mpmath import mp
|
||||
from mpmath import libmp
|
||||
|
||||
xrange = libmp.backend.xrange
|
||||
|
||||
# Attention:
|
||||
# These tests run with 15-20 decimal digits precision. For higher precision the
|
||||
# working precision must be raised.
|
||||
|
||||
def test_levin_0():
|
||||
mp.dps = 17
|
||||
eps = mp.mpf(mp.eps)
|
||||
with mp.extraprec(2 * mp.prec):
|
||||
L = mp.levin(method = "levin", variant = "u")
|
||||
S, s, n = [], 0, 1
|
||||
while 1:
|
||||
s += mp.one / (n * n)
|
||||
n += 1
|
||||
S.append(s)
|
||||
v, e = L.update_psum(S)
|
||||
if e < eps:
|
||||
break
|
||||
if n > 1000: raise RuntimeError("iteration limit exceeded")
|
||||
eps = mp.exp(0.9 * mp.log(eps))
|
||||
err = abs(v - mp.pi ** 2 / 6)
|
||||
assert err < eps
|
||||
w = mp.nsum(lambda n: 1/(n * n), [1, mp.inf], method = "levin", levin_variant = "u")
|
||||
err = abs(v - w)
|
||||
assert err < eps
|
||||
|
||||
def test_levin_1():
|
||||
mp.dps = 17
|
||||
eps = mp.mpf(mp.eps)
|
||||
with mp.extraprec(2 * mp.prec):
|
||||
L = mp.levin(method = "levin", variant = "v")
|
||||
A, n = [], 1
|
||||
while 1:
|
||||
s = mp.mpf(n) ** (2 + 3j)
|
||||
n += 1
|
||||
A.append(s)
|
||||
v, e = L.update(A)
|
||||
if e < eps:
|
||||
break
|
||||
if n > 1000: raise RuntimeError("iteration limit exceeded")
|
||||
eps = mp.exp(0.9 * mp.log(eps))
|
||||
err = abs(v - mp.zeta(-2-3j))
|
||||
assert err < eps
|
||||
w = mp.nsum(lambda n: n ** (2 + 3j), [1, mp.inf], method = "levin", levin_variant = "v")
|
||||
err = abs(v - w)
|
||||
assert err < eps
|
||||
|
||||
def test_levin_2():
|
||||
# [2] A. Sidi - "Pratical Extrapolation Methods" p.373
|
||||
mp.dps = 17
|
||||
z=mp.mpf(10)
|
||||
eps = mp.mpf(mp.eps)
|
||||
with mp.extraprec(2 * mp.prec):
|
||||
L = mp.levin(method = "sidi", variant = "t")
|
||||
n = 0
|
||||
while 1:
|
||||
s = (-1)**n * mp.fac(n) * z ** (-n)
|
||||
v, e = L.step(s)
|
||||
n += 1
|
||||
if e < eps:
|
||||
break
|
||||
if n > 1000: raise RuntimeError("iteration limit exceeded")
|
||||
eps = mp.exp(0.9 * mp.log(eps))
|
||||
exact = mp.quad(lambda x: mp.exp(-x)/(1+x/z),[0,mp.inf])
|
||||
# there is also a symbolic expression for the integral:
|
||||
# exact = z * mp.exp(z) * mp.expint(1,z)
|
||||
err = abs(v - exact)
|
||||
assert err < eps
|
||||
w = mp.nsum(lambda n: (-1) ** n * mp.fac(n) * z ** (-n), [0, mp.inf], method = "sidi", levin_variant = "t")
|
||||
assert err < eps
|
||||
|
||||
def test_levin_3():
|
||||
mp.dps = 17
|
||||
z=mp.mpf(2)
|
||||
eps = mp.mpf(mp.eps)
|
||||
with mp.extraprec(7*mp.prec): # we need copious amount of precision to sum this highly divergent series
|
||||
L = mp.levin(method = "levin", variant = "t")
|
||||
n, s = 0, 0
|
||||
while 1:
|
||||
s += (-z)**n * mp.fac(4 * n) / (mp.fac(n) * mp.fac(2 * n) * (4 ** n))
|
||||
n += 1
|
||||
v, e = L.step_psum(s)
|
||||
if e < eps:
|
||||
break
|
||||
if n > 1000: raise RuntimeError("iteration limit exceeded")
|
||||
eps = mp.exp(0.8 * mp.log(eps))
|
||||
exact = mp.quad(lambda x: mp.exp( -x * x / 2 - z * x ** 4), [0,mp.inf]) * 2 / mp.sqrt(2 * mp.pi)
|
||||
# there is also a symbolic expression for the integral:
|
||||
# exact = mp.exp(mp.one / (32 * z)) * mp.besselk(mp.one / 4, mp.one / (32 * z)) / (4 * mp.sqrt(z * mp.pi))
|
||||
err = abs(v - exact)
|
||||
assert err < eps
|
||||
w = mp.nsum(lambda n: (-z)**n * mp.fac(4 * n) / (mp.fac(n) * mp.fac(2 * n) * (4 ** n)), [0, mp.inf], method = "levin", levin_variant = "t", workprec = 8*mp.prec, steps = [2] + [1 for x in xrange(1000)])
|
||||
err = abs(v - w)
|
||||
assert err < eps
|
||||
|
||||
def test_levin_nsum():
|
||||
mp.dps = 17
|
||||
|
||||
with mp.extraprec(mp.prec):
|
||||
z = mp.mpf(10) ** (-10)
|
||||
a = mp.nsum(lambda n: n**(-(1+z)), [1, mp.inf], method = "l") - 1 / z
|
||||
assert abs(a - mp.euler) < 1e-10
|
||||
|
||||
eps = mp.exp(0.8 * mp.log(mp.eps))
|
||||
|
||||
a = mp.nsum(lambda n: (-1)**(n-1) / n, [1, mp.inf], method = "sidi")
|
||||
assert abs(a - mp.log(2)) < eps
|
||||
|
||||
z = 2 + 1j
|
||||
f = lambda n: mp.rf(2 / mp.mpf(3), n) * mp.rf(4 / mp.mpf(3), n) * z**n / (mp.rf(1 / mp.mpf(3), n) * mp.fac(n))
|
||||
v = mp.nsum(f, [0, mp.inf], method = "levin", steps = [10 for x in xrange(1000)])
|
||||
exact = mp.hyp2f1(2 / mp.mpf(3), 4 / mp.mpf(3), 1 / mp.mpf(3), z)
|
||||
assert abs(exact - v) < eps
|
||||
|
||||
def test_cohen_alt_0():
|
||||
mp.dps = 17
|
||||
AC = mp.cohen_alt()
|
||||
S, s, n = [], 0, 1
|
||||
while 1:
|
||||
s += -((-1) ** n) * mp.one / (n * n)
|
||||
n += 1
|
||||
S.append(s)
|
||||
v, e = AC.update_psum(S)
|
||||
if e < mp.eps:
|
||||
break
|
||||
if n > 1000: raise RuntimeError("iteration limit exceeded")
|
||||
eps = mp.exp(0.9 * mp.log(mp.eps))
|
||||
err = abs(v - mp.pi ** 2 / 12)
|
||||
assert err < eps
|
||||
|
||||
def test_cohen_alt_1():
|
||||
mp.dps = 17
|
||||
A = []
|
||||
AC = mp.cohen_alt()
|
||||
n = 1
|
||||
while 1:
|
||||
A.append( mp.loggamma(1 + mp.one / (2 * n - 1)))
|
||||
A.append(-mp.loggamma(1 + mp.one / (2 * n)))
|
||||
n += 1
|
||||
v, e = AC.update(A)
|
||||
if e < mp.eps:
|
||||
break
|
||||
if n > 1000: raise RuntimeError("iteration limit exceeded")
|
||||
v = mp.exp(v)
|
||||
err = abs(v - 1.06215090557106)
|
||||
assert err < 1e-12
|
||||
@@ -0,0 +1,332 @@
|
||||
# TODO: don't use round
|
||||
|
||||
from __future__ import division
|
||||
|
||||
import pytest
|
||||
from mpmath import *
|
||||
xrange = libmp.backend.xrange
|
||||
|
||||
# XXX: these shouldn't be visible(?)
|
||||
LU_decomp = mp.LU_decomp
|
||||
L_solve = mp.L_solve
|
||||
U_solve = mp.U_solve
|
||||
householder = mp.householder
|
||||
improve_solution = mp.improve_solution
|
||||
|
||||
A1 = matrix([[3, 1, 6],
|
||||
[2, 1, 3],
|
||||
[1, 1, 1]])
|
||||
b1 = [2, 7, 4]
|
||||
|
||||
A2 = matrix([[ 2, -1, -1, 2],
|
||||
[ 6, -2, 3, -1],
|
||||
[-4, 2, 3, -2],
|
||||
[ 2, 0, 4, -3]])
|
||||
b2 = [3, -3, -2, -1]
|
||||
|
||||
A3 = matrix([[ 1, 0, -1, -1, 0],
|
||||
[ 0, 1, 1, 0, -1],
|
||||
[ 4, -5, 2, 0, 0],
|
||||
[ 0, 0, -2, 9,-12],
|
||||
[ 0, 5, 0, 0, 12]])
|
||||
b3 = [0, 0, 0, 0, 50]
|
||||
|
||||
A4 = matrix([[10.235, -4.56, 0., -0.035, 5.67],
|
||||
[-2.463, 1.27, 3.97, -8.63, 1.08],
|
||||
[-6.58, 0.86, -0.257, 9.32, -43.6 ],
|
||||
[ 9.83, 7.39, -17.25, 0.036, 24.86],
|
||||
[-9.31, 34.9, 78.56, 1.07, 65.8 ]])
|
||||
b4 = [8.95, 20.54, 7.42, 5.60, 58.43]
|
||||
|
||||
A5 = matrix([[ 1, 2, -4],
|
||||
[-2, -3, 5],
|
||||
[ 3, 5, -8]])
|
||||
|
||||
A6 = matrix([[ 1.377360, 2.481400, 5.359190],
|
||||
[ 2.679280, -1.229560, 25.560210],
|
||||
[-1.225280+1.e6, 9.910180, -35.049900-1.e6]])
|
||||
b6 = [23.500000, -15.760000, 2.340000]
|
||||
|
||||
A7 = matrix([[1, -0.5],
|
||||
[2, 1],
|
||||
[-2, 6]])
|
||||
b7 = [3, 2, -4]
|
||||
|
||||
A8 = matrix([[1, 2, 3],
|
||||
[-1, 0, 1],
|
||||
[-1, -2, -1],
|
||||
[1, 0, -1]])
|
||||
b8 = [1, 2, 3, 4]
|
||||
|
||||
A9 = matrix([[ 4, 2, -2],
|
||||
[ 2, 5, -4],
|
||||
[-2, -4, 5.5]])
|
||||
b9 = [10, 16, -15.5]
|
||||
|
||||
A10 = matrix([[1.0 + 1.0j, 2.0, 2.0],
|
||||
[4.0, 5.0, 6.0],
|
||||
[7.0, 8.0, 9.0]])
|
||||
b10 = [1.0, 1.0 + 1.0j, 1.0]
|
||||
|
||||
|
||||
def test_LU_decomp():
|
||||
A = A3.copy()
|
||||
b = b3
|
||||
A, p = LU_decomp(A)
|
||||
y = L_solve(A, b, p)
|
||||
x = U_solve(A, y)
|
||||
assert p == [2, 1, 2, 3]
|
||||
assert [round(i, 14) for i in x] == [3.78953107960742, 2.9989094874591098,
|
||||
-0.081788440567070006, 3.8713195201744801, 2.9171210468920399]
|
||||
A = A4.copy()
|
||||
b = b4
|
||||
A, p = LU_decomp(A)
|
||||
y = L_solve(A, b, p)
|
||||
x = U_solve(A, y)
|
||||
assert p == [0, 3, 4, 3]
|
||||
assert [round(i, 14) for i in x] == [2.6383625899619201, 2.6643834462368399,
|
||||
0.79208015947958998, -2.5088376454101899, -1.0567657691375001]
|
||||
A = randmatrix(3)
|
||||
bak = A.copy()
|
||||
LU_decomp(A, overwrite=1)
|
||||
assert A != bak
|
||||
|
||||
def test_inverse():
|
||||
for A in [A1, A2, A5]:
|
||||
inv = inverse(A)
|
||||
assert mnorm(A*inv - eye(A.rows), 1) < 1.e-14
|
||||
|
||||
def test_householder():
|
||||
mp.dps = 15
|
||||
A, b = A8, b8
|
||||
H, p, x, r = householder(extend(A, b))
|
||||
assert H == matrix(
|
||||
[[mpf('3.0'), mpf('-2.0'), mpf('-1.0'), 0],
|
||||
[-1.0,mpf('3.333333333333333'),mpf('-2.9999999999999991'),mpf('2.0')],
|
||||
[-1.0, mpf('-0.66666666666666674'),mpf('2.8142135623730948'),
|
||||
mpf('-2.8284271247461898')],
|
||||
[1.0, mpf('-1.3333333333333333'),mpf('-0.20000000000000018'),
|
||||
mpf('4.2426406871192857')]])
|
||||
assert p == [-2, -2, mpf('-1.4142135623730949')]
|
||||
assert round(norm(r, 2), 10) == 4.2426406870999998
|
||||
|
||||
y = [102.102, 58.344, 36.463, 24.310, 17.017, 12.376, 9.282, 7.140, 5.610,
|
||||
4.488, 3.6465, 3.003]
|
||||
|
||||
def coeff(n):
|
||||
# similiar to Hilbert matrix
|
||||
A = []
|
||||
for i in range(1, 13):
|
||||
A.append([1. / (i + j - 1) for j in range(1, n + 1)])
|
||||
return matrix(A)
|
||||
|
||||
residuals = []
|
||||
refres = []
|
||||
for n in range(2, 7):
|
||||
A = coeff(n)
|
||||
H, p, x, r = householder(extend(A, y))
|
||||
x = matrix(x)
|
||||
y = matrix(y)
|
||||
residuals.append(norm(r, 2))
|
||||
refres.append(norm(residual(A, x, y), 2))
|
||||
assert [round(res, 10) for res in residuals] == [15.1733888877,
|
||||
0.82378073210000002, 0.302645887, 0.0260109244,
|
||||
0.00058653999999999998]
|
||||
assert norm(matrix(residuals) - matrix(refres), inf) < 1.e-13
|
||||
|
||||
def hilbert_cmplx(n):
|
||||
# Complexified Hilbert matrix
|
||||
A = hilbert(2*n,n)
|
||||
v = randmatrix(2*n, 2, min=-1, max=1)
|
||||
v = v.apply(lambda x: exp(1J*pi()*x))
|
||||
A = diag(v[:,0])*A*diag(v[:n,1])
|
||||
return A
|
||||
|
||||
residuals_cmplx = []
|
||||
refres_cmplx = []
|
||||
for n in range(2, 10):
|
||||
A = hilbert_cmplx(n)
|
||||
H, p, x, r = householder(A.copy())
|
||||
residuals_cmplx.append(norm(r, 2))
|
||||
refres_cmplx.append(norm(residual(A[:,:n-1], x, A[:,n-1]), 2))
|
||||
assert norm(matrix(residuals_cmplx) - matrix(refres_cmplx), inf) < 1.e-13
|
||||
|
||||
def test_factorization():
|
||||
A = randmatrix(5)
|
||||
P, L, U = lu(A)
|
||||
assert mnorm(P*A - L*U, 1) < 1.e-15
|
||||
|
||||
def test_solve():
|
||||
assert norm(residual(A6, lu_solve(A6, b6), b6), inf) < 1.e-10
|
||||
assert norm(residual(A7, lu_solve(A7, b7), b7), inf) < 1.5
|
||||
assert norm(residual(A8, lu_solve(A8, b8), b8), inf) <= 3 + 1.e-10
|
||||
assert norm(residual(A6, qr_solve(A6, b6)[0], b6), inf) < 1.e-10
|
||||
assert norm(residual(A7, qr_solve(A7, b7)[0], b7), inf) < 1.5
|
||||
assert norm(residual(A8, qr_solve(A8, b8)[0], b8), 2) <= 4.3
|
||||
assert norm(residual(A10, lu_solve(A10, b10), b10), 2) < 1.e-10
|
||||
assert norm(residual(A10, qr_solve(A10, b10)[0], b10), 2) < 1.e-10
|
||||
|
||||
def test_solve_overdet_complex():
|
||||
A = matrix([[1, 2j], [3, 4j], [5, 6]])
|
||||
b = matrix([1 + j, 2, -j])
|
||||
assert norm(residual(A, lu_solve(A, b), b)) < 1.0208
|
||||
|
||||
def test_singular():
|
||||
mp.dps = 15
|
||||
A = [[5.6, 1.2], [7./15, .1]]
|
||||
B = repr(zeros(2))
|
||||
b = [1, 2]
|
||||
for i in ['lu_solve(%s, %s)' % (A, b), 'lu_solve(%s, %s)' % (B, b),
|
||||
'qr_solve(%s, %s)' % (A, b), 'qr_solve(%s, %s)' % (B, b)]:
|
||||
pytest.raises((ZeroDivisionError, ValueError), lambda: eval(i))
|
||||
|
||||
def test_cholesky():
|
||||
assert fp.cholesky(fp.matrix(A9)) == fp.matrix([[2, 0, 0], [1, 2, 0], [-1, -3/2, 3/2]])
|
||||
x = fp.cholesky_solve(A9, b9)
|
||||
assert fp.norm(fp.residual(A9, x, b9), fp.inf) == 0
|
||||
|
||||
def test_det():
|
||||
assert det(A1) == 1
|
||||
assert round(det(A2), 14) == 8
|
||||
assert round(det(A3)) == 1834
|
||||
assert round(det(A4)) == 4443376
|
||||
assert det(A5) == 1
|
||||
assert round(det(A6)) == 78356463
|
||||
assert det(zeros(3)) == 0
|
||||
|
||||
def test_cond():
|
||||
mp.dps = 15
|
||||
A = matrix([[1.2969, 0.8648], [0.2161, 0.1441]])
|
||||
assert cond(A, lambda x: mnorm(x,1)) == mpf('327065209.73817754')
|
||||
assert cond(A, lambda x: mnorm(x,inf)) == mpf('327065209.73817754')
|
||||
assert cond(A, lambda x: mnorm(x,'F')) == mpf('249729266.80008656')
|
||||
|
||||
@extradps(50)
|
||||
def test_precision():
|
||||
A = randmatrix(10, 10)
|
||||
assert mnorm(inverse(inverse(A)) - A, 1) < 1.e-45
|
||||
|
||||
def test_interval_matrix():
|
||||
mp.dps = 15
|
||||
iv.dps = 15
|
||||
a = iv.matrix([['0.1','0.3','1.0'],['7.1','5.5','4.8'],['3.2','4.4','5.6']])
|
||||
b = iv.matrix(['4','0.6','0.5'])
|
||||
c = iv.lu_solve(a, b)
|
||||
assert c[0].delta < 1e-13
|
||||
assert c[1].delta < 1e-13
|
||||
assert c[2].delta < 1e-13
|
||||
assert 5.25823271130625686059275 in c[0]
|
||||
assert -13.155049396267837541163 in c[1]
|
||||
assert 7.42069154774972557628979 in c[2]
|
||||
|
||||
def test_LU_cache():
|
||||
A = randmatrix(3)
|
||||
LU = LU_decomp(A)
|
||||
assert A._LU == LU_decomp(A)
|
||||
A[0,0] = -1000
|
||||
assert A._LU is None
|
||||
|
||||
def test_improve_solution():
|
||||
A = randmatrix(5, min=1e-20, max=1e20)
|
||||
b = randmatrix(5, 1, min=-1000, max=1000)
|
||||
x1 = lu_solve(A, b) + randmatrix(5, 1, min=-1e-5, max=1.e-5)
|
||||
x2 = improve_solution(A, x1, b)
|
||||
assert norm(residual(A, x2, b), 2) < norm(residual(A, x1, b), 2)
|
||||
|
||||
def test_exp_pade():
|
||||
for i in range(3):
|
||||
dps = 15
|
||||
extra = 15
|
||||
mp.dps = dps + extra
|
||||
dm = 0
|
||||
N = 3
|
||||
dg = range(1,N+1)
|
||||
a = diag(dg)
|
||||
expa = diag([exp(x) for x in dg])
|
||||
# choose a random matrix not close to be singular
|
||||
# to avoid adding too much extra precision in computing
|
||||
# m**-1 * M * m
|
||||
while abs(dm) < 0.01:
|
||||
m = randmatrix(N)
|
||||
dm = det(m)
|
||||
m = m/dm
|
||||
a1 = m**-1 * a * m
|
||||
e2 = m**-1 * expa * m
|
||||
mp.dps = dps
|
||||
e1 = expm(a1, method='pade')
|
||||
mp.dps = dps + extra
|
||||
d = e2 - e1
|
||||
#print d
|
||||
mp.dps = dps
|
||||
assert norm(d, inf).ae(0)
|
||||
mp.dps = 15
|
||||
|
||||
def test_qr():
|
||||
mp.dps = 15 # used default value for dps
|
||||
lowlimit = -9 # lower limit of matrix element value
|
||||
uplimit = 9 # uppter limit of matrix element value
|
||||
maxm = 4 # max matrix size
|
||||
flg = False # toggle to create real vs complex matrix
|
||||
zero = mpf('0.0')
|
||||
|
||||
for k in xrange(0,10):
|
||||
exdps = 0
|
||||
mode = 'full'
|
||||
flg = bool(k % 2)
|
||||
|
||||
# generate arbitrary matrix size (2 to maxm)
|
||||
num1 = nint(maxm*rand())
|
||||
num2 = nint(maxm*rand())
|
||||
m = int(max(num1, num2))
|
||||
n = int(min(num1, num2))
|
||||
|
||||
# create matrix
|
||||
A = mp.matrix(m,n)
|
||||
|
||||
# populate matrix values with arbitrary integers
|
||||
if flg:
|
||||
flg = False
|
||||
dtype = 'complex'
|
||||
for j in xrange(0,n):
|
||||
for i in xrange(0,m):
|
||||
val = nint(lowlimit + (uplimit-lowlimit)*rand())
|
||||
val2 = nint(lowlimit + (uplimit-lowlimit)*rand())
|
||||
A[i,j] = mpc(val, val2)
|
||||
else:
|
||||
flg = True
|
||||
dtype = 'real'
|
||||
for j in xrange(0,n):
|
||||
for i in xrange(0,m):
|
||||
val = nint(lowlimit + (uplimit-lowlimit)*rand())
|
||||
A[i,j] = mpf(val)
|
||||
|
||||
# perform A -> QR decomposition
|
||||
Q, R = qr(A, mode, edps = exdps)
|
||||
|
||||
#print('\n\n A = \n', nstr(A, 4))
|
||||
#print('\n Q = \n', nstr(Q, 4))
|
||||
#print('\n R = \n', nstr(R, 4))
|
||||
#print('\n Q*R = \n', nstr(Q*R, 4))
|
||||
|
||||
maxnorm = mpf('1.0E-11')
|
||||
n1 = norm(A - Q * R)
|
||||
#print '\n Norm of A - Q * R = ', n1
|
||||
assert n1 <= maxnorm
|
||||
|
||||
if dtype == 'real':
|
||||
n1 = norm(eye(m) - Q.T * Q)
|
||||
#print ' Norm of I - Q.T * Q = ', n1
|
||||
assert n1 <= maxnorm
|
||||
|
||||
n1 = norm(eye(m) - Q * Q.T)
|
||||
#print ' Norm of I - Q * Q.T = ', n1
|
||||
assert n1 <= maxnorm
|
||||
|
||||
if dtype == 'complex':
|
||||
n1 = norm(eye(m) - Q.T * Q.conjugate())
|
||||
#print ' Norm of I - Q.T * Q.conjugate() = ', n1
|
||||
assert n1 <= maxnorm
|
||||
|
||||
n1 = norm(eye(m) - Q.conjugate() * Q.T)
|
||||
#print ' Norm of I - Q.conjugate() * Q.T = ', n1
|
||||
assert n1 <= maxnorm
|
||||
@@ -0,0 +1,253 @@
|
||||
import pytest
|
||||
import sys
|
||||
from mpmath import *
|
||||
|
||||
def test_matrix_basic():
|
||||
A1 = matrix(3)
|
||||
for i in range(3):
|
||||
A1[i,i] = 1
|
||||
assert A1 == eye(3)
|
||||
assert A1 == matrix(A1)
|
||||
A2 = matrix(3, 2)
|
||||
assert not A2._matrix__data
|
||||
A3 = matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
||||
assert list(A3) == list(range(1, 10))
|
||||
A3[1,1] = 0
|
||||
assert not (1, 1) in A3._matrix__data
|
||||
A4 = matrix([[1, 2, 3], [4, 5, 6]])
|
||||
A5 = matrix([[6, -1], [3, 2], [0, -3]])
|
||||
assert A4 * A5 == matrix([[12, -6], [39, -12]])
|
||||
assert A1 * A3 == A3 * A1 == A3
|
||||
pytest.raises(ValueError, lambda: A2*A2)
|
||||
l = [[10, 20, 30], [40, 0, 60], [70, 80, 90]]
|
||||
A6 = matrix(l)
|
||||
assert A6.tolist() == l
|
||||
assert A6 == eval(repr(A6))
|
||||
A6 = fp.matrix(A6)
|
||||
assert A6 == eval(repr(A6))
|
||||
assert A6*1j == eval(repr(A6*1j))
|
||||
assert A3 * 10 == 10 * A3 == A6
|
||||
assert A2.rows == 3
|
||||
assert A2.cols == 2
|
||||
A3.rows = 2
|
||||
A3.cols = 2
|
||||
assert len(A3._matrix__data) == 3
|
||||
assert A4 + A4 == 2*A4
|
||||
pytest.raises(ValueError, lambda: A4 + A2)
|
||||
assert sum(A1 - A1) == 0
|
||||
A7 = matrix([[1, 2], [3, 4], [5, 6], [7, 8]])
|
||||
x = matrix([10, -10])
|
||||
assert A7*x == matrix([-10, -10, -10, -10])
|
||||
A8 = ones(5)
|
||||
assert sum((A8 + 1) - (2 - zeros(5))) == 0
|
||||
assert (1 + ones(4)) / 2 - 1 == zeros(4)
|
||||
assert eye(3)**10 == eye(3)
|
||||
pytest.raises(ValueError, lambda: A7**2)
|
||||
A9 = randmatrix(3)
|
||||
A10 = matrix(A9)
|
||||
A9[0,0] = -100
|
||||
assert A9 != A10
|
||||
assert nstr(A9)
|
||||
|
||||
def test_matmul():
|
||||
"""
|
||||
Test the PEP465 "@" matrix multiplication syntax.
|
||||
To avoid syntax errors when importing this file in Python 3.5 and below, we have to use exec() - sorry for that.
|
||||
"""
|
||||
# TODO remove exec() wrapper as soon as we drop support for Python <= 3.5
|
||||
if sys.hexversion < 0x30500f0:
|
||||
# we are on Python < 3.5
|
||||
pytest.skip("'@' (__matmul__) is only supported in Python 3.5 or newer")
|
||||
A4 = matrix([[1, 2, 3], [4, 5, 6]])
|
||||
A5 = matrix([[6, -1], [3, 2], [0, -3]])
|
||||
exec("assert A4 @ A5 == A4 * A5")
|
||||
|
||||
def test_matrix_slices():
|
||||
A = matrix([ [1, 2, 3],
|
||||
[4, 5 ,6],
|
||||
[7, 8 ,9]])
|
||||
V = matrix([1,2,3,4,5])
|
||||
|
||||
# Get slice
|
||||
assert A[:,:] == A
|
||||
assert A[:,1] == matrix([[2],[5],[8]])
|
||||
assert A[2,:] == matrix([[7, 8 ,9]])
|
||||
assert A[1:3,1:3] == matrix([[5,6],[8,9]])
|
||||
assert V[2:4] == matrix([3,4])
|
||||
pytest.raises(IndexError, lambda: A[:,1:6])
|
||||
|
||||
# Assign slice with matrix
|
||||
A1 = matrix(3)
|
||||
A1[:,:] = A
|
||||
assert A1[:,:] == matrix([[1, 2, 3],
|
||||
[4, 5 ,6],
|
||||
[7, 8 ,9]])
|
||||
A1[0,:] = matrix([[10, 11, 12]])
|
||||
assert A1 == matrix([ [10, 11, 12],
|
||||
[4, 5 ,6],
|
||||
[7, 8 ,9]])
|
||||
A1[:,2] = matrix([[13], [14], [15]])
|
||||
assert A1 == matrix([ [10, 11, 13],
|
||||
[4, 5 ,14],
|
||||
[7, 8 ,15]])
|
||||
A1[:2,:2] = matrix([[16, 17], [18 , 19]])
|
||||
assert A1 == matrix([ [16, 17, 13],
|
||||
[18, 19 ,14],
|
||||
[7, 8 ,15]])
|
||||
V[1:3] = 10
|
||||
assert V == matrix([1,10,10,4,5])
|
||||
with pytest.raises(ValueError):
|
||||
A1[2,:] = A[:,1]
|
||||
|
||||
with pytest.raises(IndexError):
|
||||
A1[2,1:20] = A[:,:]
|
||||
|
||||
# Assign slice with scalar
|
||||
A1[:,2] = 10
|
||||
assert A1 == matrix([ [16, 17, 10],
|
||||
[18, 19 ,10],
|
||||
[7, 8 ,10]])
|
||||
A1[:,:] = 40
|
||||
for x in A1:
|
||||
assert x == 40
|
||||
|
||||
|
||||
def test_matrix_power():
|
||||
A = matrix([[1, 2], [3, 4]])
|
||||
assert A**2 == A*A
|
||||
assert A**3 == A*A*A
|
||||
assert A**-1 == inverse(A)
|
||||
assert A**-2 == inverse(A*A)
|
||||
|
||||
def test_matrix_transform():
|
||||
A = matrix([[1, 2], [3, 4], [5, 6]])
|
||||
assert A.T == A.transpose() == matrix([[1, 3, 5], [2, 4, 6]])
|
||||
swap_row(A, 1, 2)
|
||||
assert A == matrix([[1, 2], [5, 6], [3, 4]])
|
||||
l = [1, 2]
|
||||
swap_row(l, 0, 1)
|
||||
assert l == [2, 1]
|
||||
assert extend(eye(3), [1,2,3]) == matrix([[1,0,0,1],[0,1,0,2],[0,0,1,3]])
|
||||
|
||||
def test_matrix_conjugate():
|
||||
A = matrix([[1 + j, 0], [2, j]])
|
||||
assert A.conjugate() == matrix([[mpc(1, -1), 0], [2, mpc(0, -1)]])
|
||||
assert A.transpose_conj() == A.H == matrix([[mpc(1, -1), 2],
|
||||
[0, mpc(0, -1)]])
|
||||
|
||||
def test_matrix_creation():
|
||||
assert diag([1, 2, 3]) == matrix([[1, 0, 0], [0, 2, 0], [0, 0, 3]])
|
||||
A1 = ones(2, 3)
|
||||
assert A1.rows == 2 and A1.cols == 3
|
||||
for a in A1:
|
||||
assert a == 1
|
||||
A2 = zeros(3, 2)
|
||||
assert A2.rows == 3 and A2.cols == 2
|
||||
for a in A2:
|
||||
assert a == 0
|
||||
assert randmatrix(10) != randmatrix(10)
|
||||
one = mpf(1)
|
||||
assert hilbert(3) == matrix([[one, one/2, one/3],
|
||||
[one/2, one/3, one/4],
|
||||
[one/3, one/4, one/5]])
|
||||
|
||||
def test_norms():
|
||||
# matrix norms
|
||||
A = matrix([[1, -2], [-3, -1], [2, 1]])
|
||||
assert mnorm(A,1) == 6
|
||||
assert mnorm(A,inf) == 4
|
||||
assert mnorm(A,'F') == sqrt(20)
|
||||
# vector norms
|
||||
assert norm(-3) == 3
|
||||
x = [1, -2, 7, -12]
|
||||
assert norm(x, 1) == 22
|
||||
assert round(norm(x, 2), 10) == 14.0712472795
|
||||
assert round(norm(x, 10), 10) == 12.0054633727
|
||||
assert norm(x, inf) == 12
|
||||
|
||||
def test_vector():
|
||||
x = matrix([0, 1, 2, 3, 4])
|
||||
assert x == matrix([[0], [1], [2], [3], [4]])
|
||||
assert x[3] == 3
|
||||
assert len(x._matrix__data) == 4
|
||||
assert list(x) == list(range(5))
|
||||
x[0] = -10
|
||||
x[4] = 0
|
||||
assert x[0] == -10
|
||||
assert len(x) == len(x.T) == 5
|
||||
assert x.T*x == matrix([[114]])
|
||||
|
||||
def test_matrix_copy():
|
||||
A = ones(6)
|
||||
B = A.copy()
|
||||
C = +A
|
||||
assert A == B
|
||||
assert A == C
|
||||
B[0,0] = 0
|
||||
assert A != B
|
||||
C[0,0] = 42
|
||||
assert A != C
|
||||
|
||||
def test_matrix_numpy():
|
||||
try:
|
||||
import numpy
|
||||
except ImportError:
|
||||
return
|
||||
l = [[1, 2], [3, 4], [5, 6]]
|
||||
a = numpy.array(l)
|
||||
assert matrix(l) == matrix(a)
|
||||
|
||||
def test_interval_matrix_scalar_mult():
|
||||
"""Multiplication of iv.matrix and any scalar type"""
|
||||
a = mpi(-1, 1)
|
||||
b = a + a * 2j
|
||||
c = mpf(42)
|
||||
d = c + c * 2j
|
||||
e = 1.234
|
||||
f = fp.convert(e)
|
||||
g = e + e * 3j
|
||||
h = fp.convert(g)
|
||||
M = iv.ones(1)
|
||||
for x in [a, b, c, d, e, f, g, h]:
|
||||
assert x * M == iv.matrix([x])
|
||||
assert M * x == iv.matrix([x])
|
||||
|
||||
@pytest.mark.xfail()
|
||||
def test_interval_matrix_matrix_mult():
|
||||
"""Multiplication of iv.matrix and other matrix types"""
|
||||
A = ones(1)
|
||||
B = fp.ones(1)
|
||||
M = iv.ones(1)
|
||||
for X in [A, B, M]:
|
||||
assert X * M == iv.matrix(X)
|
||||
assert X * M == X
|
||||
assert M * X == iv.matrix(X)
|
||||
assert M * X == X
|
||||
|
||||
def test_matrix_conversion_to_iv():
|
||||
# Test that matrices with foreign datatypes are properly converted
|
||||
for other_type_eye in [eye(3), fp.eye(3), iv.eye(3)]:
|
||||
A = iv.matrix(other_type_eye)
|
||||
B = iv.eye(3)
|
||||
assert type(A[0,0]) == type(B[0,0])
|
||||
assert A.tolist() == B.tolist()
|
||||
|
||||
def test_interval_matrix_mult_bug():
|
||||
# regression test for interval matrix multiplication:
|
||||
# result must be nonzero-width and contain the exact result
|
||||
x = convert('1.00000000000001') # note: this is implicitly rounded to some near mpf float value
|
||||
A = matrix([[x]])
|
||||
B = iv.matrix(A)
|
||||
C = iv.matrix([[x]])
|
||||
assert B == C
|
||||
B = B * B
|
||||
C = C * C
|
||||
assert B == C
|
||||
assert B[0, 0].delta > 1e-16
|
||||
assert B[0, 0].delta < 3e-16
|
||||
assert C[0, 0].delta > 1e-16
|
||||
assert C[0, 0].delta < 3e-16
|
||||
assert mp.mpf('1.00000000000001998401444325291756783368705994138804689654') in B[0, 0]
|
||||
assert mp.mpf('1.00000000000001998401444325291756783368705994138804689654') in C[0, 0]
|
||||
# the following caused an error before the bug was fixed
|
||||
assert iv.matrix(mp.eye(2)) * (iv.ones(2) + mpi(1, 2)) == iv.matrix([[mpi(2, 3), mpi(2, 3)], [mpi(2, 3), mpi(2, 3)]])
|
||||
@@ -0,0 +1,7 @@
|
||||
from mpmath.libmp import *
|
||||
from mpmath import *
|
||||
|
||||
def test_newstyle_classes():
|
||||
for cls in [mp, fp, iv, mpf, mpc]:
|
||||
for s in cls.__class__.__mro__:
|
||||
assert isinstance(s, type)
|
||||
@@ -0,0 +1,73 @@
|
||||
#from mpmath.calculus import ODE_step_euler, ODE_step_rk4, odeint, arange
|
||||
from mpmath import odefun, cos, sin, mpf, sinc, mp
|
||||
|
||||
'''
|
||||
solvers = [ODE_step_euler, ODE_step_rk4]
|
||||
|
||||
def test_ode1():
|
||||
"""
|
||||
Let's solve:
|
||||
|
||||
x'' + w**2 * x = 0
|
||||
|
||||
i.e. x1 = x, x2 = x1':
|
||||
|
||||
x1' = x2
|
||||
x2' = -x1
|
||||
"""
|
||||
def derivs((x1, x2), t):
|
||||
return x2, -x1
|
||||
|
||||
for solver in solvers:
|
||||
t = arange(0, 3.1415926, 0.005)
|
||||
sol = odeint(derivs, (0., 1.), t, solver)
|
||||
x1 = [a[0] for a in sol]
|
||||
x2 = [a[1] for a in sol]
|
||||
# the result is x1 = sin(t), x2 = cos(t)
|
||||
# let's just check the end points for t = pi
|
||||
assert abs(x1[-1]) < 1e-2
|
||||
assert abs(x2[-1] - (-1)) < 1e-2
|
||||
|
||||
def test_ode2():
|
||||
"""
|
||||
Let's solve:
|
||||
|
||||
x' - x = 0
|
||||
|
||||
i.e. x = exp(x)
|
||||
|
||||
"""
|
||||
def derivs((x), t):
|
||||
return x
|
||||
|
||||
for solver in solvers:
|
||||
t = arange(0, 1, 1e-3)
|
||||
sol = odeint(derivs, (1.,), t, solver)
|
||||
x = [a[0] for a in sol]
|
||||
# the result is x = exp(t)
|
||||
# let's just check the end point for t = 1, i.e. x = e
|
||||
assert abs(x[-1] - 2.718281828) < 1e-2
|
||||
'''
|
||||
|
||||
def test_odefun_rational():
|
||||
mp.dps = 15
|
||||
# A rational function
|
||||
f = lambda t: 1/(1+mpf(t)**2)
|
||||
g = odefun(lambda x, y: [-2*x*y[0]**2], 0, [f(0)])
|
||||
assert f(2).ae(g(2)[0])
|
||||
|
||||
def test_odefun_sinc_large():
|
||||
mp.dps = 15
|
||||
# Sinc function; test for large x
|
||||
f = sinc
|
||||
g = odefun(lambda x, y: [(cos(x)-y[0])/x], 1, [f(1)], tol=0.01, degree=5)
|
||||
assert abs(f(100) - g(100)[0])/f(100) < 0.01
|
||||
|
||||
def test_odefun_harmonic():
|
||||
mp.dps = 15
|
||||
# Harmonic oscillator
|
||||
f = odefun(lambda x, y: [-y[1], y[0]], 0, [1, 0])
|
||||
for x in [0, 1, 2.5, 8, 3.7]: # we go back to 3.7 to check caching
|
||||
c, s = f(x)
|
||||
assert c.ae(cos(x))
|
||||
assert s.ae(sin(x))
|
||||
@@ -0,0 +1,27 @@
|
||||
import os
|
||||
import tempfile
|
||||
import pickle
|
||||
|
||||
from mpmath import *
|
||||
|
||||
def pickler(obj):
|
||||
fn = tempfile.mktemp()
|
||||
|
||||
f = open(fn, 'wb')
|
||||
pickle.dump(obj, f)
|
||||
f.close()
|
||||
|
||||
f = open(fn, 'rb')
|
||||
obj2 = pickle.load(f)
|
||||
f.close()
|
||||
os.remove(fn)
|
||||
|
||||
return obj2
|
||||
|
||||
def test_pickle():
|
||||
|
||||
obj = mpf('0.5')
|
||||
assert obj == pickler(obj)
|
||||
|
||||
obj = mpc('0.5','0.2')
|
||||
assert obj == pickler(obj)
|
||||
@@ -0,0 +1,156 @@
|
||||
from mpmath import *
|
||||
from mpmath.libmp import *
|
||||
|
||||
import random
|
||||
|
||||
def test_fractional_pow():
|
||||
mp.dps = 15
|
||||
assert mpf(16) ** 2.5 == 1024
|
||||
assert mpf(64) ** 0.5 == 8
|
||||
assert mpf(64) ** -0.5 == 0.125
|
||||
assert mpf(16) ** -2.5 == 0.0009765625
|
||||
assert (mpf(10) ** 0.5).ae(3.1622776601683791)
|
||||
assert (mpf(10) ** 2.5).ae(316.2277660168379)
|
||||
assert (mpf(10) ** -0.5).ae(0.31622776601683794)
|
||||
assert (mpf(10) ** -2.5).ae(0.0031622776601683794)
|
||||
assert (mpf(10) ** 0.3).ae(1.9952623149688795)
|
||||
assert (mpf(10) ** -0.3).ae(0.50118723362727224)
|
||||
|
||||
def test_pow_integer_direction():
|
||||
"""
|
||||
Test that inexact integer powers are rounded in the right
|
||||
direction.
|
||||
"""
|
||||
random.seed(1234)
|
||||
for prec in [10, 53, 200]:
|
||||
for i in range(50):
|
||||
a = random.randint(1<<(prec-1), 1<<prec)
|
||||
b = random.randint(2, 100)
|
||||
ab = a**b
|
||||
# note: could actually be exact, but that's very unlikely!
|
||||
assert to_int(mpf_pow(from_int(a), from_int(b), prec, round_down)) < ab
|
||||
assert to_int(mpf_pow(from_int(a), from_int(b), prec, round_up)) > ab
|
||||
|
||||
|
||||
def test_pow_epsilon_rounding():
|
||||
"""
|
||||
Stress test directed rounding for powers with integer exponents.
|
||||
Basically, we look at the following cases:
|
||||
|
||||
>>> 1.0001 ** -5 # doctest: +SKIP
|
||||
0.99950014996500702
|
||||
>>> 0.9999 ** -5 # doctest: +SKIP
|
||||
1.000500150035007
|
||||
>>> (-1.0001) ** -5 # doctest: +SKIP
|
||||
-0.99950014996500702
|
||||
>>> (-0.9999) ** -5 # doctest: +SKIP
|
||||
-1.000500150035007
|
||||
|
||||
>>> 1.0001 ** -6 # doctest: +SKIP
|
||||
0.99940020994401269
|
||||
>>> 0.9999 ** -6 # doctest: +SKIP
|
||||
1.0006002100560125
|
||||
>>> (-1.0001) ** -6 # doctest: +SKIP
|
||||
0.99940020994401269
|
||||
>>> (-0.9999) ** -6 # doctest: +SKIP
|
||||
1.0006002100560125
|
||||
|
||||
etc.
|
||||
|
||||
We run the tests with values a very small epsilon away from 1:
|
||||
small enough that the result is indistinguishable from 1 when
|
||||
rounded to nearest at the output precision. We check that the
|
||||
result is not erroneously rounded to 1 in cases where the
|
||||
rounding should be done strictly away from 1.
|
||||
"""
|
||||
|
||||
def powr(x, n, r):
|
||||
return make_mpf(mpf_pow_int(x._mpf_, n, mp.prec, r))
|
||||
|
||||
for (inprec, outprec) in [(100, 20), (5000, 3000)]:
|
||||
|
||||
mp.prec = inprec
|
||||
|
||||
pos10001 = mpf(1) + mpf(2)**(-inprec+5)
|
||||
pos09999 = mpf(1) - mpf(2)**(-inprec+5)
|
||||
neg10001 = -pos10001
|
||||
neg09999 = -pos09999
|
||||
|
||||
mp.prec = outprec
|
||||
r = round_up
|
||||
assert powr(pos10001, 5, r) > 1
|
||||
assert powr(pos09999, 5, r) == 1
|
||||
assert powr(neg10001, 5, r) < -1
|
||||
assert powr(neg09999, 5, r) == -1
|
||||
assert powr(pos10001, 6, r) > 1
|
||||
assert powr(pos09999, 6, r) == 1
|
||||
assert powr(neg10001, 6, r) > 1
|
||||
assert powr(neg09999, 6, r) == 1
|
||||
|
||||
assert powr(pos10001, -5, r) == 1
|
||||
assert powr(pos09999, -5, r) > 1
|
||||
assert powr(neg10001, -5, r) == -1
|
||||
assert powr(neg09999, -5, r) < -1
|
||||
assert powr(pos10001, -6, r) == 1
|
||||
assert powr(pos09999, -6, r) > 1
|
||||
assert powr(neg10001, -6, r) == 1
|
||||
assert powr(neg09999, -6, r) > 1
|
||||
|
||||
r = round_down
|
||||
assert powr(pos10001, 5, r) == 1
|
||||
assert powr(pos09999, 5, r) < 1
|
||||
assert powr(neg10001, 5, r) == -1
|
||||
assert powr(neg09999, 5, r) > -1
|
||||
assert powr(pos10001, 6, r) == 1
|
||||
assert powr(pos09999, 6, r) < 1
|
||||
assert powr(neg10001, 6, r) == 1
|
||||
assert powr(neg09999, 6, r) < 1
|
||||
|
||||
assert powr(pos10001, -5, r) < 1
|
||||
assert powr(pos09999, -5, r) == 1
|
||||
assert powr(neg10001, -5, r) > -1
|
||||
assert powr(neg09999, -5, r) == -1
|
||||
assert powr(pos10001, -6, r) < 1
|
||||
assert powr(pos09999, -6, r) == 1
|
||||
assert powr(neg10001, -6, r) < 1
|
||||
assert powr(neg09999, -6, r) == 1
|
||||
|
||||
r = round_ceiling
|
||||
assert powr(pos10001, 5, r) > 1
|
||||
assert powr(pos09999, 5, r) == 1
|
||||
assert powr(neg10001, 5, r) == -1
|
||||
assert powr(neg09999, 5, r) > -1
|
||||
assert powr(pos10001, 6, r) > 1
|
||||
assert powr(pos09999, 6, r) == 1
|
||||
assert powr(neg10001, 6, r) > 1
|
||||
assert powr(neg09999, 6, r) == 1
|
||||
|
||||
assert powr(pos10001, -5, r) == 1
|
||||
assert powr(pos09999, -5, r) > 1
|
||||
assert powr(neg10001, -5, r) > -1
|
||||
assert powr(neg09999, -5, r) == -1
|
||||
assert powr(pos10001, -6, r) == 1
|
||||
assert powr(pos09999, -6, r) > 1
|
||||
assert powr(neg10001, -6, r) == 1
|
||||
assert powr(neg09999, -6, r) > 1
|
||||
|
||||
r = round_floor
|
||||
assert powr(pos10001, 5, r) == 1
|
||||
assert powr(pos09999, 5, r) < 1
|
||||
assert powr(neg10001, 5, r) < -1
|
||||
assert powr(neg09999, 5, r) == -1
|
||||
assert powr(pos10001, 6, r) == 1
|
||||
assert powr(pos09999, 6, r) < 1
|
||||
assert powr(neg10001, 6, r) == 1
|
||||
assert powr(neg09999, 6, r) < 1
|
||||
|
||||
assert powr(pos10001, -5, r) < 1
|
||||
assert powr(pos09999, -5, r) == 1
|
||||
assert powr(neg10001, -5, r) == -1
|
||||
assert powr(neg09999, -5, r) < -1
|
||||
assert powr(pos10001, -6, r) < 1
|
||||
assert powr(pos09999, -6, r) == 1
|
||||
assert powr(neg10001, -6, r) < 1
|
||||
assert powr(neg09999, -6, r) == 1
|
||||
|
||||
mp.dps = 15
|
||||
@@ -0,0 +1,95 @@
|
||||
import pytest
|
||||
from mpmath import *
|
||||
|
||||
def ae(a, b):
|
||||
return abs(a-b) < 10**(-mp.dps+5)
|
||||
|
||||
def test_basic_integrals():
|
||||
for prec in [15, 30, 100]:
|
||||
mp.dps = prec
|
||||
assert ae(quadts(lambda x: x**3 - 3*x**2, [-2, 4]), -12)
|
||||
assert ae(quadgl(lambda x: x**3 - 3*x**2, [-2, 4]), -12)
|
||||
assert ae(quadts(sin, [0, pi]), 2)
|
||||
assert ae(quadts(sin, [0, 2*pi]), 0)
|
||||
assert ae(quadts(exp, [-inf, -1]), 1/e)
|
||||
assert ae(quadts(lambda x: exp(-x), [0, inf]), 1)
|
||||
assert ae(quadts(lambda x: exp(-x*x), [-inf, inf]), sqrt(pi))
|
||||
assert ae(quadts(lambda x: 1/(1+x*x), [-1, 1]), pi/2)
|
||||
assert ae(quadts(lambda x: 1/(1+x*x), [-inf, inf]), pi)
|
||||
assert ae(quadts(lambda x: 2*sqrt(1-x*x), [-1, 1]), pi)
|
||||
mp.dps = 15
|
||||
|
||||
def test_multiple_intervals():
|
||||
y,err = quad(lambda x: sign(x), [-0.5, 0.9, 1], maxdegree=2, error=True)
|
||||
assert abs(y-0.5) < 2*err
|
||||
|
||||
def test_quad_symmetry():
|
||||
assert quadts(sin, [-1, 1]) == 0
|
||||
assert quadgl(sin, [-1, 1]) == 0
|
||||
|
||||
def test_quad_infinite_mirror():
|
||||
# Check mirrored infinite interval
|
||||
assert ae(quad(lambda x: exp(-x*x), [inf,-inf]), -sqrt(pi))
|
||||
assert ae(quad(lambda x: exp(x), [0,-inf]), -1)
|
||||
|
||||
def test_quadgl_linear():
|
||||
assert quadgl(lambda x: x, [0, 1], maxdegree=1).ae(0.5)
|
||||
|
||||
def test_complex_integration():
|
||||
assert quadts(lambda x: x, [0, 1+j]).ae(j)
|
||||
|
||||
def test_quadosc():
|
||||
mp.dps = 15
|
||||
assert quadosc(lambda x: sin(x)/x, [0, inf], period=2*pi).ae(pi/2)
|
||||
|
||||
# Double integrals
|
||||
def test_double_trivial():
|
||||
assert ae(quadts(lambda x, y: x, [0, 1], [0, 1]), 0.5)
|
||||
assert ae(quadts(lambda x, y: x, [-1, 1], [-1, 1]), 0.0)
|
||||
|
||||
def test_double_1():
|
||||
assert ae(quadts(lambda x, y: cos(x+y/2), [-pi/2, pi/2], [0, pi]), 4)
|
||||
|
||||
def test_double_2():
|
||||
assert ae(quadts(lambda x, y: (x-1)/((1-x*y)*log(x*y)), [0, 1], [0, 1]), euler)
|
||||
|
||||
def test_double_3():
|
||||
assert ae(quadts(lambda x, y: 1/sqrt(1+x*x+y*y), [-1, 1], [-1, 1]), 4*log(2+sqrt(3))-2*pi/3)
|
||||
|
||||
def test_double_4():
|
||||
assert ae(quadts(lambda x, y: 1/(1-x*x * y*y), [0, 1], [0, 1]), pi**2 / 8)
|
||||
|
||||
def test_double_5():
|
||||
assert ae(quadts(lambda x, y: 1/(1-x*y), [0, 1], [0, 1]), pi**2 / 6)
|
||||
|
||||
def test_double_6():
|
||||
assert ae(quadts(lambda x, y: exp(-(x+y)), [0, inf], [0, inf]), 1)
|
||||
|
||||
def test_double_7():
|
||||
assert ae(quadts(lambda x, y: exp(-x*x-y*y), [-inf, inf], [-inf, inf]), pi)
|
||||
|
||||
|
||||
# Test integrals from "Experimentation in Mathematics" by Borwein,
|
||||
# Bailey & Girgensohn
|
||||
def test_expmath_integrals():
|
||||
for prec in [15, 30, 50]:
|
||||
mp.dps = prec
|
||||
assert ae(quadts(lambda x: x/sinh(x), [0, inf]), pi**2 / 4)
|
||||
assert ae(quadts(lambda x: log(x)**2 / (1+x**2), [0, inf]), pi**3 / 8)
|
||||
assert ae(quadts(lambda x: (1+x**2)/(1+x**4), [0, inf]), pi/sqrt(2))
|
||||
assert ae(quadts(lambda x: log(x)/cosh(x)**2, [0, inf]), log(pi)-2*log(2)-euler)
|
||||
assert ae(quadts(lambda x: log(1+x**3)/(1-x+x**2), [0, inf]), 2*pi*log(3)/sqrt(3))
|
||||
assert ae(quadts(lambda x: log(x)**2 / (x**2+x+1), [0, 1]), 8*pi**3 / (81*sqrt(3)))
|
||||
assert ae(quadts(lambda x: log(cos(x))**2, [0, pi/2]), pi/2 * (log(2)**2+pi**2/12))
|
||||
assert ae(quadts(lambda x: x**2 / sin(x)**2, [0, pi/2]), pi*log(2))
|
||||
assert ae(quadts(lambda x: x**2/sqrt(exp(x)-1), [0, inf]), 4*pi*(log(2)**2 + pi**2/12))
|
||||
assert ae(quadts(lambda x: x*exp(-x)*sqrt(1-exp(-2*x)), [0, inf]), pi*(1+2*log(2))/8)
|
||||
mp.dps = 15
|
||||
|
||||
# Do not reach full accuracy
|
||||
@pytest.mark.xfail
|
||||
def test_expmath_fail():
|
||||
assert ae(quadts(lambda x: sqrt(tan(x)), [0, pi/2]), pi*sqrt(2)/2)
|
||||
assert ae(quadts(lambda x: atan(x)/(x*sqrt(1-x**2)), [0, 1]), pi*log(1+sqrt(2))/2)
|
||||
assert ae(quadts(lambda x: log(1+x**2)/x**2, [0, 1]), pi/2-log(2))
|
||||
assert ae(quadts(lambda x: x**2/((1+x**4)*sqrt(1-x**4)), [0, 1]), pi/8)
|
||||
@@ -0,0 +1,91 @@
|
||||
import pytest
|
||||
from mpmath import *
|
||||
from mpmath.calculus.optimization import Secant, Muller, Bisection, Illinois, \
|
||||
Pegasus, Anderson, Ridder, ANewton, Newton, MNewton, MDNewton
|
||||
|
||||
def test_findroot():
|
||||
# old tests, assuming secant
|
||||
mp.dps = 15
|
||||
assert findroot(lambda x: 4*x-3, mpf(5)).ae(0.75)
|
||||
assert findroot(sin, mpf(3)).ae(pi)
|
||||
assert findroot(sin, (mpf(3), mpf(3.14))).ae(pi)
|
||||
assert findroot(lambda x: x*x+1, mpc(2+2j)).ae(1j)
|
||||
# test all solvers with 1 starting point
|
||||
f = lambda x: cos(x)
|
||||
for solver in [Newton, Secant, MNewton, Muller, ANewton]:
|
||||
x = findroot(f, 2., solver=solver)
|
||||
assert abs(f(x)) < eps
|
||||
# test all solvers with interval of 2 points
|
||||
for solver in [Secant, Muller, Bisection, Illinois, Pegasus, Anderson,
|
||||
Ridder]:
|
||||
x = findroot(f, (1., 2.), solver=solver)
|
||||
assert abs(f(x)) < eps
|
||||
# test types
|
||||
f = lambda x: (x - 2)**2
|
||||
|
||||
assert isinstance(findroot(f, 1, tol=1e-10), mpf)
|
||||
assert isinstance(iv.findroot(f, 1., tol=1e-10), iv.mpf)
|
||||
assert isinstance(fp.findroot(f, 1, tol=1e-10), float)
|
||||
assert isinstance(fp.findroot(f, 1+0j, tol=1e-10), complex)
|
||||
|
||||
# issue 401
|
||||
with pytest.raises(ValueError):
|
||||
with workprec(2):
|
||||
findroot(lambda x: x**2 - 4456178*x + 60372201703370,
|
||||
mpc(real='5.278e+13', imag='-5.278e+13'))
|
||||
|
||||
# issue 192
|
||||
with pytest.raises(ValueError):
|
||||
findroot(lambda x: -1, 0)
|
||||
|
||||
# issue 387
|
||||
with pytest.raises(ValueError):
|
||||
findroot(lambda p: (1 - p)**30 - 1, 0.9)
|
||||
|
||||
def test_bisection():
|
||||
# issue 273
|
||||
assert findroot(lambda x: x**2-1,(0,2),solver='bisect') == 1
|
||||
|
||||
def test_mnewton():
|
||||
f = lambda x: polyval([1,3,3,1],x)
|
||||
x = findroot(f, -0.9, solver='mnewton')
|
||||
assert abs(f(x)) < eps
|
||||
|
||||
def test_anewton():
|
||||
f = lambda x: (x - 2)**100
|
||||
x = findroot(f, 1., solver=ANewton)
|
||||
assert abs(f(x)) < eps
|
||||
|
||||
def test_muller():
|
||||
f = lambda x: (2 + x)**3 + 2
|
||||
x = findroot(f, 1., solver=Muller)
|
||||
assert abs(f(x)) < eps
|
||||
|
||||
def test_multiplicity():
|
||||
for i in range(1, 5):
|
||||
assert multiplicity(lambda x: (x - 1)**i, 1) == i
|
||||
assert multiplicity(lambda x: x**2, 1) == 0
|
||||
|
||||
def test_multidimensional():
|
||||
def f(*x):
|
||||
return [3*x[0]**2-2*x[1]**2-1, x[0]**2-2*x[0]+x[1]**2+2*x[1]-8]
|
||||
assert mnorm(jacobian(f, (1,-2)) - matrix([[6,8],[0,-2]]),1) < 1.e-7
|
||||
for x, error in MDNewton(mp, f, (1,-2), verbose=0,
|
||||
norm=lambda x: norm(x, inf)):
|
||||
pass
|
||||
assert norm(f(*x), 2) < 1e-14
|
||||
# The Chinese mathematician Zhu Shijie was the very first to solve this
|
||||
# nonlinear system 700 years ago
|
||||
f1 = lambda x, y: -x + 2*y
|
||||
f2 = lambda x, y: (x**2 + x*(y**2 - 2) - 4*y) / (x + 4)
|
||||
f3 = lambda x, y: sqrt(x**2 + y**2)
|
||||
def f(x, y):
|
||||
f1x = f1(x, y)
|
||||
return (f2(x, y) - f1x, f3(x, y) - f1x)
|
||||
x = findroot(f, (10, 10))
|
||||
assert [int(round(i)) for i in x] == [3, 4]
|
||||
|
||||
def test_trivial():
|
||||
assert findroot(lambda x: 0, 1) == 1
|
||||
assert findroot(lambda x: x, 0) == 0
|
||||
#assert findroot(lambda x, y: x + y, (1, -1)) == (1, -1)
|
||||
@@ -0,0 +1,113 @@
|
||||
from mpmath import *
|
||||
|
||||
def test_special():
|
||||
assert inf == inf
|
||||
assert inf != -inf
|
||||
assert -inf == -inf
|
||||
assert inf != nan
|
||||
assert nan != nan
|
||||
assert isnan(nan)
|
||||
assert --inf == inf
|
||||
assert abs(inf) == inf
|
||||
assert abs(-inf) == inf
|
||||
assert abs(nan) != abs(nan)
|
||||
|
||||
assert isnan(inf - inf)
|
||||
assert isnan(inf + (-inf))
|
||||
assert isnan(-inf - (-inf))
|
||||
|
||||
assert isnan(inf + nan)
|
||||
assert isnan(-inf + nan)
|
||||
|
||||
assert mpf(2) + inf == inf
|
||||
assert 2 + inf == inf
|
||||
assert mpf(2) - inf == -inf
|
||||
assert 2 - inf == -inf
|
||||
|
||||
assert inf > 3
|
||||
assert 3 < inf
|
||||
assert 3 > -inf
|
||||
assert -inf < 3
|
||||
assert inf > mpf(3)
|
||||
assert mpf(3) < inf
|
||||
assert mpf(3) > -inf
|
||||
assert -inf < mpf(3)
|
||||
|
||||
assert not (nan < 3)
|
||||
assert not (nan > 3)
|
||||
|
||||
assert isnan(inf * 0)
|
||||
assert isnan(-inf * 0)
|
||||
assert inf * 3 == inf
|
||||
assert inf * -3 == -inf
|
||||
assert -inf * 3 == -inf
|
||||
assert -inf * -3 == inf
|
||||
assert inf * inf == inf
|
||||
assert -inf * -inf == inf
|
||||
|
||||
assert isnan(nan / 3)
|
||||
assert inf / -3 == -inf
|
||||
assert inf / 3 == inf
|
||||
assert 3 / inf == 0
|
||||
assert -3 / inf == 0
|
||||
assert 0 / inf == 0
|
||||
assert isnan(inf / inf)
|
||||
assert isnan(inf / -inf)
|
||||
assert isnan(inf / nan)
|
||||
|
||||
assert mpf('inf') == mpf('+inf') == inf
|
||||
assert mpf('-inf') == -inf
|
||||
assert isnan(mpf('nan'))
|
||||
|
||||
assert isinf(inf)
|
||||
assert isinf(-inf)
|
||||
assert not isinf(mpf(0))
|
||||
assert not isinf(nan)
|
||||
|
||||
def test_special_powers():
|
||||
assert inf**3 == inf
|
||||
assert isnan(inf**0)
|
||||
assert inf**-3 == 0
|
||||
assert (-inf)**2 == inf
|
||||
assert (-inf)**3 == -inf
|
||||
assert isnan((-inf)**0)
|
||||
assert (-inf)**-2 == 0
|
||||
assert (-inf)**-3 == 0
|
||||
assert isnan(nan**5)
|
||||
assert isnan(nan**0)
|
||||
|
||||
def test_functions_special():
|
||||
assert exp(inf) == inf
|
||||
assert exp(-inf) == 0
|
||||
assert isnan(exp(nan))
|
||||
assert log(inf) == inf
|
||||
assert isnan(log(nan))
|
||||
assert isnan(sin(inf))
|
||||
assert isnan(sin(nan))
|
||||
assert atan(inf).ae(pi/2)
|
||||
assert atan(-inf).ae(-pi/2)
|
||||
assert isnan(sqrt(nan))
|
||||
assert sqrt(inf) == inf
|
||||
|
||||
def test_convert_special():
|
||||
float_inf = 1e300 * 1e300
|
||||
float_ninf = -float_inf
|
||||
float_nan = float_inf/float_ninf
|
||||
assert mpf(3) * float_inf == inf
|
||||
assert mpf(3) * float_ninf == -inf
|
||||
assert isnan(mpf(3) * float_nan)
|
||||
assert not (mpf(3) < float_nan)
|
||||
assert not (mpf(3) > float_nan)
|
||||
assert not (mpf(3) <= float_nan)
|
||||
assert not (mpf(3) >= float_nan)
|
||||
assert float(mpf('1e1000')) == float_inf
|
||||
assert float(mpf('-1e1000')) == float_ninf
|
||||
assert float(mpf('1e100000000000000000')) == float_inf
|
||||
assert float(mpf('-1e100000000000000000')) == float_ninf
|
||||
assert float(mpf('1e-100000000000000000')) == 0.0
|
||||
|
||||
def test_div_bug():
|
||||
assert isnan(nan/1)
|
||||
assert isnan(nan/2)
|
||||
assert inf/2 == inf
|
||||
assert (-inf)/2 == -inf
|
||||
@@ -0,0 +1,14 @@
|
||||
from mpmath import nstr, matrix, inf
|
||||
|
||||
def test_nstr():
|
||||
m = matrix([[0.75, 0.190940654, -0.0299195971],
|
||||
[0.190940654, 0.65625, 0.205663228],
|
||||
[-0.0299195971, 0.205663228, 0.64453125e-20]])
|
||||
assert nstr(m, 4, min_fixed=-inf) == \
|
||||
'''[ 0.75 0.1909 -0.02992]
|
||||
[ 0.1909 0.6563 0.2057]
|
||||
[-0.02992 0.2057 0.000000000000000000006445]'''
|
||||
assert nstr(m, 4) == \
|
||||
'''[ 0.75 0.1909 -0.02992]
|
||||
[ 0.1909 0.6563 0.2057]
|
||||
[-0.02992 0.2057 6.445e-21]'''
|
||||
@@ -0,0 +1,53 @@
|
||||
from mpmath import *
|
||||
|
||||
def test_sumem():
|
||||
mp.dps = 15
|
||||
assert sumem(lambda k: 1/k**2.5, [50, 100]).ae(0.0012524505324784962)
|
||||
assert sumem(lambda k: k**4 + 3*k + 1, [10, 100]).ae(2050333103)
|
||||
|
||||
def test_nsum():
|
||||
mp.dps = 15
|
||||
assert nsum(lambda x: x**2, [1, 3]) == 14
|
||||
assert nsum(lambda k: 1/factorial(k), [0, inf]).ae(e)
|
||||
assert nsum(lambda k: (-1)**(k+1) / k, [1, inf]).ae(log(2))
|
||||
assert nsum(lambda k: (-1)**(k+1) / k**2, [1, inf]).ae(pi**2 / 12)
|
||||
assert nsum(lambda k: (-1)**k / log(k), [2, inf]).ae(0.9242998972229388)
|
||||
assert nsum(lambda k: 1/k**2, [1, inf]).ae(pi**2 / 6)
|
||||
assert nsum(lambda k: 2**k/fac(k), [0, inf]).ae(exp(2))
|
||||
assert nsum(lambda k: 1/k**2, [4, inf], method='e').ae(0.2838229557371153)
|
||||
assert abs(fp.nsum(lambda k: 1/k**4, [1, fp.inf]) - 1.082323233711138) < 1e-5
|
||||
assert abs(fp.nsum(lambda k: 1/k**4, [1, fp.inf], method='e') - 1.082323233711138) < 1e-4
|
||||
|
||||
def test_nprod():
|
||||
mp.dps = 15
|
||||
assert nprod(lambda k: exp(1/k**2), [1,inf], method='r').ae(exp(pi**2/6))
|
||||
assert nprod(lambda x: x**2, [1, 3]) == 36
|
||||
|
||||
def test_fsum():
|
||||
mp.dps = 15
|
||||
assert fsum([]) == 0
|
||||
assert fsum([-4]) == -4
|
||||
assert fsum([2,3]) == 5
|
||||
assert fsum([1e-100,1]) == 1
|
||||
assert fsum([1,1e-100]) == 1
|
||||
assert fsum([1e100,1]) == 1e100
|
||||
assert fsum([1,1e100]) == 1e100
|
||||
assert fsum([1e-100,0]) == 1e-100
|
||||
assert fsum([1e-100,1e100,1e-100]) == 1e100
|
||||
assert fsum([2,1+1j,1]) == 4+1j
|
||||
assert fsum([2,inf,3]) == inf
|
||||
assert fsum([2,-1], absolute=1) == 3
|
||||
assert fsum([2,-1], squared=1) == 5
|
||||
assert fsum([1,1+j], squared=1) == 1+2j
|
||||
assert fsum([1,3+4j], absolute=1) == 6
|
||||
assert fsum([1,2+3j], absolute=1, squared=1) == 14
|
||||
assert isnan(fsum([inf,-inf]))
|
||||
assert fsum([inf,-inf], absolute=1) == inf
|
||||
assert fsum([inf,-inf], squared=1) == inf
|
||||
assert fsum([inf,-inf], absolute=1, squared=1) == inf
|
||||
assert iv.fsum([1,mpi(2,3)]) == mpi(3,4)
|
||||
|
||||
def test_fprod():
|
||||
mp.dps = 15
|
||||
assert fprod([]) == 1
|
||||
assert fprod([2,3]) == 6
|
||||
@@ -0,0 +1,136 @@
|
||||
from mpmath import *
|
||||
from mpmath.libmp import *
|
||||
|
||||
def test_trig_misc_hard():
|
||||
mp.prec = 53
|
||||
# Worst-case input for an IEEE double, from a paper by Kahan
|
||||
x = ldexp(6381956970095103,797)
|
||||
assert cos(x) == mpf('-4.6871659242546277e-19')
|
||||
assert sin(x) == 1
|
||||
|
||||
mp.prec = 150
|
||||
a = mpf(10**50)
|
||||
mp.prec = 53
|
||||
assert sin(a).ae(-0.7896724934293100827)
|
||||
assert cos(a).ae(-0.6135286082336635622)
|
||||
|
||||
# Check relative accuracy close to x = zero
|
||||
assert sin(1e-100) == 1e-100 # when rounding to nearest
|
||||
assert sin(1e-6).ae(9.999999999998333e-007, rel_eps=2e-15, abs_eps=0)
|
||||
assert sin(1e-6j).ae(1.0000000000001666e-006j, rel_eps=2e-15, abs_eps=0)
|
||||
assert sin(-1e-6j).ae(-1.0000000000001666e-006j, rel_eps=2e-15, abs_eps=0)
|
||||
assert cos(1e-100) == 1
|
||||
assert cos(1e-6).ae(0.9999999999995)
|
||||
assert cos(-1e-6j).ae(1.0000000000005)
|
||||
assert tan(1e-100) == 1e-100
|
||||
assert tan(1e-6).ae(1.0000000000003335e-006, rel_eps=2e-15, abs_eps=0)
|
||||
assert tan(1e-6j).ae(9.9999999999966644e-007j, rel_eps=2e-15, abs_eps=0)
|
||||
assert tan(-1e-6j).ae(-9.9999999999966644e-007j, rel_eps=2e-15, abs_eps=0)
|
||||
|
||||
def test_trig_near_zero():
|
||||
mp.dps = 15
|
||||
|
||||
for r in [round_nearest, round_down, round_up, round_floor, round_ceiling]:
|
||||
assert sin(0, rounding=r) == 0
|
||||
assert cos(0, rounding=r) == 1
|
||||
|
||||
a = mpf('1e-100')
|
||||
b = mpf('-1e-100')
|
||||
|
||||
assert sin(a, rounding=round_nearest) == a
|
||||
assert sin(a, rounding=round_down) < a
|
||||
assert sin(a, rounding=round_floor) < a
|
||||
assert sin(a, rounding=round_up) >= a
|
||||
assert sin(a, rounding=round_ceiling) >= a
|
||||
assert sin(b, rounding=round_nearest) == b
|
||||
assert sin(b, rounding=round_down) > b
|
||||
assert sin(b, rounding=round_floor) <= b
|
||||
assert sin(b, rounding=round_up) <= b
|
||||
assert sin(b, rounding=round_ceiling) > b
|
||||
|
||||
assert cos(a, rounding=round_nearest) == 1
|
||||
assert cos(a, rounding=round_down) < 1
|
||||
assert cos(a, rounding=round_floor) < 1
|
||||
assert cos(a, rounding=round_up) == 1
|
||||
assert cos(a, rounding=round_ceiling) == 1
|
||||
assert cos(b, rounding=round_nearest) == 1
|
||||
assert cos(b, rounding=round_down) < 1
|
||||
assert cos(b, rounding=round_floor) < 1
|
||||
assert cos(b, rounding=round_up) == 1
|
||||
assert cos(b, rounding=round_ceiling) == 1
|
||||
|
||||
|
||||
def test_trig_near_n_pi():
|
||||
|
||||
mp.dps = 15
|
||||
a = [n*pi for n in [1, 2, 6, 11, 100, 1001, 10000, 100001]]
|
||||
mp.dps = 135
|
||||
a.append(10**100 * pi)
|
||||
mp.dps = 15
|
||||
|
||||
assert sin(a[0]) == mpf('1.2246467991473531772e-16')
|
||||
assert sin(a[1]) == mpf('-2.4492935982947063545e-16')
|
||||
assert sin(a[2]) == mpf('-7.3478807948841190634e-16')
|
||||
assert sin(a[3]) == mpf('4.8998251578625894243e-15')
|
||||
assert sin(a[4]) == mpf('1.9643867237284719452e-15')
|
||||
assert sin(a[5]) == mpf('-8.8632615209684813458e-15')
|
||||
assert sin(a[6]) == mpf('-4.8568235395684898392e-13')
|
||||
assert sin(a[7]) == mpf('3.9087342299491231029e-11')
|
||||
assert sin(a[8]) == mpf('-1.369235466754566993528e-36')
|
||||
|
||||
r = round_nearest
|
||||
assert cos(a[0], rounding=r) == -1
|
||||
assert cos(a[1], rounding=r) == 1
|
||||
assert cos(a[2], rounding=r) == 1
|
||||
assert cos(a[3], rounding=r) == -1
|
||||
assert cos(a[4], rounding=r) == 1
|
||||
assert cos(a[5], rounding=r) == -1
|
||||
assert cos(a[6], rounding=r) == 1
|
||||
assert cos(a[7], rounding=r) == -1
|
||||
assert cos(a[8], rounding=r) == 1
|
||||
|
||||
r = round_up
|
||||
assert cos(a[0], rounding=r) == -1
|
||||
assert cos(a[1], rounding=r) == 1
|
||||
assert cos(a[2], rounding=r) == 1
|
||||
assert cos(a[3], rounding=r) == -1
|
||||
assert cos(a[4], rounding=r) == 1
|
||||
assert cos(a[5], rounding=r) == -1
|
||||
assert cos(a[6], rounding=r) == 1
|
||||
assert cos(a[7], rounding=r) == -1
|
||||
assert cos(a[8], rounding=r) == 1
|
||||
|
||||
r = round_down
|
||||
assert cos(a[0], rounding=r) > -1
|
||||
assert cos(a[1], rounding=r) < 1
|
||||
assert cos(a[2], rounding=r) < 1
|
||||
assert cos(a[3], rounding=r) > -1
|
||||
assert cos(a[4], rounding=r) < 1
|
||||
assert cos(a[5], rounding=r) > -1
|
||||
assert cos(a[6], rounding=r) < 1
|
||||
assert cos(a[7], rounding=r) > -1
|
||||
assert cos(a[8], rounding=r) < 1
|
||||
|
||||
r = round_floor
|
||||
assert cos(a[0], rounding=r) == -1
|
||||
assert cos(a[1], rounding=r) < 1
|
||||
assert cos(a[2], rounding=r) < 1
|
||||
assert cos(a[3], rounding=r) == -1
|
||||
assert cos(a[4], rounding=r) < 1
|
||||
assert cos(a[5], rounding=r) == -1
|
||||
assert cos(a[6], rounding=r) < 1
|
||||
assert cos(a[7], rounding=r) == -1
|
||||
assert cos(a[8], rounding=r) < 1
|
||||
|
||||
r = round_ceiling
|
||||
assert cos(a[0], rounding=r) > -1
|
||||
assert cos(a[1], rounding=r) == 1
|
||||
assert cos(a[2], rounding=r) == 1
|
||||
assert cos(a[3], rounding=r) > -1
|
||||
assert cos(a[4], rounding=r) == 1
|
||||
assert cos(a[5], rounding=r) > -1
|
||||
assert cos(a[6], rounding=r) == 1
|
||||
assert cos(a[7], rounding=r) > -1
|
||||
assert cos(a[8], rounding=r) == 1
|
||||
|
||||
mp.dps = 15
|
||||
@@ -0,0 +1,32 @@
|
||||
"""
|
||||
Limited tests of the visualization module. Right now it just makes
|
||||
sure that passing custom Axes works.
|
||||
|
||||
"""
|
||||
|
||||
from mpmath import mp, fp
|
||||
|
||||
def test_axes():
|
||||
try:
|
||||
import matplotlib
|
||||
version = matplotlib.__version__.split("-")[0]
|
||||
version = version.split(".")[:2]
|
||||
if [int(_) for _ in version] < [0,99]:
|
||||
raise ImportError
|
||||
import pylab
|
||||
except ImportError:
|
||||
print("\nSkipping test (pylab not available or too old version)\n")
|
||||
return
|
||||
fig = pylab.figure()
|
||||
axes = fig.add_subplot(111)
|
||||
for ctx in [mp, fp]:
|
||||
ctx.plot(lambda x: x**2, [0, 3], axes=axes)
|
||||
assert axes.get_xlabel() == 'x'
|
||||
assert axes.get_ylabel() == 'f(x)'
|
||||
|
||||
fig = pylab.figure()
|
||||
axes = fig.add_subplot(111)
|
||||
for ctx in [mp, fp]:
|
||||
ctx.cplot(lambda z: z, [-2, 2], [-10, 10], axes=axes)
|
||||
assert axes.get_xlabel() == 'Re(z)'
|
||||
assert axes.get_ylabel() == 'Im(z)'
|
||||
@@ -0,0 +1,224 @@
|
||||
"""
|
||||
Torture tests for asymptotics and high precision evaluation of
|
||||
special functions.
|
||||
|
||||
(Other torture tests may also be placed here.)
|
||||
|
||||
Running this file (gmpy recommended!) takes several CPU minutes.
|
||||
With Python 2.6+, multiprocessing is used automatically to run tests
|
||||
in parallel if many cores are available. (A single test may take between
|
||||
a second and several minutes; possibly more.)
|
||||
|
||||
The idea:
|
||||
|
||||
* We evaluate functions at positive, negative, imaginary, 45- and 135-degree
|
||||
complex values with magnitudes between 10^-20 to 10^20, at precisions between
|
||||
5 and 150 digits (we can go even higher for fast functions).
|
||||
|
||||
* Comparing the result from two different precision levels provides
|
||||
a strong consistency check (particularly for functions that use
|
||||
different algorithms at different precision levels).
|
||||
|
||||
* That the computation finishes at all (without failure), within reasonable
|
||||
time, provides a check that evaluation works at all: that the code runs,
|
||||
that it doesn't get stuck in an infinite loop, and that it doesn't use
|
||||
some extremely slowly algorithm where it could use a faster one.
|
||||
|
||||
TODO:
|
||||
|
||||
* Speed up those functions that take long to finish!
|
||||
* Generalize to test more cases; more options.
|
||||
* Implement a timeout mechanism.
|
||||
* Some functions are notably absent, including the following:
|
||||
* inverse trigonometric functions (some become inaccurate for complex arguments)
|
||||
* ci, si (not implemented properly for large complex arguments)
|
||||
* zeta functions (need to modify test not to try too large imaginary values)
|
||||
* and others...
|
||||
|
||||
"""
|
||||
|
||||
|
||||
import sys, os
|
||||
from timeit import default_timer as clock
|
||||
|
||||
if "-nogmpy" in sys.argv:
|
||||
sys.argv.remove('-nogmpy')
|
||||
os.environ['MPMATH_NOGMPY'] = 'Y'
|
||||
|
||||
filt = ''
|
||||
if not sys.argv[-1].endswith(".py"):
|
||||
filt = sys.argv[-1]
|
||||
|
||||
from mpmath import *
|
||||
from mpmath.libmp.backend import exec_
|
||||
|
||||
def test_asymp(f, maxdps=150, verbose=False, huge_range=False):
|
||||
dps = [5,15,25,50,90,150,500,1500,5000,10000]
|
||||
dps = [p for p in dps if p <= maxdps]
|
||||
def check(x,y,p,inpt):
|
||||
if abs(x-y)/abs(y) < workprec(20)(power)(10, -p+1):
|
||||
return
|
||||
print()
|
||||
print("Error!")
|
||||
print("Input:", inpt)
|
||||
print("dps =", p)
|
||||
print("Result 1:", x)
|
||||
print("Result 2:", y)
|
||||
print("Absolute error:", abs(x-y))
|
||||
print("Relative error:", abs(x-y)/abs(y))
|
||||
raise AssertionError
|
||||
exponents = range(-20,20)
|
||||
if huge_range:
|
||||
exponents += [-1000, -100, -50, 50, 100, 1000]
|
||||
for n in exponents:
|
||||
if verbose:
|
||||
sys.stdout.write(". ")
|
||||
mp.dps = 25
|
||||
xpos = mpf(10)**n / 1.1287
|
||||
xneg = -xpos
|
||||
ximag = xpos*j
|
||||
xcomplex1 = xpos*(1+j)
|
||||
xcomplex2 = xpos*(-1+j)
|
||||
for i in range(len(dps)):
|
||||
if verbose:
|
||||
print("Testing dps = %s" % dps[i])
|
||||
mp.dps = dps[i]
|
||||
new = f(xpos), f(xneg), f(ximag), f(xcomplex1), f(xcomplex2)
|
||||
if i != 0:
|
||||
p = dps[i-1]
|
||||
check(prev[0], new[0], p, xpos)
|
||||
check(prev[1], new[1], p, xneg)
|
||||
check(prev[2], new[2], p, ximag)
|
||||
check(prev[3], new[3], p, xcomplex1)
|
||||
check(prev[4], new[4], p, xcomplex2)
|
||||
prev = new
|
||||
if verbose:
|
||||
print()
|
||||
|
||||
a1, a2, a3, a4, a5 = 1.5, -2.25, 3.125, 4, 2
|
||||
|
||||
def test_bernoulli_huge():
|
||||
p, q = bernfrac(9000)
|
||||
assert p % 10**10 == 9636701091
|
||||
assert q == 4091851784687571609141381951327092757255270
|
||||
mp.dps = 15
|
||||
assert str(bernoulli(10**100)) == '-2.58183325604736e+987675256497386331227838638980680030172857347883537824464410652557820800494271520411283004120790908623'
|
||||
mp.dps = 50
|
||||
assert str(bernoulli(10**100)) == '-2.5818332560473632073252488656039475548106223822913e+987675256497386331227838638980680030172857347883537824464410652557820800494271520411283004120790908623'
|
||||
mp.dps = 15
|
||||
|
||||
cases = """\
|
||||
test_bernoulli_huge()
|
||||
test_asymp(lambda z: +pi, maxdps=10000)
|
||||
test_asymp(lambda z: +e, maxdps=10000)
|
||||
test_asymp(lambda z: +ln2, maxdps=10000)
|
||||
test_asymp(lambda z: +ln10, maxdps=10000)
|
||||
test_asymp(lambda z: +phi, maxdps=10000)
|
||||
test_asymp(lambda z: +catalan, maxdps=5000)
|
||||
test_asymp(lambda z: +euler, maxdps=5000)
|
||||
test_asymp(lambda z: +glaisher, maxdps=1000)
|
||||
test_asymp(lambda z: +khinchin, maxdps=1000)
|
||||
test_asymp(lambda z: +twinprime, maxdps=150)
|
||||
test_asymp(lambda z: stieltjes(2), maxdps=150)
|
||||
test_asymp(lambda z: +mertens, maxdps=150)
|
||||
test_asymp(lambda z: +apery, maxdps=5000)
|
||||
test_asymp(sqrt, maxdps=10000, huge_range=True)
|
||||
test_asymp(cbrt, maxdps=5000, huge_range=True)
|
||||
test_asymp(lambda z: root(z,4), maxdps=5000, huge_range=True)
|
||||
test_asymp(lambda z: root(z,-5), maxdps=5000, huge_range=True)
|
||||
test_asymp(exp, maxdps=5000, huge_range=True)
|
||||
test_asymp(expm1, maxdps=1500)
|
||||
test_asymp(ln, maxdps=5000, huge_range=True)
|
||||
test_asymp(cosh, maxdps=5000)
|
||||
test_asymp(sinh, maxdps=5000)
|
||||
test_asymp(tanh, maxdps=1500)
|
||||
test_asymp(sin, maxdps=5000, huge_range=True)
|
||||
test_asymp(cos, maxdps=5000, huge_range=True)
|
||||
test_asymp(tan, maxdps=1500)
|
||||
test_asymp(agm, maxdps=1500, huge_range=True)
|
||||
test_asymp(ellipk, maxdps=1500)
|
||||
test_asymp(ellipe, maxdps=1500)
|
||||
test_asymp(lambertw, huge_range=True)
|
||||
test_asymp(lambda z: lambertw(z,-1))
|
||||
test_asymp(lambda z: lambertw(z,1))
|
||||
test_asymp(lambda z: lambertw(z,4))
|
||||
test_asymp(gamma)
|
||||
test_asymp(loggamma) # huge_range=True ?
|
||||
test_asymp(ei)
|
||||
test_asymp(e1)
|
||||
test_asymp(li, huge_range=True)
|
||||
test_asymp(ci)
|
||||
test_asymp(si)
|
||||
test_asymp(chi)
|
||||
test_asymp(shi)
|
||||
test_asymp(erf)
|
||||
test_asymp(erfc)
|
||||
test_asymp(erfi)
|
||||
test_asymp(lambda z: besselj(2, z))
|
||||
test_asymp(lambda z: bessely(2, z))
|
||||
test_asymp(lambda z: besseli(2, z))
|
||||
test_asymp(lambda z: besselk(2, z))
|
||||
test_asymp(lambda z: besselj(-2.25, z))
|
||||
test_asymp(lambda z: bessely(-2.25, z))
|
||||
test_asymp(lambda z: besseli(-2.25, z))
|
||||
test_asymp(lambda z: besselk(-2.25, z))
|
||||
test_asymp(airyai)
|
||||
test_asymp(airybi)
|
||||
test_asymp(lambda z: hyp0f1(a1, z))
|
||||
test_asymp(lambda z: hyp1f1(a1, a2, z))
|
||||
test_asymp(lambda z: hyp1f2(a1, a2, a3, z))
|
||||
test_asymp(lambda z: hyp2f0(a1, a2, z))
|
||||
test_asymp(lambda z: hyperu(a1, a2, z))
|
||||
test_asymp(lambda z: hyp2f1(a1, a2, a3, z))
|
||||
test_asymp(lambda z: hyp2f2(a1, a2, a3, a4, z))
|
||||
test_asymp(lambda z: hyp2f3(a1, a2, a3, a4, a5, z))
|
||||
test_asymp(lambda z: coulombf(a1, a2, z))
|
||||
test_asymp(lambda z: coulombg(a1, a2, z))
|
||||
test_asymp(lambda z: polylog(2,z))
|
||||
test_asymp(lambda z: polylog(3,z))
|
||||
test_asymp(lambda z: polylog(-2,z))
|
||||
test_asymp(lambda z: expint(4, z))
|
||||
test_asymp(lambda z: expint(-4, z))
|
||||
test_asymp(lambda z: expint(2.25, z))
|
||||
test_asymp(lambda z: gammainc(2.5, z, 5))
|
||||
test_asymp(lambda z: gammainc(2.5, 5, z))
|
||||
test_asymp(lambda z: hermite(3, z))
|
||||
test_asymp(lambda z: hermite(2.5, z))
|
||||
test_asymp(lambda z: legendre(3, z))
|
||||
test_asymp(lambda z: legendre(4, z))
|
||||
test_asymp(lambda z: legendre(2.5, z))
|
||||
test_asymp(lambda z: legenp(a1, a2, z))
|
||||
test_asymp(lambda z: legenq(a1, a2, z), maxdps=90) # abnormally slow
|
||||
test_asymp(lambda z: jtheta(1, z, 0.5))
|
||||
test_asymp(lambda z: jtheta(2, z, 0.5))
|
||||
test_asymp(lambda z: jtheta(3, z, 0.5))
|
||||
test_asymp(lambda z: jtheta(4, z, 0.5))
|
||||
test_asymp(lambda z: jtheta(1, z, 0.5, 1))
|
||||
test_asymp(lambda z: jtheta(2, z, 0.5, 1))
|
||||
test_asymp(lambda z: jtheta(3, z, 0.5, 1))
|
||||
test_asymp(lambda z: jtheta(4, z, 0.5, 1))
|
||||
test_asymp(barnesg, maxdps=90)
|
||||
"""
|
||||
|
||||
def testit(line):
|
||||
if filt in line:
|
||||
print(line)
|
||||
t1 = clock()
|
||||
exec_(line, globals(), locals())
|
||||
t2 = clock()
|
||||
elapsed = t2-t1
|
||||
print("Time:", elapsed, "for", line, "(OK)")
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
from multiprocessing import Pool
|
||||
mapf = Pool(None).map
|
||||
print("Running tests with multiprocessing")
|
||||
except ImportError:
|
||||
print("Not using multiprocessing")
|
||||
mapf = map
|
||||
t1 = clock()
|
||||
tasks = cases.splitlines()
|
||||
mapf(testit, tasks)
|
||||
t2 = clock()
|
||||
print("Cumulative wall time:", t2-t1)
|
||||
@@ -0,0 +1,93 @@
|
||||
|
||||
def monitor(f, input='print', output='print'):
|
||||
"""
|
||||
Returns a wrapped copy of *f* that monitors evaluation by calling
|
||||
*input* with every input (*args*, *kwargs*) passed to *f* and
|
||||
*output* with every value returned from *f*. The default action
|
||||
(specify using the special string value ``'print'``) is to print
|
||||
inputs and outputs to stdout, along with the total evaluation
|
||||
count::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> mp.dps = 5; mp.pretty = False
|
||||
>>> diff(monitor(exp), 1) # diff will eval f(x-h) and f(x+h)
|
||||
in 0 (mpf('0.99999999906867742538452148'),) {}
|
||||
out 0 mpf('2.7182818259274480055282064')
|
||||
in 1 (mpf('1.0000000009313225746154785'),) {}
|
||||
out 1 mpf('2.7182818309906424675501024')
|
||||
mpf('2.7182808')
|
||||
|
||||
To disable either the input or the output handler, you may
|
||||
pass *None* as argument.
|
||||
|
||||
Custom input and output handlers may be used e.g. to store
|
||||
results for later analysis::
|
||||
|
||||
>>> mp.dps = 15
|
||||
>>> input = []
|
||||
>>> output = []
|
||||
>>> findroot(monitor(sin, input.append, output.append), 3.0)
|
||||
mpf('3.1415926535897932')
|
||||
>>> len(input) # Count number of evaluations
|
||||
9
|
||||
>>> print(input[3]); print(output[3])
|
||||
((mpf('3.1415076583334066'),), {})
|
||||
8.49952562843408e-5
|
||||
>>> print(input[4]); print(output[4])
|
||||
((mpf('3.1415928201669122'),), {})
|
||||
-1.66577118985331e-7
|
||||
|
||||
"""
|
||||
if not input:
|
||||
input = lambda v: None
|
||||
elif input == 'print':
|
||||
incount = [0]
|
||||
def input(value):
|
||||
args, kwargs = value
|
||||
print("in %s %r %r" % (incount[0], args, kwargs))
|
||||
incount[0] += 1
|
||||
if not output:
|
||||
output = lambda v: None
|
||||
elif output == 'print':
|
||||
outcount = [0]
|
||||
def output(value):
|
||||
print("out %s %r" % (outcount[0], value))
|
||||
outcount[0] += 1
|
||||
def f_monitored(*args, **kwargs):
|
||||
input((args, kwargs))
|
||||
v = f(*args, **kwargs)
|
||||
output(v)
|
||||
return v
|
||||
return f_monitored
|
||||
|
||||
def timing(f, *args, **kwargs):
|
||||
"""
|
||||
Returns time elapsed for evaluating ``f()``. Optionally arguments
|
||||
may be passed to time the execution of ``f(*args, **kwargs)``.
|
||||
|
||||
If the first call is very quick, ``f`` is called
|
||||
repeatedly and the best time is returned.
|
||||
"""
|
||||
once = kwargs.get('once')
|
||||
if 'once' in kwargs:
|
||||
del kwargs['once']
|
||||
if args or kwargs:
|
||||
if len(args) == 1 and not kwargs:
|
||||
arg = args[0]
|
||||
g = lambda: f(arg)
|
||||
else:
|
||||
g = lambda: f(*args, **kwargs)
|
||||
else:
|
||||
g = f
|
||||
from timeit import default_timer as clock
|
||||
t1=clock(); v=g(); t2=clock(); t=t2-t1
|
||||
if t > 0.05 or once:
|
||||
return t
|
||||
for i in range(3):
|
||||
t1=clock();
|
||||
# Evaluate multiple times because the timer function
|
||||
# has a significant overhead
|
||||
g();g();g();g();g();g();g();g();g();g()
|
||||
t2=clock()
|
||||
t=min(t,(t2-t1)/10)
|
||||
return t
|
||||
@@ -0,0 +1,313 @@
|
||||
"""
|
||||
Plotting (requires matplotlib)
|
||||
"""
|
||||
|
||||
from colorsys import hsv_to_rgb, hls_to_rgb
|
||||
from .libmp import NoConvergence
|
||||
from .libmp.backend import xrange
|
||||
|
||||
class VisualizationMethods(object):
|
||||
plot_ignore = (ValueError, ArithmeticError, ZeroDivisionError, NoConvergence)
|
||||
|
||||
def plot(ctx, f, xlim=[-5,5], ylim=None, points=200, file=None, dpi=None,
|
||||
singularities=[], axes=None):
|
||||
r"""
|
||||
Shows a simple 2D plot of a function `f(x)` or list of functions
|
||||
`[f_0(x), f_1(x), \ldots, f_n(x)]` over a given interval
|
||||
specified by *xlim*. Some examples::
|
||||
|
||||
plot(lambda x: exp(x)*li(x), [1, 4])
|
||||
plot([cos, sin], [-4, 4])
|
||||
plot([fresnels, fresnelc], [-4, 4])
|
||||
plot([sqrt, cbrt], [-4, 4])
|
||||
plot(lambda t: zeta(0.5+t*j), [-20, 20])
|
||||
plot([floor, ceil, abs, sign], [-5, 5])
|
||||
|
||||
Points where the function raises a numerical exception or
|
||||
returns an infinite value are removed from the graph.
|
||||
Singularities can also be excluded explicitly
|
||||
as follows (useful for removing erroneous vertical lines)::
|
||||
|
||||
plot(cot, ylim=[-5, 5]) # bad
|
||||
plot(cot, ylim=[-5, 5], singularities=[-pi, 0, pi]) # good
|
||||
|
||||
For parts where the function assumes complex values, the
|
||||
real part is plotted with dashes and the imaginary part
|
||||
is plotted with dots.
|
||||
|
||||
.. note :: This function requires matplotlib (pylab).
|
||||
"""
|
||||
if file:
|
||||
axes = None
|
||||
fig = None
|
||||
if not axes:
|
||||
import pylab
|
||||
fig = pylab.figure()
|
||||
axes = fig.add_subplot(111)
|
||||
if not isinstance(f, (tuple, list)):
|
||||
f = [f]
|
||||
a, b = xlim
|
||||
colors = ['b', 'r', 'g', 'm', 'k']
|
||||
for n, func in enumerate(f):
|
||||
x = ctx.arange(a, b, (b-a)/float(points))
|
||||
segments = []
|
||||
segment = []
|
||||
in_complex = False
|
||||
for i in xrange(len(x)):
|
||||
try:
|
||||
if i != 0:
|
||||
for sing in singularities:
|
||||
if x[i-1] <= sing and x[i] >= sing:
|
||||
raise ValueError
|
||||
v = func(x[i])
|
||||
if ctx.isnan(v) or abs(v) > 1e300:
|
||||
raise ValueError
|
||||
if hasattr(v, "imag") and v.imag:
|
||||
re = float(v.real)
|
||||
im = float(v.imag)
|
||||
if not in_complex:
|
||||
in_complex = True
|
||||
segments.append(segment)
|
||||
segment = []
|
||||
segment.append((float(x[i]), re, im))
|
||||
else:
|
||||
if in_complex:
|
||||
in_complex = False
|
||||
segments.append(segment)
|
||||
segment = []
|
||||
if hasattr(v, "real"):
|
||||
v = v.real
|
||||
segment.append((float(x[i]), v))
|
||||
except ctx.plot_ignore:
|
||||
if segment:
|
||||
segments.append(segment)
|
||||
segment = []
|
||||
if segment:
|
||||
segments.append(segment)
|
||||
for segment in segments:
|
||||
x = [s[0] for s in segment]
|
||||
y = [s[1] for s in segment]
|
||||
if not x:
|
||||
continue
|
||||
c = colors[n % len(colors)]
|
||||
if len(segment[0]) == 3:
|
||||
z = [s[2] for s in segment]
|
||||
axes.plot(x, y, '--'+c, linewidth=3)
|
||||
axes.plot(x, z, ':'+c, linewidth=3)
|
||||
else:
|
||||
axes.plot(x, y, c, linewidth=3)
|
||||
axes.set_xlim([float(_) for _ in xlim])
|
||||
if ylim:
|
||||
axes.set_ylim([float(_) for _ in ylim])
|
||||
axes.set_xlabel('x')
|
||||
axes.set_ylabel('f(x)')
|
||||
axes.grid(True)
|
||||
if fig:
|
||||
if file:
|
||||
pylab.savefig(file, dpi=dpi)
|
||||
else:
|
||||
pylab.show()
|
||||
|
||||
def default_color_function(ctx, z):
|
||||
if ctx.isinf(z):
|
||||
return (1.0, 1.0, 1.0)
|
||||
if ctx.isnan(z):
|
||||
return (0.5, 0.5, 0.5)
|
||||
pi = 3.1415926535898
|
||||
a = (float(ctx.arg(z)) + ctx.pi) / (2*ctx.pi)
|
||||
a = (a + 0.5) % 1.0
|
||||
b = 1.0 - float(1/(1.0+abs(z)**0.3))
|
||||
return hls_to_rgb(a, b, 0.8)
|
||||
|
||||
blue_orange_colors = [
|
||||
(-1.0, (0.0, 0.0, 0.0)),
|
||||
(-0.95, (0.1, 0.2, 0.5)), # dark blue
|
||||
(-0.5, (0.0, 0.5, 1.0)), # blueish
|
||||
(-0.05, (0.4, 0.8, 0.8)), # cyanish
|
||||
( 0.0, (1.0, 1.0, 1.0)),
|
||||
( 0.05, (1.0, 0.9, 0.3)), # yellowish
|
||||
( 0.5, (0.9, 0.5, 0.0)), # orangeish
|
||||
( 0.95, (0.7, 0.1, 0.0)), # redish
|
||||
( 1.0, (0.0, 0.0, 0.0)),
|
||||
( 2.0, (0.0, 0.0, 0.0)),
|
||||
]
|
||||
|
||||
def phase_color_function(ctx, z):
|
||||
if ctx.isinf(z):
|
||||
return (1.0, 1.0, 1.0)
|
||||
if ctx.isnan(z):
|
||||
return (0.5, 0.5, 0.5)
|
||||
pi = 3.1415926535898
|
||||
w = float(ctx.arg(z)) / pi
|
||||
w = max(min(w, 1.0), -1.0)
|
||||
for i in range(1,len(blue_orange_colors)):
|
||||
if blue_orange_colors[i][0] > w:
|
||||
a, (ra, ga, ba) = blue_orange_colors[i-1]
|
||||
b, (rb, gb, bb) = blue_orange_colors[i]
|
||||
s = (w-a) / (b-a)
|
||||
return ra+(rb-ra)*s, ga+(gb-ga)*s, ba+(bb-ba)*s
|
||||
|
||||
def cplot(ctx, f, re=[-5,5], im=[-5,5], points=2000, color=None,
|
||||
verbose=False, file=None, dpi=None, axes=None):
|
||||
"""
|
||||
Plots the given complex-valued function *f* over a rectangular part
|
||||
of the complex plane specified by the pairs of intervals *re* and *im*.
|
||||
For example::
|
||||
|
||||
cplot(lambda z: z, [-2, 2], [-10, 10])
|
||||
cplot(exp)
|
||||
cplot(zeta, [0, 1], [0, 50])
|
||||
|
||||
By default, the complex argument (phase) is shown as color (hue) and
|
||||
the magnitude is show as brightness. You can also supply a
|
||||
custom color function (*color*). This function should take a
|
||||
complex number as input and return an RGB 3-tuple containing
|
||||
floats in the range 0.0-1.0.
|
||||
|
||||
Alternatively, you can select a builtin color function by passing
|
||||
a string as *color*:
|
||||
|
||||
* "default" - default color scheme
|
||||
* "phase" - a color scheme that only renders the phase of the function,
|
||||
with white for positive reals, black for negative reals, gold in the
|
||||
upper half plane, and blue in the lower half plane.
|
||||
|
||||
To obtain a sharp image, the number of points may need to be
|
||||
increased to 100,000 or thereabout. Since evaluating the
|
||||
function that many times is likely to be slow, the 'verbose'
|
||||
option is useful to display progress.
|
||||
|
||||
.. note :: This function requires matplotlib (pylab).
|
||||
"""
|
||||
if color is None or color == "default":
|
||||
color = ctx.default_color_function
|
||||
if color == "phase":
|
||||
color = ctx.phase_color_function
|
||||
import pylab
|
||||
if file:
|
||||
axes = None
|
||||
fig = None
|
||||
if not axes:
|
||||
fig = pylab.figure()
|
||||
axes = fig.add_subplot(111)
|
||||
rea, reb = re
|
||||
ima, imb = im
|
||||
dre = reb - rea
|
||||
dim = imb - ima
|
||||
M = int(ctx.sqrt(points*dre/dim)+1)
|
||||
N = int(ctx.sqrt(points*dim/dre)+1)
|
||||
x = pylab.linspace(rea, reb, M)
|
||||
y = pylab.linspace(ima, imb, N)
|
||||
# Note: we have to be careful to get the right rotation.
|
||||
# Test with these plots:
|
||||
# cplot(lambda z: z if z.real < 0 else 0)
|
||||
# cplot(lambda z: z if z.imag < 0 else 0)
|
||||
w = pylab.zeros((N, M, 3))
|
||||
for n in xrange(N):
|
||||
for m in xrange(M):
|
||||
z = ctx.mpc(x[m], y[n])
|
||||
try:
|
||||
v = color(f(z))
|
||||
except ctx.plot_ignore:
|
||||
v = (0.5, 0.5, 0.5)
|
||||
w[n,m] = v
|
||||
if verbose:
|
||||
print(str(n) + ' of ' + str(N))
|
||||
rea, reb, ima, imb = [float(_) for _ in [rea, reb, ima, imb]]
|
||||
axes.imshow(w, extent=(rea, reb, ima, imb), origin='lower')
|
||||
axes.set_xlabel('Re(z)')
|
||||
axes.set_ylabel('Im(z)')
|
||||
if fig:
|
||||
if file:
|
||||
pylab.savefig(file, dpi=dpi)
|
||||
else:
|
||||
pylab.show()
|
||||
|
||||
def splot(ctx, f, u=[-5,5], v=[-5,5], points=100, keep_aspect=True, \
|
||||
wireframe=False, file=None, dpi=None, axes=None):
|
||||
"""
|
||||
Plots the surface defined by `f`.
|
||||
|
||||
If `f` returns a single component, then this plots the surface
|
||||
defined by `z = f(x,y)` over the rectangular domain with
|
||||
`x = u` and `y = v`.
|
||||
|
||||
If `f` returns three components, then this plots the parametric
|
||||
surface `x, y, z = f(u,v)` over the pairs of intervals `u` and `v`.
|
||||
|
||||
For example, to plot a simple function::
|
||||
|
||||
>>> from mpmath import *
|
||||
>>> f = lambda x, y: sin(x+y)*cos(y)
|
||||
>>> splot(f, [-pi,pi], [-pi,pi]) # doctest: +SKIP
|
||||
|
||||
Plotting a donut::
|
||||
|
||||
>>> r, R = 1, 2.5
|
||||
>>> f = lambda u, v: [r*cos(u), (R+r*sin(u))*cos(v), (R+r*sin(u))*sin(v)]
|
||||
>>> splot(f, [0, 2*pi], [0, 2*pi]) # doctest: +SKIP
|
||||
|
||||
.. note :: This function requires matplotlib (pylab) 0.98.5.3 or higher.
|
||||
"""
|
||||
import pylab
|
||||
import mpl_toolkits.mplot3d as mplot3d
|
||||
if file:
|
||||
axes = None
|
||||
fig = None
|
||||
if not axes:
|
||||
fig = pylab.figure()
|
||||
axes = mplot3d.axes3d.Axes3D(fig)
|
||||
ua, ub = u
|
||||
va, vb = v
|
||||
du = ub - ua
|
||||
dv = vb - va
|
||||
if not isinstance(points, (list, tuple)):
|
||||
points = [points, points]
|
||||
M, N = points
|
||||
u = pylab.linspace(ua, ub, M)
|
||||
v = pylab.linspace(va, vb, N)
|
||||
x, y, z = [pylab.zeros((M, N)) for i in xrange(3)]
|
||||
xab, yab, zab = [[0, 0] for i in xrange(3)]
|
||||
for n in xrange(N):
|
||||
for m in xrange(M):
|
||||
fdata = f(ctx.convert(u[m]), ctx.convert(v[n]))
|
||||
try:
|
||||
x[m,n], y[m,n], z[m,n] = fdata
|
||||
except TypeError:
|
||||
x[m,n], y[m,n], z[m,n] = u[m], v[n], fdata
|
||||
for c, cab in [(x[m,n], xab), (y[m,n], yab), (z[m,n], zab)]:
|
||||
if c < cab[0]:
|
||||
cab[0] = c
|
||||
if c > cab[1]:
|
||||
cab[1] = c
|
||||
if wireframe:
|
||||
axes.plot_wireframe(x, y, z, rstride=4, cstride=4)
|
||||
else:
|
||||
axes.plot_surface(x, y, z, rstride=4, cstride=4)
|
||||
axes.set_xlabel('x')
|
||||
axes.set_ylabel('y')
|
||||
axes.set_zlabel('z')
|
||||
if keep_aspect:
|
||||
dx, dy, dz = [cab[1] - cab[0] for cab in [xab, yab, zab]]
|
||||
maxd = max(dx, dy, dz)
|
||||
if dx < maxd:
|
||||
delta = maxd - dx
|
||||
axes.set_xlim3d(xab[0] - delta / 2.0, xab[1] + delta / 2.0)
|
||||
if dy < maxd:
|
||||
delta = maxd - dy
|
||||
axes.set_ylim3d(yab[0] - delta / 2.0, yab[1] + delta / 2.0)
|
||||
if dz < maxd:
|
||||
delta = maxd - dz
|
||||
axes.set_zlim3d(zab[0] - delta / 2.0, zab[1] + delta / 2.0)
|
||||
if fig:
|
||||
if file:
|
||||
pylab.savefig(file, dpi=dpi)
|
||||
else:
|
||||
pylab.show()
|
||||
|
||||
|
||||
VisualizationMethods.plot = plot
|
||||
VisualizationMethods.default_color_function = default_color_function
|
||||
VisualizationMethods.phase_color_function = phase_color_function
|
||||
VisualizationMethods.cplot = cplot
|
||||
VisualizationMethods.splot = splot
|
||||
Reference in New Issue
Block a user