Definite integrals, trapezoid, Simpson
Overview
Section titled “Overview”| Module | M01.4 · build · Python · Pass 5 · 2 to 3 h |
| You build | python/tinyllm/num/integrate.py: trapezoid, trapezoid_rule, simpson |
| Contract | course/contracts/py/tinyllm/num/integrate.pyi |
| Tests | course/tests/M01.4/test_integrate.py (what they check: section 4) |
| Needs | nothing to call. Reading: M01.1 finite differences (Richardson extrapolation, section 2.6 here) |
| Used by | M07.7 computes ROC-AUC as trapezoid(tpr, fpr) · later ethics.04 integrates the safety report’s ROC and reliability curves the same way |
| Milestone | MS-P5 (the Pass 5 gate) |
| Optional depth | OpenStax, Calculus Volume 1 (free), sections 5.1 to 5.3 (the definite integral and the fundamental theorem) and 3.6 of Volume 2 (numerical integration); Sauer, Numerical Analysis, sections 5.2 and 5.3 (Newton-Cotes rules and Romberg) |
Key Takeaways
Section titled “Key Takeaways”- The definite integral is the signed area under , the limit of sums of thin slices; a computer stops at slices of width (
test_hand_example). - The trapezoid rule joins neighbouring points by straight lines: error proportional to , exact on lines (
test_trapezoid_error_order_two,test_trapezoid_rule_exact_on_lines). - Simpson’s rule fits a parabola through each three points: error proportional to and, by a symmetry bonus, exact on cubics (
test_simpson_error_order_four,test_simpson_exact_on_cubics). - Simpson is Richardson extrapolation of the trapezoid rule: , the same error-cancelling step as
M01.1(test_simpson_is_richardson_of_trapezoid). - Samples that are not on a grid, such as the corners of an ROC curve, are integrated step by step with each step’s own signed width (
test_trapezoid_samples_match_scipy).
How to work this chapter
Section titled “How to work this chapter”ol start M01.4 # stubs python/tinyllm/num/integrate.py into your repool tests M01.4 # read the test catalog first: rung R0, you write no tests hereol check M01.4 # exit code is the verdictol diff M01.4 # after passing: your code against the reference1. Why now
Section titled “1. Why now”Pass 5 trains classifiers for the first time: BERT and ELECTRA heads in L6.3 and L6.5, and the usage-policy head the gateway will run (D33). A classifier outputs a score, and the question “how good is this score at separating the two classes?” has a standard answer that does not depend on any one threshold: the area under the ROC curve, which M07.7 computes. That curve is not a formula; it is a list of corner points, one per threshold, and its area is a definite integral over samples. The same integral computes the expected calibration area in the safety report (ethics.04). Get the integration rule subtly wrong (count each step at its left height, or sort points that were deliberately ordered) and every AUC in the model zoo is biased, with nothing visibly broken. This module builds the area rule for samples and, for functions you can evaluate anywhere, the two classic rules with predictable errors.
2. Principles
Section titled “2. Principles”| Symbol | Meaning | Type / shape |
|---|---|---|
| a function of one real variable, vectorized over float64 arrays | Callable[[NDArray], NDArray] | |
| the interval of integration; is allowed | float, float | |
| the definite integral: the signed area between and the axis | float | |
| an antiderivative of : | function | |
| the number of equal steps | int | |
| the step, | float | |
the nodes , (numpy.linspace(a, b, n + 1)) | float64[n + 1] | |
float64[n + 1] | ||
| , | the composite trapezoid and Simpson values with step | float |
| at most a constant times as |
2.1 The definite integral
Section titled “2.1 The definite integral”Cut into slices of width , stand a rectangle on each slice with the height of somewhere in it, and add the areas: . This is a Riemann sum. As the sums for a continuous settle on one number whatever points you chose, and that number is the definite integral . Area below the axis counts as negative, which is why it is a signed area. Running from to makes every width , so , and an interval of length 0 has integral 0.
The fundamental theorem of calculus links this area to derivatives (M01.1): if , then . For , , so . Most integrals you meet in practice have no in closed form (, the normal density, is the famous one) or is only known at sample points. Then you compute the area numerically, which is called quadrature.
2.2 The trapezoid
Section titled “2.2 The trapezoid”Replace on one step by the straight line through its two end values. The region under that line is a trapezoid with parallel sides and and width , so its area is : the width times the average height. A straight line is integrated exactly, because the “approximation” is the function itself.
2.3 The composite rules
Section titled “2.3 The composite rules”Add one trapezoid per step. Every inner node belongs to two trapezoids and is counted twice at half weight; the two end nodes belong to one each:
Simpson’s rule instead takes the steps in pairs and fits the parabola through the three points . The area under that parabola, over a panel of width , is (integrate the parabola through term by term to check it). Adding the panels, every even inner node is shared by two panels:
The pattern 1, 4, 2, 4, …, 2, 4, 1 only closes when is even. An odd is not a smaller Simpson’s rule; it is a different, wrong one, so the contract raises instead of rounding.
2.4 Integrating samples
Section titled “2.4 Integrating samples”An ROC curve is a list of points with uneven gaps, produced by sweeping a threshold. Its area is the trapezoid rule applied step by step with each step’s own width: . The widths keep their sign, so the order of the points is the direction of travel: the same points in decreasing order give the negative area, exactly as running an integral from to . The function never sorts, because sorting would silently reconnect the points in a different order (the curve the caller drew is the curve that gets measured). Fewer than two points enclose nothing: the area is 0.
2.5 Error and order
Section titled “2.5 Error and order”Expand around the middle of one step with Taylor’s formula (M01.1 section 2.2, proved in M02.1). The straight line misses the curvature term, and integrating the difference over one step gives an error of for some in the step. There are steps, so the composite error is
The trapezoid rule is second order: halve and the error drops by 4. Simpson is fourth order: halve and it drops by 16. A parabola should only be exact on quadratics, yet the error involves , so Simpson is exact on cubics too: the error on the left half of a panel cancels the one on the right half by symmetry. On :
| 2 | 4 | 8 | 16 | 32 | |
|---|---|---|---|---|---|
| trapezoid error | |||||
| Simpson error |
Each column divides the trapezoid error by 4 and the Simpson error by 16: the slopes 2 and 4 the order tests measure. The error formulas assume a smooth at the scale of . On Runge’s peak over with , Simpson’s error is 0.026 and the trapezoid’s 0.0075: the parabolas overshoot a peak that is narrower than two steps. Higher order pays off only once resolves the function.
2.6 Simpson is Richardson
Section titled “2.6 Simpson is Richardson”The trapezoid error is not just ; it is a series in even powers, (the Euler-Maclaurin formula). That is the same shape as the central difference of M01.1, so the same step removes the leading term:
Write it out node by node and the weights come out as 1, 4, 2, 4, …, 1 over 3: it is Simpson’s rule on the finer grid. Repeating the step (weights ) is Romberg integration. The test test_simpson_is_richardson_of_trapezoid checks this identity to rounding.
3. Worked example by hand
Section titled “3. Worked example by hand”Take .
| Rule | Nodes and values | Computation | Value | Error |
|---|---|---|---|---|
| , , | 5 | 1 | ||
| , , | 4.25 | 0.25 | ||
| , , | 4 | 0 | ||
| Richardson | , | 4 | 0 |
Halving divided the trapezoid error by exactly 4 (here and the error formula is exact up to the mean value). Simpson is exact because is a cubic, and Richardson’s combination of the two trapezoid values gives the same 4.
Now three samples, the corners of an ROC curve: , , . Two trapezoids: and , total 0.625. Counting each step at its left height only (a left Riemann sum) gives . These numbers are the first two tests, test_hand_example and test_hand_example_samples.
4. The interface
Section titled “4. The interface”def trapezoid(y: ArrayLike, x: ArrayLike) -> float: ...def trapezoid_rule(f: Callable[[NDArray], NDArray], a: float, b: float, n: int) -> float: ...def simpson(f: Callable[[NDArray], NDArray], a: float, b: float, n: int) -> float: ...trapezoid integrates samples in the order given (numpy’s trapezoid(y, x)). The two rules evaluate f once on numpy.linspace(a, b, n + 1), so both end nodes are exact and every implementation agrees with scipy to the last bit. All three return a Python float and raise ValueError for non-finite input, mismatched samples, a wrongly shaped or non-finite f, n < 1, or (for Simpson) an odd n.
What the tests check
Section titled “What the tests check”| Test | KIND | Checks | Why it matters downstream |
|---|---|---|---|
test_hand_example | unit, smoke | section 3: 5, 4.25, 4, 4 | you and the tests agree on the rules |
test_hand_example_samples | unit, smoke | the ROC corners give 0.625 | roc_auc in M07.7 is this call |
test_rules_match_scipy | golden | scipy.integrate.trapezoid and simpson on the same nodes, six functions and a reversed interval | the rules are the standard ones |
test_trapezoid_samples_match_scipy | golden | uneven, unsorted, and decreasing samples | ROC and reliability curves |
test_trapezoid_error_order_two | property | log-log error slope 2 | section 2.5 |
test_simpson_error_order_four | property | log-log error slope 4 | section 2.5 |
test_simpson_exact_on_cubics | property | 40 random cubics, | the symmetry bonus |
test_trapezoid_rule_exact_on_lines | property | exact on lines, not on | section 2.2 |
test_simpson_is_richardson_of_trapezoid | property | to rounding | section 2.6 |
test_reversed_and_empty_intervals | boundary | , , decreasing samples negative | signed areas |
test_f_called_once_on_linspace_nodes | unit | one vectorized call on the contract’s nodes | bit-identical results across languages |
test_returns_python_float | unit | a float, not a numpy scalar | callers compare and serialize it |
test_trapezoid_short_inputs | boundary, smoke | zero or one sample gives 0.0 | a one-threshold ROC curve |
test_rejects_bad_arguments | boundary | odd or non-positive n, non-finite limits, bad f, bad samples raise | bugs surface at the call |
5. Pitfalls
Section titled “5. Pitfalls”| Pitfall | Symptom | Caught by |
|---|---|---|
| 1. a Riemann sum (left heights only) for samples | the hand ROC area is 0.375, not 0.625; AUCs biased low | test_hand_example_samples (mutant s01) |
| 2. Simpson weights 2 and 4 swapped | order 2 instead of 4; the hand example gives 10/3 | test_hand_example, test_simpson_exact_on_cubics (mutant s02) |
| 3. accepting an odd number of Simpson steps | a plausible but wrong number | test_rejects_bad_arguments (mutant s03) |
| 4. counting the end points fully in the trapezoid rule | the hand example gives 9, order 1 | test_hand_example (mutant s04) |
| 5. sorting samples or using | a decreasing curve has positive area; unsorted samples are reconnected | test_trapezoid_samples_match_scipy (mutant s05) |
| 6. nodes instead of | the last step is lost; the rules disagree with scipy | test_rules_match_scipy, test_f_called_once_on_linspace_nodes (mutant s06) |
| 7. Simpson with the panel width in place of the step | every result doubled | test_hand_example (mutant s07) |
| 8. assuming equal gaps between samples | wrong area on any real ROC curve | test_trapezoid_samples_match_scipy (mutant s08) |
6. Where it’s used next
Section titled “6. Where it’s used next”| Direction | Module | How it uses this |
|---|---|---|
| Back | M01.1 | Richardson extrapolation and the Taylor error terms of section 2.5 (reading) |
| Forward | M07.7 | roc_auc(scores, labels) builds the ROC corners and returns trapezoid(tpr, fpr) |
| Forward | ethics.04 | the safety report’s ROC area and reliability curve |
| Forward | S-M07d | q8 computes an AUC by hand, the same area as pairs ranked correctly |
If you skip this module, ol check M07.7 stops with BLOCKED ... needs M01.4: build it, or pass --ref-deps.
Going further
Section titled “Going further”| Your piece | Production equivalent | What it adds | Where to look |
|---|---|---|---|
trapezoid | numpy.trapezoid, scipy.integrate.trapezoid | integration along any axis of an N-d array | numpy/lib/_function_base_impl.py |
simpson | scipy.integrate.simpson | uneven sample spacing and an odd number of intervals (a corrected last panel) | scipy/integrate/_quadrature.py |
simpson with Richardson | Romberg integration, scipy.integrate.quad | adaptive Gauss-Kronrod: more nodes only where the error estimate is large | QUADPACK (qags) |
roc_auc via trapezoid | sklearn.metrics.roc_auc_score | the same corners and trapezoids, plus multiclass averaging | sklearn/metrics/_ranking.py (auc) |