A scientific-computing claim needs several kinds of evidence that fail in different ways. This tutorial uses a deliberately suspicious quadrature implementation to compare exact cases, properties, refinement rates, independent algorithms, and a bounded high-precision reference.
Compare two quadrature candidates on constant and affine exact cases.
Test a broad bound and identify what it does not establish.
Construct an independently checked reference for \(e-1\).
Refine three quadrature methods and estimate their observed orders.
Bracket the integral using convexity and different sample locations.
Recompute the analytic value with an exact rational series.
Assemble a claim-evidence-limitation record.
Establish the arithmetic and reporting tools
All calculations are dimensionless. Binary64 candidates use Python float. The standard-library Decimal and Fraction types provide high-precision and exact-rational checks without adding notebook dependencies. The examples are deterministic and require no external data.
from decimal import Decimal, localcontextfrom fractions import Fractionimport mathimport sysD = Decimaldef decimal_error(candidate, reference):"""Return the absolute error of a binary64 value as a Decimal."""returnabs(D.from_float(candidate) - reference)def format_decimal(value):"""Format exact zero plainly and other Decimal values scientifically."""return"0"if value ==0elsef"{value:.5E}"print(f"Python: {sys.version.split()[0]}")print("candidate arithmetic: Python float (binary64)")print("reference arithmetic: Decimal and exact Fraction")
The intended composite trapezoidal rule gives half weight to both endpoints. The suspicious candidate omits the right endpoint and samples every left endpoint with full weight. The midpoint method samples different locations and will later provide a cross-method comparison.
Read the three functions and predict which inputs can distinguish their contracts. math.fsum reduces incidental accumulation error; it does not change the quadrature formula.
def composite_trapezoid(function, lower, upper, subintervals):"""Integrate with the composite trapezoidal rule."""if subintervals <=0:raiseValueError("subintervals must be positive") width = (upper - lower) / subintervals interior = math.fsum( function(lower + index * width)for index inrange(1, subintervals) )return width * (0.5* function(lower)+ interior+0.5* function(upper) )def suspicious_candidate(function, lower, upper, subintervals):"""A candidate claimed to be trapezoidal, but using left endpoints."""if subintervals <=0:raiseValueError("subintervals must be positive") width = (upper - lower) / subintervalsreturn width * math.fsum( function(lower + index * width)for index inrange(subintervals) )def composite_midpoint(function, lower, upper, subintervals):"""Integrate with the composite midpoint rule."""if subintervals <=0:raiseValueError("subintervals must be positive") width = (upper - lower) / subintervalsreturn width * math.fsum( function(lower + (index +0.5) * width)for index inrange(subintervals) )
Prediction: a constant reference case
Both candidates integrate \(f(x)=1\) on \([0,1]\) with eight subintervals. Predict whether the constant case can detect the missing right-endpoint contribution. The exact integral is one.
candidate value absolute error
trapezoidal 1.0000000 0
suspicious 1.0000000 0
Both candidates return one exactly. This verifies the interval width and basic accumulation for one simple case, but it does not exercise the endpoint weights. A passing test supports only the behaviour it actually distinguishes.
Prediction: affine exactness
The trapezoidal rule integrates every affine function exactly in exact arithmetic. For \(f(x)=x\) on \([0,1]\), the reference is \(1/2\). Predict which candidate now satisfies the claimed contract and how the missing endpoint affects the other result.
candidate value absolute error
trapezoidal 0.5000000 0
suspicious 0.4375000 6.25000E-2
The intended rule returns \(0.5\) exactly for these binary64 inputs. The suspicious result is \(0.4375\), exposing the left-endpoint rule. Constant exactness was too broad; affine exactness directly tests the trapezoidal contract.
Test a necessary but insufficient bound
For \(f(x)=e^x\) on \([0,1]\), every sampled value lies between \(1\) and \(e\). Each candidate uses non-negative weights that sum to one, so every estimate must satisfy
\[
1\le Q_n\le e.
\]
Predict whether this broad property rejects the suspicious candidate. A pass means that an impossible result was not observed; it does not identify which quadrature rule was implemented.
All three estimates pass. The property is still useful because it could reject any value outside \([1,e]\), including \(0.5\) or a value larger than \(e\). Its success cannot repair the failed affine-exactness evidence.
Construct and check a decimal reference
The analytic answer is \(e-1\). Evaluate it independently at 80 and 100 decimal digits and measure their relative change. The word “analytic” justifies the formula; agreement between precisions checks that the digits used below have stabilized in this decimal calculation.
def decimal_exp_increment(precision):"""Evaluate exp(1)-1 at the requested decimal precision."""with localcontext() as context: context.prec = precisionreturn+(D(1).exp() - D(1))reference_80 = decimal_exp_increment(80)reference_100 = decimal_exp_increment(100)reference_change =abs(reference_80 - reference_100) /abs(reference_100)print(f"100-digit reference: {reference_100:.40f}")print(f"80/100 relative change: {reference_change:.3E}")
If a method has error \(E(h)\approx Ch^p\), halving \(h\) should reduce the error by about \(2^p\). The trapezoidal and midpoint rules are predicted to have order two for this smooth integrand. The diagnosed left-endpoint rule has order one.
Before running the cell, predict the observed orders and whether simply seeing all errors decrease would have been enough to verify the claimed method.
def observed_order(coarse_error, fine_error):"""Estimate p when halving h changes error from coarse to fine."""return math.log2(float(coarse_error / fine_error))quadrature_methods = {"trapezoidal": composite_trapezoid,"midpoint": composite_midpoint,"suspicious": suspicious_candidate,}refinement_levels = [4, 8, 16, 32, 64]refinement_records = []print(f"{'method':>14s}{'n':>4s}{'value':>19s} "f"{'absolute error':>16s}{'order':>7s}")for name, method in quadrature_methods.items(): previous_error =Nonefor subintervals in refinement_levels: value = method(math.exp, 0.0, 1.0, subintervals) error = decimal_error(value, reference_100) order = (Noneif previous_error isNoneelse observed_order(previous_error, error) ) refinement_records.append( {"method": name,"subintervals": subintervals,"value": value,"absolute_error": error,"observed_order": order, } ) order_text ="-"if order isNoneelsef"{order:.4f}"print(f"{name:>14s}{subintervals:4d}{value:19.15f} "f"{error:16.5E}{order_text:>7s}" ) previous_error = error
The two intended rules approach order two. The suspicious candidate approaches order one: it converges to the analytic value, but not with the leading error of the method it was claimed to implement. A decreasing error is useful evidence; the predicted rate makes it diagnostic.
The table covers selected resolutions in one arithmetic environment. It is not a proof for every integrand or an assurance that arbitrarily fine grids will remain in the asymptotic regime.
Exercise: add a refinement level
Choose a positive number of subintervals, preferably twice one of the tabulated levels. Predict the ordering of the three estimates and compare each absolute error. If you choose a very small grid, explain whether asymptotic rates should already be expected.
learner_subintervals =128# Change this after writing down a prediction.if learner_subintervals <=0:raiseValueError("learner_subintervals must be positive")for name, method in quadrature_methods.items(): value = method(math.exp, 0.0, 1.0, learner_subintervals) error = decimal_error(value, reference_100)print(f"{name:>14s}: value = {value:.15f}, "f"absolute error = {error:.5E}" )
trapezoidal: value = 1.718290568083478, absolute error = 8.73962E-6
midpoint: value = 1.718277458650163, absolute error = 4.36981E-6
suspicious: value = 1.711578529691060, absolute error = 6.70330E-3
Use convexity to bracket the answer
For a convex function, the composite midpoint estimate lies below the exact integral and the composite trapezoidal estimate lies above it. This produces a reference-free bracket once the implementations and convexity assumption are trusted.
Predict how the bracket width changes when the number of subintervals doubles. The decimal reference is shown only to check the expected ordering in this teaching example.
Every tested bracket contains the reference, and doubling the grid reduces its width by approximately four. Midpoint and trapezoidal quadrature have different sample locations and leading-error signs, but these implementations still share the same language, integrand, interval, and similar loop structure. That limits their independence.
Check the reference with a bounded exact series
Use
\[
e-1=\sum_{k=1}^{\infty}\frac{1}{k!}.
\]
Fraction forms the partial sum \(P_N\) exactly. For the omitted tail,
The reference gap is approximately \(8.6522\times10^{-18}\) and the bound is approximately \(8.6533\times10^{-18}\). The decimal reference lies inside the independently bounded interval. This is stronger than agreement between two nearly identical loops, although it still verifies only this analytic calculation—not a physical model.
Assemble a claim-evidence-limitation record
The final record keeps successful and failed checks together. It does not collapse them to validated=True: each result supports or rejects a particular claim and retains its scope.
trapezoidal_orders = [ record["observed_order"]for record in refinement_recordsif record["method"] =="trapezoidal"and record["observed_order"] isnotNone]suspicious_orders = [ record["observed_order"]for record in refinement_recordsif record["method"] =="suspicious"and record["observed_order"] isnotNone]evidence_record = [ {"claim": "basic constant case is integrated","observation": "both candidates return 1 exactly","status": "supported for this case","limitation": "endpoint weights are not distinguished", }, {"claim": "suspicious candidate is trapezoidal","observation": "affine result is 0.4375 instead of 0.5","status": "rejected","limitation": "diagnoses the tested implementation and interval", }, {"claim": "intended rules show predicted refinement","observation": (f"final observed order: trapezoidal {trapezoidal_orders[-1]:.4f}; "f"suspicious {suspicious_orders[-1]:.4f}" ),"status": "supported on tested grids","limitation": "other integrands and finer grids remain untested", }, {"claim": "analytic integral is not method-specific","observation": "convex brackets and bounded series contain reference","status": "supported for this problem","limitation": "computational checks do not validate a physical model", },]for index, item inenumerate(evidence_record, start=1):print(f"evidence item {index}")for key, value in item.items():print(f" {key}: {value}")
evidence item 1
claim: basic constant case is integrated
observation: both candidates return 1 exactly
status: supported for this case
limitation: endpoint weights are not distinguished
evidence item 2
claim: suspicious candidate is trapezoidal
observation: affine result is 0.4375 instead of 0.5
status: rejected
limitation: diagnoses the tested implementation and interval
evidence item 3
claim: intended rules show predicted refinement
observation: final observed order: trapezoidal 2.0000; suspicious 0.9962
status: supported on tested grids
limitation: other integrands and finer grids remain untested
evidence item 4
claim: analytic integral is not method-specific
observation: convex brackets and bounded series contain reference
status: supported for this problem
limitation: computational checks do not validate a physical model
What the experiments establish
The exact cases confirm that the intended trapezoidal implementation satisfies constant and affine exactness, while the suspicious candidate does not satisfy the claimed method contract. The refinement study observes the predicted second-order trend for midpoint and trapezoidal quadrature and the diagnosed first-order trend for the left-endpoint candidate. Convexity brackets the exact integral, and an exact rational series with a proved tail bound checks the decimal reference through a different formulation.
The evidence is deliberately bounded. It uses one smooth integrand on one interval, similar local implementations for the quadrature methods, and no experimental observations. It does not validate physical modelling choices, input uncertainty, behaviour at discontinuities or singularities, or results in another computing environment.
Reflection questions
Which claim does the constant case support, and which claim does it fail to test?
Why does observing first-order convergence diagnose more than observing a decreasing error?
Which assumptions and code paths are shared by the midpoint and trapezoidal comparisons?
Why does increasing decimal precision not by itself establish an authoritative reference?
What external evidence would be required if \(e^x\) represented a physical rate model rather than a dimensionless test function?
Suggested answers
It supports basic interval scaling and accumulation for that input, but does not distinguish the endpoint weighting required by the trapezoidal rule.
The rate tests the leading error predicted for the claimed method. The suspicious result improves, but at the rate of another algorithm.
They share the integrand, interval, binary64 arithmetic, language, and similar loop structure; their sample locations and leading-error signs differ.
A higher-precision calculation can share the same unstable formulation, defect, or modelling assumption. Precision stability and an independent property, bound, or method are stronger evidence.
Measurements or trusted observations with units, uncertainty, calibration, and an intended-use regime, together with sensitivity to uncertain inputs and modelling choices.
Takeaways
Begin with a claim and choose evidence that can actually challenge it.
A passing reference case or invariant establishes only its declared scope.
Refinement rates can distinguish algorithms that converge to the same limit.
Independence depends on shared mathematics, code, data, and assumptions.
Check high-precision references with precision sweeps, bounds, or other formulations.
Preserve claims, observations, and limitations as a reviewable portfolio.