Skip to content

Commit 7123a67

Browse files
naive quadrature using adaptive subdivision
1 parent c6a35f9 commit 7123a67

5 files changed

Lines changed: 117 additions & 5 deletions

File tree

doc/source/calculus/integration.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ Standard quadrature (``quad``)
66

77
.. autofunction:: mpmath.quad
88

9+
Quadrature with subdivision (``quadsubdiv``)
10+
............................................
11+
12+
.. autofunction:: mpmath.quadsubdiv
13+
914
Oscillatory quadrature (``quadosc``)
1015
....................................
1116

mpmath/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
quadgl = mp.quadgl
104104
quadts = mp.quadts
105105
quadosc = mp.quadosc
106+
quadsubdiv = mp.quadsubdiv
106107

107108
invertlaplace = mp.invertlaplace
108109
invlaptalbot = mp.invlaptalbot

mpmath/calculus/quadrature.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,8 @@ def quad(ctx, f, *points, **kwargs):
718718
>>> quad(f, [-100, 0, 100]) # Also good
719719
3.12159332021646
720720
721+
For such integr
722+
721723
**References**
722724
723725
1. http://mathworld.wolfram.com/DoubleIntegral.html
@@ -1006,6 +1008,108 @@ def term(k):
10061008
s += ctx.nsum(term, [n, ctx.inf])
10071009
return s
10081010

1011+
def quadsubdiv(ctx, f, interval, tol=None, maxintervals=None, **kwargs):
1012+
"""
1013+
Computes the integral of *f* over the interval or path specified
1014+
by *interval*, using :func:`~mpmath.quad` together with adaptive
1015+
subdivision of the interval.
1016+
1017+
This function gives an accurate answer for some integrals where
1018+
:func:`~mpmath.quad` fails::
1019+
1020+
>>> mp.dps = 15; mp.pretty = True
1021+
>>> quad(lambda x: abs(sin(x)), [0, 2*pi])
1022+
3.99900894176779
1023+
>>> quadsubdiv(lambda x: abs(sin(x)), [0, 2*pi])
1024+
4.0
1025+
>>> quadsubdiv(sin, [0, 1000])
1026+
0.437620923709297
1027+
>>> quadsubdiv(lambda x: 1/(1+x**2), [-100, 100])
1028+
3.12159332021646
1029+
>>> quadsubdiv(lambda x: ceil(x), [0, 100])
1030+
5050.0
1031+
>>> quadsubdiv(lambda x: sin(x+exp(x)), [0,8])
1032+
0.347400172657248
1033+
1034+
The argument *maxintervals* can be set to limit the permissible
1035+
subdivision::
1036+
1037+
>>> quadsubdiv(lambda x: sin(x**2), [0,100], maxintervals=5, error=True)
1038+
(-5.40487904307774, 5.011)
1039+
>>> quadsubdiv(lambda x: sin(x**2), [0,100], maxintervals=100, error=True)
1040+
(0.631417921866934, 1.10101120134116e-17)
1041+
1042+
Subdivision does not guarantee a correct answer since, the error
1043+
estimate on subintervals may be inaccurate::
1044+
1045+
>>> quadsubdiv(lambda x: sech(10*x-2)**2 + sech(100*x-40)**4 + sech(1000*x-600)**6, [0,1], error=True)
1046+
(0.209736068833883, 1.00011000000001e-18)
1047+
>>> mp.dps = 20
1048+
>>> quadsubdiv(lambda x: sech(10*x-2)**2 + sech(100*x-40)**4 + sech(1000*x-600)**6, [0,1], error=True)
1049+
(0.21080273550054927738, 2.200000001e-24)
1050+
1051+
The second answer is correct. We can get an accurate result at lower
1052+
precision by forcing a finer initial subdivision::
1053+
1054+
>>> quadsubdiv(lambda x: sech(10*x-2)**2 + sech(100*x-40)**4 + sech(1000*x-600)**6, linspace(0,1,5))
1055+
0.210802735500549
1056+
1057+
The following integral is too oscillatory for convergence, but we can get a
1058+
reasonable estimate::
1059+
1060+
>>> v, err = fp.quadsubdiv(lambda x: fp.sin(1/x), [0,1], error=True)
1061+
>>> round(v, 6), round(err, 6)
1062+
(0.504067, 1e-06)
1063+
>>> sin(1) - ci(1)
1064+
0.504067061906928
1065+
1066+
"""
1067+
queue = []
1068+
for i in range(len(interval)-1):
1069+
queue.append((interval[i], interval[i+1]))
1070+
total = ctx.zero
1071+
total_error = ctx.zero
1072+
if maxintervals is None:
1073+
maxintervals = 10 * ctx.prec
1074+
count = 0
1075+
quad_args = kwargs.copy()
1076+
quad_args["verbose"] = False
1077+
quad_args["error"] = True
1078+
if tol is None:
1079+
tol = +ctx.eps
1080+
orig = ctx.prec
1081+
try:
1082+
ctx.prec += 5
1083+
while queue:
1084+
a, b = queue.pop()
1085+
s, err = ctx.quad(f, [a, b], **quad_args)
1086+
if kwargs.get("verbose"):
1087+
print("subinterval", count, a, b, err)
1088+
if err < tol or count > maxintervals:
1089+
total += s
1090+
total_error += err
1091+
else:
1092+
count += 1
1093+
if count == maxintervals and kwargs.get("verbose"):
1094+
print("warning: number of intervals exceeded maxintervals")
1095+
if a == -ctx.inf and b == ctx.inf:
1096+
m = 0
1097+
elif a == -ctx.inf:
1098+
m = min(b-1, 2*b)
1099+
elif b == ctx.inf:
1100+
m = max(a+1, 2*a)
1101+
else:
1102+
m = a + (b - a) / 2
1103+
queue.append((a, m))
1104+
queue.append((m, b))
1105+
finally:
1106+
ctx.prec = orig
1107+
if kwargs.get("error"):
1108+
return +total, +total_error
1109+
else:
1110+
return +total
1111+
10091112
if __name__ == '__main__':
10101113
import doctest
10111114
doctest.testmod()
1115+

mpmath/functions/elliptic.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -586,8 +586,8 @@ def RJ_calc(ctx, x, y, z, p, r, integration):
586586
N += margin
587587
F = lambda t: 1/(ctx.sqrt(t+x)*ctx.sqrt(t+y)*ctx.sqrt(t+z)*(t+p))
588588
if integration == 2:
589-
return 1.5 * ctx.quad(F, [0, N, ctx.inf])
590-
initial_integral = 1.5 * ctx.quad(F, [0, N])
589+
return 1.5 * ctx.quadsubdiv(F, [0, N, ctx.inf])
590+
initial_integral = 1.5 * ctx.quadsubdiv(F, [0, N])
591591
x += N; y += N; z += N; p += N
592592
xm,ym,zm,pm = x,y,z,p
593593
A0 = Am = (x + y + z + 2*p)/5

mpmath/tests/test_elliptic.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -654,9 +654,11 @@ def test_elliptic_integrals():
654654
assert elliprj(0.3068, -4.037+0.0632j, 1.654, -0.9609).ae(-1.17157627949475577 - 0.069182614173988811j, abs_eps=1e-8)
655655
assert elliprj(0.3068, -4.037+0.00632j, 1.654, -0.9609).ae(-1.17337595670549633 - 0.0623069224526925j, abs_eps=1e-8)
656656

657-
# these don't work because of inaccurate integration
658-
# assert elliprj(0.3068, -4.037-0.0632j, 1.654, -0.9609).ae(1.77940452391261626 + 0.0388711305592447234j, abs_eps=1e-8)
659-
# assert elliprj(0.3068, -4.037-0.00632j, 1.654, -0.9609).ae(1.77806722756403055 + 0.0592749824572262329j, abs_eps=1e-8)
657+
# these require accurate integration
658+
assert elliprj(0.3068, -4.037-0.0632j, 1.654, -0.9609).ae(1.77940452391261626 + 0.0388711305592447234j)
659+
assert elliprj(0.3068, -4.037-0.00632j, 1.654, -0.9609).ae(1.77806722756403055 + 0.0592749824572262329j)
660+
# issue #571
661+
assert ellippi(2.1 + 0.94j, 2.3 + 0.98j, 2.5 + 0.01j).ae(-0.40652414240811963438 + 2.1547659461404749309j)
660662

661663
assert ellippi(2.0-1.0j, 2.0+1.0j).ae(1.8578723151271115 - 1.18642180609983531j)
662664
assert ellippi(2.0-0.5j, 0.5+1.0j).ae(0.936761970766645807 - 1.61876787838890786j)

0 commit comments

Comments
 (0)