Simpson's Rule and Numerical Integration — Practice Tasks¶
All tasks must be solved in Go, Java, and Python. Each task ships with a precise spec or starter code in all three languages. Implement the integration logic and validate against the evaluation criteria. Always cross-check your result against (a) a known closed-form value, (b) the trapezoidal rule on the same points, and (c) a library oracle (
scipy.integrate.quad) on small inputs before trusting it. Remember the answer is approximate — compare within a tolerance, never with==.
Beginner Tasks (5)¶
Task 1 — Composite Simpson's rule¶
Problem. Implement simpson(f, a, b, n) returning the composite Simpson estimate of ∫_a^b f(x) dx with n (even) subintervals, using the 1-4-2-...-4-1 weighting.
Constraints. - Force n even (bump up by one if odd). - h = (b−a)/n; endpoints weight 1, odd indices 4, even interior indices 2; multiply the sum by h/3.
Hint. s = f(a)+f(b); for i in 1..n-1: s += (i odd?4:2)*f(a+i*h); return s*h/3.
Starter — Go.
package main
import (
"fmt"
"math"
)
func simpson(f func(float64) float64, a, b float64, n int) float64 {
// TODO: composite Simpson
return 0
}
func main() {
fmt.Printf("%.7f\n", simpson(math.Sin, 0, math.Pi, 100)) // expect ~2.0000000
}
Starter — Java.
import java.util.function.DoubleUnaryOperator;
public class Task1 {
static double simpson(DoubleUnaryOperator f, double a, double b, int n) {
// TODO
return 0;
}
public static void main(String[] args) {
System.out.printf("%.7f%n", simpson(Math::sin, 0, Math.PI, 100)); // ~2.0
}
}
Starter — Python.
import math
def simpson(f, a, b, n):
# TODO
return 0.0
if __name__ == "__main__":
print(f"{simpson(math.sin, 0, math.pi, 100):.7f}") # ~2.0000000
- Expected Output:
∫_0^π sin = 2.0. - Evaluation: correctness, even-
nhandling,h/3factor.
Task 2 — Trapezoidal rule (baseline)¶
Problem. Implement trapezoid(f, a, b, n) with the 1-2-...-2-1 weighting. Print the error of both trapezoid and Simpson against the true value of ∫_0^1 e^{-x^2} dx ≈ 0.7468241328 for n = 8.
Constraints. Any n ≥ 1 allowed for the trapezoid.
Evaluation: Simpson's error should be roughly 100× smaller than the trapezoid's on the same n.
Task 3 — Estimate π via integration¶
Problem. Use composite Simpson to estimate π from ∫_0^1 4/(1+x^2) dx = π. Find the smallest even n for which your estimate is within 1e-9 of math.Pi.
Constraints. Loop over n = 2, 4, 8, ... and stop at the first that meets the tolerance; print n and the estimate.
Evaluation: correct π to 9 digits; reasonable n (should be small, a few hundred at most).
Task 4 — Verify degree of precision 3¶
Problem. Write a test that confirms composite Simpson integrates 1, x, x^2, x^3 exactly (to within 1e-9) over [0, 2] for n = 2, but is not exact for x^4.
Constraints. Compare against the closed-form integrals (∫_0^2 x^k = 2^{k+1}/(k+1)).
Evaluation: prints PASS for k = 0..3, FAIL (nonzero error) for k = 4.
Task 5 — Refine until converged¶
Problem. Implement simpson_until(f, a, b, eps) that doubles n (starting at 2) until two successive Simpson estimates differ by less than eps, then returns the latest. Test on ∫_0^1 e^{-x^2} dx.
Constraints. Reuse is optional here; just double n and compare.
Evaluation: returns a value within eps of the truth; report how many doublings it took.
Intermediate Tasks (5)¶
Task 6 — Adaptive Simpson¶
Problem. Implement adaptive Simpson integrate(f, a, b, eps) using the |S2 − S| < 15·eps criterion, the Richardson return S2 + (S2−S)/15, sample reuse (thread fa, fb, fm), and a depth cap of 50.
Constraints. Each recursion must add only two new function evaluations (flm, frm).
Evaluation: matches the library to eps on sin over [0,π], e^{-x^2} over [0,1], and 1/(1+x^2) over [0,1].
Task 7 — Count function evaluations¶
Problem. Extend Task 6 to also return the number of function evaluations. Compare evaluation counts of adaptive Simpson vs fixed-n Simpson (matched to the same final accuracy) on f(x) = 1/(x^2 + 0.001) over [-1, 1] (a sharp spike at 0).
Constraints. Wrap f in a counter; report both counts.
Evaluation: adaptive should use far fewer evaluations than uniform Simpson for the same accuracy on the spiky integrand.
Task 8 — Arc length of a curve¶
Problem. Compute the arc length of y = sin(x) from 0 to π, i.e. ∫_0^π sqrt(1 + cos^2 x) dx, using adaptive Simpson to 10 digits.
Constraints. Define g(x) = sqrt(1 + cos(x)^2).
Evaluation: result ≈ 3.8201977; verify against a library quad.
Task 9 — Normal CDF (probability integral)¶
Problem. Implement normal_cdf(z) = 0.5 + (1/sqrt(2π))·∫_0^z e^{-t^2/2} dt using composite or adaptive Simpson. Validate normal_cdf(0)=0.5, normal_cdf(1.96)≈0.975, normal_cdf(-1.96)≈0.025.
Constraints. Handle negative z by symmetry; integrate from 0 to |z|.
Evaluation: all three checks within 1e-6.
Task 10 — Simpson 3/8 vs 1/3¶
Problem. Implement the basic Simpson 3/8 rule (3h/8)(f0 + 3f1 + 3f2 + f3) (4 nodes, 3 panels) and compare its error to Simpson 1/3 on ∫_0^1 e^{-x^2} dx using a matched number of evaluations.
Constraints. Both are O(h^4); note 3/8 has a slightly larger error constant.
Evaluation: both O(h^4); 1/3 is marginally more accurate per evaluation.
Advanced Tasks (5)¶
Task 11 — Endpoint singularity via substitution¶
Problem. Compute ∫_0^1 1/sqrt(x) dx (true value 2). Direct Simpson fails (f(0)=Inf). Apply the substitution x = t^2 to get ∫_0^1 2 dt = 2, and verify Simpson now nails it. Generalize to ∫_0^1 cos(x)/sqrt(x) dx.
Constraints. Show the naive attempt returns Inf/NaN; the substituted attempt converges.
Evaluation: substituted result within 1e-9 of the true value; library quad agrees.
Task 12 — Infinite domain transform¶
Problem. Compute ∫_0^∞ e^{-x^2} dx = sqrt(π)/2 ≈ 0.8862269. Map [0, ∞) to [0, 1) via x = t/(1−t), dx = dt/(1−t)^2, then integrate with adaptive Simpson.
Constraints. Guard the t → 1 endpoint (where the transformed integrand → 0 but 1/(1−t)^2 → ∞ is tamed by the faster e^{-x^2} decay).
Evaluation: within 1e-6 of sqrt(π)/2; cross-check scipy.integrate.quad(f, 0, inf).
Task 13 — Romberg integration¶
Problem. Implement Romberg integration: build a table R[i][j] where R[i][0] is the trapezoid with 2^i panels and R[i][j] = (4^j·R[i][j-1] − R[i-1][j-1])/(4^j − 1). Show R[i][1] equals composite Simpson, and that higher columns converge faster.
Constraints. Reuse trapezoid samples across rows (each row halves h).
Evaluation: R[i][1] == Simpson to round-off; the table reaches 1e-12 on ∫_0^1 e^{-x^2} in few rows.
Task 14 — Adaptive Simpson with error reporting¶
Problem. Extend adaptive Simpson to return (value, estimated_error, evals, converged) honoring both epsabs and epsrel and a max_evals budget. Return a converged=false status (not an exception) when the budget is exhausted.
Constraints. Accept a leaf when error ≤ max(epsabs, epsrel·|leaf_value|). Sum leaf error estimates for the reported total error.
Evaluation: correct value + error bar on smooth cases; graceful non-converged status on 1/x over [0,1].
Task 15 — Gauss–Legendre comparison¶
Problem. Implement 5-point Gauss–Legendre quadrature on [a,b] (hard-code the 5 nodes/weights on [-1,1] and remap). Compare its accuracy-per-evaluation against composite Simpson on ∫_0^1 e^{-x^2} dx and on a high-degree polynomial x^9 (which 5-point Gauss integrates exactly: degree 2·5−1 = 9).
Constraints. Nodes/weights for n=5 on [-1,1]: x = ±0.9061798459, ±0.5384693101, 0; w = 0.2369268851, 0.4786286705, 0.5688888889 (symmetric).
Evaluation: Gauss is exact on x^9; Simpson is not. On e^{-x^2}, 5-point Gauss beats Simpson with far fewer evaluations.
Benchmark Task¶
Compare error vs work across all 3 languages: how fast does each rule's error fall as you double
n?
Go¶
package main
import (
"fmt"
"math"
)
func main() {
f := func(x float64) float64 { return math.Exp(-x * x) }
const truth = 0.7468241328124270
for _, n := range []int{4, 8, 16, 32, 64, 128, 256} {
t := math.Abs(trapezoid(f, 0, 1, n) - truth)
s := math.Abs(simpson(f, 0, 1, n) - truth)
fmt.Printf("n=%4d trap=%.2e simpson=%.2e\n", n, t, s)
}
}
Java¶
import java.util.function.DoubleUnaryOperator;
public class Benchmark {
public static void main(String[] args) {
DoubleUnaryOperator f = x -> Math.exp(-x * x);
final double truth = 0.7468241328124270;
for (int n : new int[]{4, 8, 16, 32, 64, 128, 256}) {
double t = Math.abs(Task1.simpson(f, 0, 1, n) - truth); // swap in trapezoid for the first column
double s = Math.abs(Task1.simpson(f, 0, 1, n) - truth);
System.out.printf("n=%4d trap=%.2e simpson=%.2e%n", n, t, s);
}
}
}
Python¶
import math
def benchmark():
f = lambda x: math.exp(-x * x)
truth = 0.7468241328124270
for n in (4, 8, 16, 32, 64, 128, 256):
t = abs(trapezoid(f, 0, 1, n) - truth)
s = abs(simpson(f, 0, 1, n) - truth)
print(f"n={n:4d} trap={t:.2e} simpson={s:.2e}")
if __name__ == "__main__":
benchmark()
Expected pattern: the trapezoid error divides by ~4 per doubling (O(h^2)); Simpson divides by ~16 per doubling (O(h^4)) until it flatlines near 1e-15 where floating-point round-off dominates.