Skip to content

Commit 43b77ff

Browse files
author
Gilles Sadowski
committed
Merge branch 'fix__MATH-1558'
Closes #161.
2 parents 0b3629b + 0fe8ef6 commit 43b77ff

3 files changed

Lines changed: 57 additions & 19 deletions

File tree

src/changes/changes.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ If the output is not quite correct, check for invisible trailing spaces!
5454
</release>
5555

5656
<release version="4.0" date="XXXX-XX-XX" description="">
57+
<action dev="erans" type="fix" issue="MATH-1558" due-to="Sam Ritchie">
58+
"MidPointIntegrator": Fix iterative procedure in order to actually benefit
59+
from evaluations performed at earlier stages.
60+
</action>
5761
<action dev="erans" type="fix" issue="MATH-1555" due-to="Laurent Galluccio">
5862
"Atan2": Documentation issue.
5963
</action>

src/main/java/org/apache/commons/math4/analysis/integration/MidPointIntegrator.java

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import org.apache.commons.math4.util.FastMath;
2626

2727
/**
28-
* Implements the <a href="http://en.wikipedia.org/wiki/Midpoint_method">
28+
* Implements the <a href="https://en.wikipedia.org/wiki/Riemann_sum#Midpoint_rule">
2929
* Midpoint Rule</a> for integration of real univariate functions. For
3030
* reference, see <b>Numerical Mathematics</b>, ISBN 0387989595,
3131
* chapter 9.2.
@@ -36,8 +36,10 @@
3636
*/
3737
public class MidPointIntegrator extends BaseAbstractUnivariateIntegrator {
3838

39-
/** Maximum number of iterations for midpoint. */
40-
private static final int MIDPOINT_MAX_ITERATIONS_COUNT = 63;
39+
/** Maximum number of iterations for midpoint. 39 = floor(log_3(2^63)), the
40+
* maximum number of triplings allowed before exceeding 64-bit bounds.
41+
*/
42+
private static final int MIDPOINT_MAX_ITERATIONS_COUNT = 39;
4143

4244
/**
4345
* Build a midpoint integrator with given accuracies and iterations counts.
@@ -50,7 +52,7 @@ public class MidPointIntegrator extends BaseAbstractUnivariateIntegrator {
5052
* @exception NumberIsTooSmallException if maximal number of iterations
5153
* is lesser than or equal to the minimal number of iterations
5254
* @exception NumberIsTooLargeException if maximal number of iterations
53-
* is greater than 63.
55+
* is greater than 39.
5456
*/
5557
public MidPointIntegrator(final double relativeAccuracy,
5658
final double absoluteAccuracy,
@@ -73,7 +75,7 @@ public MidPointIntegrator(final double relativeAccuracy,
7375
* @exception NumberIsTooSmallException if maximal number of iterations
7476
* is lesser than or equal to the minimal number of iterations
7577
* @exception NumberIsTooLargeException if maximal number of iterations
76-
* is greater than 63.
78+
* is greater than 39.
7779
*/
7880
public MidPointIntegrator(final int minimalIterationCount,
7981
final int maximalIterationCount)
@@ -98,11 +100,11 @@ public MidPointIntegrator() {
98100
* This function should only be called by API <code>integrate()</code> in the package.
99101
* To save time it does not verify arguments - caller does.
100102
* <p>
101-
* The interval is divided equally into 2^n sections rather than an
103+
* The interval is divided equally into 3^n sections rather than an
102104
* arbitrary m sections because this configuration can best utilize the
103105
* already computed values.</p>
104106
*
105-
* @param n the stage of 1/2 refinement. Must be larger than 0.
107+
* @param n the stage of 1/3 refinement. Must be larger than 0.
106108
* @param previousStageResult Result from the previous call to the
107109
* {@code stage} method.
108110
* @param min Lower bound of the integration interval.
@@ -118,21 +120,29 @@ private double stage(final int n,
118120
double diffMaxMin)
119121
throws TooManyEvaluationsException {
120122

121-
// number of new points in this stage
122-
final long np = 1L << (n - 1);
123+
// number of points in the previous stage. This stage will contribute
124+
// 2*3^{n-1} more points.
125+
final long np = (long) FastMath.pow(3, n - 1);
123126
double sum = 0;
124127

125128
// spacing between adjacent new points
126129
final double spacing = diffMaxMin / np;
130+
final double leftOffset = spacing / 6;
131+
final double rightOffset = 5 * leftOffset;
127132

128-
// the first new point
129-
double x = min + 0.5 * spacing;
133+
double x = min;
130134
for (long i = 0; i < np; i++) {
131-
sum += computeObjectiveValue(x);
135+
// The first and second new points are located at the new midpoints
136+
// generated when each previous integration slice is split into 3.
137+
//
138+
// |--------x--------|
139+
// |--x--|--x--|--x--|
140+
sum += computeObjectiveValue(x + leftOffset);
141+
sum += computeObjectiveValue(x + rightOffset);
132142
x += spacing;
133143
}
134144
// add the new sum to previously calculated result
135-
return 0.5 * (previousStageResult + sum * spacing);
145+
return (previousStageResult + sum * spacing) / 3.0;
136146
}
137147

138148

src/test/java/org/apache/commons/math4/analysis/integration/MidPointIntegratorTest.java

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,25 @@
3535
public final class MidPointIntegratorTest {
3636
private static final int NUM_ITER = 30;
3737

38+
/**
39+
* The initial iteration contributes 1 evaluation. Each successive iteration
40+
* contributes 2 points to each previous slice.
41+
*
42+
* The total evaluation count == 1 + 2*3^0 + 2*3^1 + ... 2*3^n
43+
*
44+
* the series 3^0 + 3^1 + ... + 3^n sums to 3^(n-1) / (3-1), so the total
45+
* expected evaluations == 1 + 2*(3^(n-1) - 1)/2 == 3^(n-1).
46+
*
47+
* The n in the series above is offset by 1 from the MidPointIntegrator
48+
* iteration count so the actual result == 3^n.
49+
*
50+
* Without the incremental implementation, the same result would require
51+
* (3^(n + 1) - 1) / 2 evaluations; just under 50% more.
52+
*/
53+
private long expectedEvaluations(int iterations) {
54+
return (long) FastMath.pow(3, iterations);
55+
}
56+
3857
/**
3958
* Test of integrator for the sine function.
4059
*/
@@ -48,8 +67,9 @@ public void testLowAccuracy() {
4867
double expected = -3697001.0 / 48.0;
4968
double tolerance = FastMath.abs(expected * integrator.getRelativeAccuracy());
5069
double result = integrator.integrate(Integer.MAX_VALUE, f, min, max);
51-
Assert.assertTrue(integrator.getEvaluations() < Integer.MAX_VALUE / 2);
70+
Assert.assertTrue(integrator.getEvaluations() < Integer.MAX_VALUE / 3);
5271
Assert.assertTrue(integrator.getIterations() < NUM_ITER);
72+
Assert.assertEquals(expectedEvaluations(integrator.getIterations()), integrator.getEvaluations());
5373
Assert.assertEquals(expected, result, tolerance);
5474

5575
}
@@ -67,17 +87,19 @@ public void testSinFunction() {
6787
double expected = 2;
6888
double tolerance = FastMath.abs(expected * integrator.getRelativeAccuracy());
6989
double result = integrator.integrate(Integer.MAX_VALUE, f, min, max);
70-
Assert.assertTrue(integrator.getEvaluations() < Integer.MAX_VALUE / 2);
90+
Assert.assertTrue(integrator.getEvaluations() < Integer.MAX_VALUE / 3);
7191
Assert.assertTrue(integrator.getIterations() < NUM_ITER);
92+
Assert.assertEquals(expectedEvaluations(integrator.getIterations()), integrator.getEvaluations());
7293
Assert.assertEquals(expected, result, tolerance);
7394

7495
min = -FastMath.PI/3;
7596
max = 0;
7697
expected = -0.5;
7798
tolerance = FastMath.abs(expected * integrator.getRelativeAccuracy());
7899
result = integrator.integrate(Integer.MAX_VALUE, f, min, max);
79-
Assert.assertTrue(integrator.getEvaluations() < Integer.MAX_VALUE / 2);
100+
Assert.assertTrue(integrator.getEvaluations() < Integer.MAX_VALUE / 3);
80101
Assert.assertTrue(integrator.getIterations() < NUM_ITER);
102+
Assert.assertEquals(expectedEvaluations(integrator.getIterations()), integrator.getEvaluations());
81103
Assert.assertEquals(expected, result, tolerance);
82104

83105
}
@@ -95,16 +117,17 @@ public void testQuinticFunction() {
95117
double expected = -1.0 / 48;
96118
double tolerance = FastMath.abs(expected * integrator.getRelativeAccuracy());
97119
double result = integrator.integrate(Integer.MAX_VALUE, f, min, max);
98-
Assert.assertTrue(integrator.getEvaluations() < Integer.MAX_VALUE / 2);
120+
Assert.assertTrue(integrator.getEvaluations() < Integer.MAX_VALUE / 3);
99121
Assert.assertTrue(integrator.getIterations() < NUM_ITER);
122+
Assert.assertEquals(expectedEvaluations(integrator.getIterations()), integrator.getEvaluations());
100123
Assert.assertEquals(expected, result, tolerance);
101124

102125
min = 0;
103126
max = 0.5;
104127
expected = 11.0 / 768;
105128
tolerance = FastMath.abs(expected * integrator.getRelativeAccuracy());
106129
result = integrator.integrate(Integer.MAX_VALUE, f, min, max);
107-
Assert.assertTrue(integrator.getEvaluations() < Integer.MAX_VALUE / 2);
130+
Assert.assertTrue(integrator.getEvaluations() < Integer.MAX_VALUE / 3);
108131
Assert.assertTrue(integrator.getIterations() < NUM_ITER);
109132
Assert.assertEquals(expected, result, tolerance);
110133

@@ -113,8 +136,9 @@ public void testQuinticFunction() {
113136
expected = 2048 / 3.0 - 78 + 1.0 / 48;
114137
tolerance = FastMath.abs(expected * integrator.getRelativeAccuracy());
115138
result = integrator.integrate(Integer.MAX_VALUE, f, min, max);
116-
Assert.assertTrue(integrator.getEvaluations() < Integer.MAX_VALUE / 2);
139+
Assert.assertTrue(integrator.getEvaluations() < Integer.MAX_VALUE / 3);
117140
Assert.assertTrue(integrator.getIterations() < NUM_ITER);
141+
Assert.assertEquals(expectedEvaluations(integrator.getIterations()), integrator.getEvaluations());
118142
Assert.assertEquals(expected, result, tolerance);
119143

120144
}

0 commit comments

Comments
 (0)