Skip to content
The Lumenary
Grades 9–12Ages 15–18 80 min

Mathematical modelling with functions

Simplify, formulate, solve, interpret, validate — and learn to tell a useful model from a merely flattering one.

Formally verified · 11 LeanComputationally tested · 2 PythonAI-drafted · awaiting human reviewMathematically reviewed: Awaiting human review

Your guides: Chloe, Margaret, Gideon

MotivationH

Three questions, three functions

  • A taxi company in Nairobi charges a fixed fee plus an amount per kilometre. What will a 1212 km trip cost?
  • A bus driver in Mumbai sees a child step into the road. How far does the bus travel before it stops, and how much farther at twice the speed?
  • A family in Lima saves in an account that pays interest every year. When will their savings double?

Each question is about the real world, and each has an answer that comes from a function: linear for the taxi, quadratic for the stopping distance, exponential for the savings. But no formula is a taxi, a bus or a bank account. To get from the situation to a formula we must simplify: decide what matters, what to ignore and what to assume. To trust the answer we must check it against reality. This lesson is about that whole journey, which is called mathematical modelling.

IntuitionH

The modelling cycle

Real situationModelResultsPredictionssimplify, formulatesolveinterpretvalidate,revise
The modelling cycle. Simplify the situation and formulate a model; solve the mathematics; interpret the results in the situation; validate them against reality. If the predictions fail, revise the assumptions and go round again.
  1. Simplify. Decide which features matter and state your assumptions ("the fare is the same at every time of day", "the road is dry", "the interest rate stays the same").
  2. Formulate. Choose variables and a function that links them.
  3. Solve. Do the mathematics: evaluate, solve an equation, find a maximum.
  4. Interpret. Translate the answer back: units, sensible ranges, what it means.
  5. Validate. Compare predictions with observations the model was not built from. If they disagree, revise the assumptions and go round again.

The statistician George Box put it memorably in 1979: "all models are wrong, but some are useful". A model leaves things out on purpose, and it is judged by whether it serves its purpose, not by whether it is "true".

DefinitionsH

Definitions

Definition— mathematical model

A mathematical model is a simplified mathematical description of a real situation, together with its assumptions. It relates variables (quantities that change, such as time or distance) through a function whose parameters (constants such as a rate or a starting value) are chosen to suit the situation.

Definition— fit, residual and validation

Fitting a model means choosing its parameters to match data. The residual of a data point is observed value minus predicted value. Validation compares a model's predictions with observations, preferably ones not used in fitting.

Definition— interpolation and extrapolation

Using a model inside the range of the data used to build it is interpolation; using it outside that range is extrapolation.

ProofH

Fingerprints of three families

Suppose a quantity is measured at equally spaced times 0,1,2,…0, 1, 2, \dots, giving values u0,u1,u2,…u_0, u_1, u_2, \dots How can a table tell you which kind of function to try?

Theorem— Constant differences mean linear

If un+1−un=du_{n+1} - u_n = d for every nn, then un=u0+ndu_n = u_0 + nd for every nn.

Proof

Induction: true for n=0n = 0, and if un=u0+ndu_n = u_0 + nd then un+1=un+d=u0+(n+1)du_{n+1} = u_n + d = u_0 + (n + 1)d.

∎

Theorem— Constant ratios mean exponential

If un+1=b unu_{n+1} = b\,u_n for every nn, then un=u0 bnu_n = u_0\, b^n for every nn.

Proof

Induction: un+1=b un=b u0bn=u0bn+1u_{n+1} = b\,u_n = b\,u_0 b^n = u_0 b^{n+1}.

∎

Theorem— Constant second differences mean quadratic

If the differences of the differences are constant, un+2−2un+1+un=cu_{n+2} - 2u_{n+1} + u_n = c for every nn, then

un=u0+n (u1−u0)+c n(n−1)2,u_n = u_0 + n\,(u_1 - u_0) + c\,\frac{n(n-1)}{2},

a quadratic function of nn.

Proof

We prove the formula for nn and for n+1n + 1 together, by induction on nn. For n=0n = 0 both are immediate. If the formula holds for unu_n and un+1u_{n+1}, then un+2=c+2un+1−unu_{n+2} = c + 2u_{n+1} - u_n, and substituting the two formulas and simplifying gives the formula for n+2n + 2.

∎

Example— Reading a table

nn01234
A710131619
B25101726
C36122448

Column A has constant differences 33: linear, un=7+3nu_n = 7 + 3n. Column B has differences 3,5,7,93, 5, 7, 9 and constant second differences 22: quadratic, un=2+3n+n(n−1)=n2+2n+2u_n = 2 + 3n + n(n - 1) = n^2 + 2n + 2. Column C has constant ratio 22: exponential, un=3⋅2nu_n = 3 \cdot 2^n. Real data are noisy, so in practice you look for differences or ratios that are roughly constant.

Theorem— Two points determine a line

If x1≠x2x_1 \ne x_2, there is exactly one line y=mx+cy = mx + c through (x1,y1)(x_1, y_1) and (x2,y2)(x_2, y_2), namely m=y2−y1x2−x1m = \dfrac{y_2 - y_1}{x_2 - x_1} and c=y1−mx1c = y_1 - m x_1.

Proof

These values work: mx1+c=y1mx_1 + c = y_1, and mx2+c=y1+m(x2−x1)=y2mx_2 + c = y_1 + m(x_2 - x_1) = y_2. If another line m′x+c′m'x + c' passes through both points, subtracting the two equations gives (m−m′)(x1−x2)=0(m - m')(x_1 - x_2) = 0, so m=m′m = m', and then c=c′c = c'.

∎

Theorem— Doubling time

A quantity AertA e^{rt} with A>0A > 0 and continuous growth rate r>0r > 0 doubles in time T=ln⁡2r≈0.693rT = \dfrac{\ln 2}{r} \approx \dfrac{0.693}{r}, whatever its starting value. With yearly compounding at rate rr the doubling time is ln⁡2ln⁡(1+r)\dfrac{\ln 2}{\ln(1 + r)}, which is at least as long.

Proof

AerT=2A  ⟺  erT=2  ⟺  rT=ln⁡2Ae^{rT} = 2A \iff e^{rT} = 2 \iff rT = \ln 2. For yearly compounding, (1+r)T=2  ⟺  T=ln⁡2ln⁡(1+r)(1 + r)^T = 2 \iff T = \tfrac{\ln 2}{\ln(1 + r)}. Since 1+r≤er1 + r \le e^r, we have ln⁡(1+r)≤r\ln(1 + r) \le r, and dividing ln⁡2\ln 2 by a smaller positive number gives a larger answer.

∎

At r=7%r = 7\% the doubling time is about 0.693/0.07≈9.90.693 / 0.07 \approx 9.9 years. This is the "rule of 70": divide 7070 by the percentage rate.

Worked examplesH

Building models

Example— Linear: the taxi fare (illustrative numbers)

Suppose a 44 km trip costs 500500 shillings and a 1010 km trip costs 950950. Assuming the fare is a fixed fee plus a constant amount per kilometre, the fare is linear in the distance xx. By the two-point theorem the slope is m=950−50010−4=75m = \tfrac{950 - 500}{10 - 4} = 75 shillings per km, and the intercept is c=500−75⋅4=200c = 500 - 75 \cdot 4 = 200 shillings. So F(x)=200+75xF(x) = 200 + 75x, and a 1212 km trip costs F(12)=1100F(12) = 1100 shillings.

Interpret: 7575 is the price per kilometre and 200200 is the fixed fee. F(0)=200F(0) = 200 is the price of getting in and going nowhere. The model is meaningful only for x≥0x \ge 0, and it ignores waiting time, surge pricing and tolls.

Example— Quadratic: stopping distance

A vehicle's stopping distance has two parts. During the driver's reaction time trt_r the vehicle keeps its speed vv and travels v trv\,t_r. While braking with constant deceleration aa it travels v22a\tfrac{v^2}{2a}. With the assumptions tr=1t_r = 1 s and a=7 m/s2a = 7\ \text{m/s}^2 (a dry road),

d(v)=v+v214metres, for v in m/s.d(v) = v + \frac{v^2}{14} \quad \text{metres, for } v \text{ in m/s}.

At 5050 km/h (13.913.9 m/s), d≈13.9+13.8=27.7d \approx 13.9 + 13.8 = 27.7 m. At 100100 km/h (27.827.8 m/s), d≈27.8+55.1=82.9d \approx 27.8 + 55.1 = 82.9 m: doubling the speed doubles the reaction distance but quadruples the braking distance. On a wet road (a≈4 m/s2a \approx 4\ \text{m/s}^2, say) the braking distance would be much longer. Changing an assumption changes the prediction, and a good modeller reports both.

Example— Exponential: savings and a warning from biology

Savings growing at 5%5\% a year follow S(t)=S0⋅1.05tS(t) = S_0 \cdot 1.05^t and double in ln⁡2ln⁡1.05≈14.2\tfrac{\ln 2}{\ln 1.05} \approx 14.2 years.

Exponential models can be spectacularly right for a while and then spectacularly wrong. In ideal laboratory conditions a bacterium such as E. coli can divide about every 2020 minutes. If that continued for 4848 hours, one cell would become 2144≈2×10432^{144} \approx 2 \times 10^{43} cells, weighing far more than the Earth. In reality nutrients run out and growth slows. A better model then is the logistic model, in which the growth rate falls as the population approaches a limit (see mathematical biology).

ProofH

Fitting a line to data: least squares

Real data never lie exactly on a line. Which line fits "best"? The standard answer, due to Legendre and Gauss, is the line that makes the sum of squared residuals

S(m,c)=∑i=1n(yi−(mxi+c))2S(m, c) = \sum_{i=1}^{n} \big(y_i - (m x_i + c)\big)^2

as small as possible. Squaring treats overshoots and undershoots alike and punishes large misses heavily.

Theorem— The best intercept is set by the means

Fix a slope mm and put ri=yi−mxir_i = y_i - m x_i, with mean rˉ=1n∑ri=yˉ−mxˉ\bar r = \tfrac1n \sum r_i = \bar y - m \bar x. Then for every cc,

∑i(ri−c)2=∑i(ri−rˉ)2+n (c−rˉ)2.\sum_{i} (r_i - c)^2 = \sum_{i} (r_i - \bar r)^2 + n\,(c - \bar r)^2.

So, for a given slope, the sum of squared residuals is smallest exactly when c=yˉ−mxˉc = \bar y - m\bar x: the best line passes through the point of means (xˉ,yˉ)(\bar x, \bar y).

Proof

Write ri−c=(ri−rˉ)+(rˉ−c)r_i - c = (r_i - \bar r) + (\bar r - c) and expand:

∑(ri−c)2=∑(ri−rˉ)2+2(rˉ−c)∑(ri−rˉ)+n(rˉ−c)2.\sum (r_i - c)^2 = \sum (r_i - \bar r)^2 + 2(\bar r - c)\sum (r_i - \bar r) + n(\bar r - c)^2.

The middle sum is ∑ri−nrˉ=0\sum r_i - n\bar r = 0: deviations from the mean add up to zero. The last term is never negative and is zero only when c=rˉc = \bar r.

∎

Now substitute c=yˉ−mxˉc = \bar y - m\bar x. Writing Sxx=∑(xi−xˉ)2S_{xx} = \sum (x_i - \bar x)^2, Sxy=∑(xi−xˉ)(yi−yˉ)S_{xy} = \sum (x_i - \bar x)(y_i - \bar y) and Syy=∑(yi−yˉ)2S_{yy} = \sum (y_i - \bar y)^2, the sum of squares becomes

∑((yi−yˉ)−m(xi−xˉ))2=Syy−2mSxy+m2Sxx,\sum \big((y_i - \bar y) - m(x_i - \bar x)\big)^2 = S_{yy} - 2m S_{xy} + m^2 S_{xx},

a quadratic in mm with positive leading coefficient SxxS_{xx} (if the xix_i are not all equal). By the vertex formula of the quadratics lesson, its minimum is at

m=SxySxx,c=yˉ−mxˉ.m = \frac{S_{xy}}{S_{xx}}, \qquad c = \bar y - m \bar x.

That is the least-squares line. To fit an exponential P=AertP = A e^{rt}, take logarithms: ln⁡P=ln⁡A+rt\ln P = \ln A + rt is linear in tt, so fit a line to the points (t,ln⁡P)(t, \ln P). This minimises errors on the logarithmic scale, which means roughly relative errors.

ExplanationH

Validation: where models fail

A model earns trust by predicting what it was not built from. Three warnings:

  • Fitting the past is not forecasting the future. In the computation section, a linear and an exponential model both fit world population for 1950–1980 closely, yet their forecasts for 2020 differ by more than 2.52.5 billion people, and both miss.
  • A perfect fit proves nothing. Through any nn data points with different xx-values there is a polynomial of degree less than nn passing exactly through all of them. A model with as many parameters as data points will always fit perfectly, and it typically forecasts nonsense. This is called overfitting.
  • Data cannot decide extrapolation. The theorem below shows that infinitely many models agree exactly with any finite set of data and yet predict any value you like at a new point. What justifies an extrapolation is not the data alone but the reasons behind the model: a mechanism (such as Newton's law of cooling) or a well-tested assumption.

Theorem— Extrapolation is not determined by data

Let ff be any model and x1,…,xnx_1, \dots, x_n the points where we have data. For every number kk, the model

g(x)=f(x)+k (x−x1)(x−x2)⋯(x−xn)g(x) = f(x) + k\,(x - x_1)(x - x_2)\cdots(x - x_n)

agrees with ff at every data point. At any new point x0x_0 (different from all xix_i), choosing k=v−f(x0)(x0−x1)⋯(x0−xn)k = \dfrac{v - f(x_0)}{(x_0 - x_1)\cdots(x_0 - x_n)} makes g(x0)=vg(x_0) = v, for any value vv we like.

Proof

At x=xix = x_i the product contains the factor xi−xi=0x_i - x_i = 0, so g(xi)=f(xi)g(x_i) = f(x_i). At x0x_0 the product is a product of non-zero numbers, hence non-zero, so we may divide by it; substituting the chosen kk gives g(x0)=f(x0)+(v−f(x0))=vg(x_0) = f(x_0) + (v - f(x_0)) = v.

∎

Formal mathematicsF

Modelling facts, checked by Lean

Lean cannot check that a model suits a situation. That is a judgement about the world. But it can check the mathematics inside the modelling cycle. The fingerprints of the three families:

linear_of_const_diffLean 4Proved here from basic factsFormally verified
/-- **Constant differences mean a linear model.** If a quantity measured at times
`0, 1, 2, …` always changes by the same amount `d`, then `u n = u 0 + n d`. Induction on `n`. -/
theorem linear_of_const_diff (u : ℕ → ℝ) (d : ℝ) (h : ∀ n, u (n + 1) - u n = d) (n : ℕ) :
    u n = u 0 + n * d := by
  induction n with
  | zero => simp
  | succ n ih =>
    have := h n
    push_cast
    linarith
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/ModellingWithFunctions.leanTry it in the Lean web editor
exponential_of_const_ratioLean 4Proved here from basic factsFormally verified
/-- **Constant ratios mean an exponential model.** If each value is `b` times the one before,
then `u n = u 0 · bⁿ`. Induction on `n`. -/
theorem exponential_of_const_ratio (u : ℕ → ℝ) (b : ℝ) (h : ∀ n, u (n + 1) = b * u n) (n : ℕ) :
    u n = u 0 * b ^ n := by
  induction n with
  | zero => simp
  | succ n ih => rw [h n, ih, pow_succ]; ring
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/ModellingWithFunctions.leanTry it in the Lean web editor
quadratic_of_const_second_diffLean 4Proved here from basic factsFormally verified
/-- **Constant second differences mean a quadratic model.** If the differences of the
differences are always `c`, then `u n = u 0 + n (u 1 − u 0) + c · n(n − 1)/2`, a quadratic in `n`.
We prove the formula for `n` and `n + 1` together, by induction. -/
theorem quadratic_of_const_second_diff (u : ℕ → ℝ) (c : ℝ)
    (h : ∀ n, u (n + 2) - 2 * u (n + 1) + u n = c) (n : ℕ) :
    u n = u 0 + n * (u 1 - u 0) + c * (n * (n - 1) / 2) := by
  have key : ∀ n : ℕ, u n = u 0 + n * (u 1 - u 0) + c * (n * (n - 1) / 2) ∧
      u (n + 1) = u 0 + (n + 1) * (u 1 - u 0) + c * ((n + 1) * n / 2) := by
    intro n
    induction n with
    | zero => constructor <;> simp
    | succ n ih =>
      obtain ⟨h0, h1⟩ := ih
      refine ⟨by push_cast; linarith, ?_⟩
      have h2 := h n
      push_cast
      linear_combination h2 + 2 * h1 - h0
  exact (key n).1
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/ModellingWithFunctions.leanTry it in the Lean web editor

Fitting. ∃! means "there exists exactly one", and ∑ i, … is a sum over the data points i : Fin n:

line_through_two_pointsLean 4Proved here from basic factsFormally verified
/-- **Two points with different `x` determine exactly one line** `y = m x + c`:
`m = (y₂ − y₁)/(x₂ − x₁)` and `c = y₁ − m x₁`. If two lines pass through both points, subtracting
gives `(m − m')(x₁ − x₂) = 0`, so `m = m'` and then `c = c'`. -/
theorem line_through_two_points {x₁ y₁ x₂ y₂ : ℝ} (hx : x₁ ≠ x₂) :
    ∃! p : ℝ × ℝ, p.1 * x₁ + p.2 = y₁ ∧ p.1 * x₂ + p.2 = y₂ := by
  have hd : x₂ - x₁ ≠ 0 := sub_ne_zero.mpr (Ne.symm hx)
  refine ⟨((y₂ - y₁) / (x₂ - x₁), y₁ - (y₂ - y₁) / (x₂ - x₁) * x₁), ⟨by ring, ?_⟩, ?_⟩
  · field_simp
    ring
  · rintro ⟨m, c⟩ ⟨h₁, h₂⟩
    have hm : m = (y₂ - y₁) / (x₂ - x₁) := by
      field_simp
      linear_combination h₂ - h₁
    simp only [Prod.mk.injEq]
    exact ⟨hm, by rw [← hm]; linarith⟩
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/ModellingWithFunctions.leanTry it in the Lean web editor
least_squares_interceptLean 4Proved here from basic factsFormally verified
/-- **The best intercept is set by the means.** For data `r₁, …, rₙ` (in a line fit,
`rᵢ = yᵢ − m xᵢ` for a chosen slope `m`) and any `c`,
`Σ (rᵢ − c)² = Σ (rᵢ − c̄)² + n (c − c̄)²`, where `c̄ = (Σ rᵢ)/n` is the mean.
So the sum of squared errors is smallest exactly at `c = c̄`, that is,
`c = mean(y) − m · mean(x)`. The proof expands the square; the cross term vanishes because
the deviations from the mean add up to zero. -/
theorem least_squares_intercept {n : ℕ} (hn : 0 < n) (r : Fin n → ℝ) (c : ℝ) :
    ∑ i, (r i - c) ^ 2 =
      ∑ i, (r i - (∑ j, r j) / n) ^ 2 + n * (c - (∑ j, r j) / n) ^ 2 := by
  set cbar := (∑ j, r j) / n with hcbar
  have hn' : (n : ℝ) ≠ 0 := by exact_mod_cast hn.ne'
  -- the deviations from the mean add up to zero
  have hdev : ∑ i, (r i - cbar) = 0 := by
    rw [Finset.sum_sub_distrib, Finset.sum_const, Finset.card_univ, Fintype.card_fin,
      nsmul_eq_mul, hcbar]
    field_simp
    ring
  have hexpand : ∀ i, (r i - c) ^ 2 = (r i - cbar) ^ 2 + 2 * (cbar - c) * (r i - cbar) +
      (c - cbar) ^ 2 := fun i => by ring
  simp_rw [hexpand, Finset.sum_add_distrib, ← Finset.mul_sum, hdev, Finset.sum_const,
    Finset.card_univ, Fintype.card_fin, nsmul_eq_mul]
  ring
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/ModellingWithFunctions.leanTry it in the Lean web editor
least_squares_minLean 4Proved here from basic factsFormally verified
/-- **Corollary:** no intercept does better than the mean: `Σ (rᵢ − c̄)² ≤ Σ (rᵢ − c)²`. -/
theorem least_squares_min {n : ℕ} (hn : 0 < n) (r : Fin n → ℝ) (c : ℝ) :
    ∑ i, (r i - (∑ j, r j) / n) ^ 2 ≤ ∑ i, (r i - c) ^ 2 := by
  rw [least_squares_intercept hn r c]
  have : 0 ≤ (n : ℝ) * (c - (∑ j, r j) / n) ^ 2 := by positivity
  linarith
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/ModellingWithFunctions.leanTry it in the Lean web editor

Doubling times, using the inequality 1+x≤ex1 + x \le e^x from Mathlib:

doubling_timeLean 4Proved here from basic factsFormally verified
/-- **Doubling time of continuous growth.** A quantity `A eʳᵗ` with `A > 0` and rate `r > 0`
doubles exactly when `t = ln 2 / r`: `A eʳᵗ = 2A ⇔ eʳᵗ = 2 ⇔ rt = ln 2`. At `r = 0.05` (5%) this
is about `13.9` time units: the "rule of 70" says `70/5 = 14`. -/
theorem doubling_time {A r : ℝ} (hA : 0 < A) (hr : 0 < r) (t : ℝ) :
    A * exp (r * t) = 2 * A ↔ t = log 2 / r := by
  constructor
  · intro h
    have h1 : exp (r * t) = 2 := by
      have := mul_left_cancel₀ hA.ne' (h.trans (mul_comm 2 A))
      exact this
    have h2 : r * t = log 2 := by rw [← h1, log_exp]
    field_simp
    linarith
  · intro ht
    rw [ht, mul_div_cancel₀ _ hr.ne', exp_log (by norm_num)]
    ring
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/ModellingWithFunctions.leanTry it in the Lean web editor
yearly_doubling_slowerLean 4Proved here from basic factsFormally verified
/-- **Yearly compounding doubles no faster than continuous growth at the same rate:**
`ln 2 / r ≤ ln 2 / ln(1 + r)` for `r > 0`. The key step is `ln(1 + r) ≤ r`, from Mathlib's
`Real.add_one_le_exp` (`1 + r ≤ eʳ`). -/
theorem yearly_doubling_slower {r : ℝ} (hr : 0 < r) : log 2 / r ≤ log 2 / log (1 + r) := by
  have hlog_pos : 0 < log (1 + r) := log_pos (by linarith)
  have hlog_le : log (1 + r) ≤ r := by
    have h := add_one_le_exp r
    calc log (1 + r) ≤ log (exp r) := log_le_log (by linarith) (by linarith)
      _ = r := log_exp r
  exact div_le_div_of_nonneg_left (log_nonneg (by norm_num)) hlog_pos hlog_le
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/ModellingWithFunctions.leanTry it in the Lean web editor
add_one_le_exp_libraryLean 4Applies a library theoremFormally verified
/-- `1 + x ≤ eˣ` for every real `x` (the tangent line at `0` lies below the graph). This applies
Mathlib's `Real.add_one_le_exp`. -/
theorem add_one_le_exp_library (x : ℝ) : x + 1 ≤ exp x :=
  add_one_le_exp x
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/ModellingWithFunctions.leanTry it in the Lean web editor

And the two theorems about what data cannot tell us. The first comes from Mathlib (Lagrange interpolation); the second is proved here:

perfect_fit_existsLean 4Applies a library theoremFormally verified
/-- **A perfect fit is always available.** For any `n` data points `(xᵢ, yᵢ)` with distinct
`xᵢ`, some polynomial passes through all of them (Lagrange interpolation; its degree can be
taken below `n`). This applies Mathlib's `Polynomial.exists_eval_eq_iff`. So a model that fits
the data perfectly has not yet earned our trust. -/
theorem perfect_fit_exists {n : ℕ} (x y : Fin n → ℝ) (hx : Function.Injective x) :
    ∃ q : Polynomial ℝ, ∀ i, q.eval (x i) = y i :=
  (Polynomial.exists_eval_eq_iff x y).mpr fun i j hij => by rw [hx hij]
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/ModellingWithFunctions.leanTry it in the Lean web editor
extrapolation_undeterminedLean 4Proved here from basic factsFormally verified
/-- **Data cannot decide extrapolation.** Let `f` be any model and `S` the finite set of
`x`-values where we have data. The models `f(x) + k · ∏_{s ∈ S} (x − s)` agree with `f` at every
data point, for every `k`. At a new point `x₀ ∉ S` the product is not zero, so by choosing `k`
we can make the model predict *any* value `v` there. -/
theorem extrapolation_undetermined (f : ℝ → ℝ) (S : Finset ℝ) {x₀ : ℝ} (hx₀ : x₀ ∉ S) (v : ℝ) :
    ∃ k : ℝ, (∀ x ∈ S, f x + k * ∏ s ∈ S, (x - s) = f x) ∧
      f x₀ + k * ∏ s ∈ S, (x₀ - s) = v := by
  have hprod : ∏ s ∈ S, (x₀ - s) ≠ 0 := by
    rw [Finset.prod_ne_zero_iff]
    intro s hs h
    exact hx₀ (sub_eq_zero.mp h ▸ hs)
  refine ⟨(v - f x₀) / ∏ s ∈ S, (x₀ - s), ?_, ?_⟩
  · intro x hx
    have : ∏ s ∈ S, (x - s) = 0 := Finset.prod_eq_zero hx (sub_self x)
    rw [this, mul_zero, add_zero]
  · field_simp
    ring
Axioms used: propext, Classical.choice, Quot.soundformal/Lumenary/ModellingWithFunctions.leanTry it in the Lean web editor
ComputationC

Validating models against data

Linear or exponential? Forecasting world populationPythonNumerical approximationComputationally tested
"""Linear or exponential? Fitting world population and testing the forecast.

We fit two models to approximate world population for 1950-1980 (UN
estimates, rounded to 0.1 billion): a straight line, and an exponential
P = A e^(r t) fitted as a straight line through (t, ln P). Then we compare
each model's forecasts for 1990-2020 with the later estimates. This is how a
model is validated: by predictions it was not built from.
"""

import math

#: World population in billions, approximate UN estimates rounded to 0.1.
YEARS = [1950, 1960, 1970, 1980, 1990, 2000, 2010, 2020]
POPULATION = [2.5, 3.0, 3.7, 4.4, 5.3, 6.1, 7.0, 7.8]

def fit_line(xs, ys):
    """Least-squares line y = m x + c; returns (m, c)."""
    n = len(xs)
    if n < 2 or len(set(xs)) < 2:
        raise ValueError("need at least two different x-values")
    mx, my = sum(xs) / n, sum(ys) / n
    sxx = sum((x - mx) ** 2 for x in xs)
    sxy = sum((x - mx) * (y - my) for x, y in zip(xs, ys))
    m = sxy / sxx
    return m, my - m * mx

def fit_exponential(ts, ps):
    """Fit P = A e^(r t) by a least-squares line through (t, ln P).

    Returns (A, r).
    """
    r, log_a = fit_line(ts, [math.log(p) for p in ps])
    return math.exp(log_a), r

def doubling_time(r):
    """Time for e^(r t) to double: ln 2 / r."""
    if r <= 0:
        raise ValueError("growth rate must be positive")
    return math.log(2) / r

def main():
    k = 4  # fit on 1950-1980 only
    ts = [y - 1950 for y in YEARS]
    m, c = fit_line(ts[:k], POPULATION[:k])
    a, r = fit_exponential(ts[:k], POPULATION[:k])
    print("Models fitted to 1950-1980 (t = years after 1950, P in billions):")
    print(f"  linear:      P = {c:.3f} + {m:.4f} t")
    print(f"  exponential: P = {a:.3f} e^({r:.4f} t)")
    print(f"  (growth {100 * r:.2f}% a year: doubling every"
          f" {doubling_time(r):.0f} years)")
    print()
    print("year  estimate    linear   error  exponential   error")
    for year, t, p in zip(YEARS, ts, POPULATION):
        lin, expo = c + m * t, a * math.exp(r * t)
        tag = "fitted" if year <= 1980 else "forecast"
        print(f"{year}  {p:7.1f}  {lin:8.2f}  {lin - p:+6.2f}  {expo:10.2f}"
              f"   {expo - p:+6.2f}  {tag}")
    print()
    rates = [math.log(POPULATION[i + 1] / POPULATION[i]) / 10
             for i in range(len(YEARS) - 1)]
    print("average growth rate in each decade (% a year):")
    print("  " + "  ".join(f"{YEARS[i]}s {100 * g:.1f}" for i, g in
                           enumerate(rates[:4])))
    print("  " + "  ".join(f"{YEARS[i]}s {100 * g:.1f}" for i, g in
                           enumerate(rates[4:], start=4)))
    print("The exponential model assumes a constant rate. The rate peaked in the")
    print("1960s and has fallen since (decade rates from rounded data are rough),")
    print("so its forecast overshoots. The line (a fixed yearly increase)")
    print("undershoots.")
    print()
    print("Epistemic status: numerical computation on rounded estimates (which")
    print("are themselves revised between UN editions). The comparison shows how")
    print("two models fitted to the same past data can forecast differently; it")
    print("is not a demographic projection.")

if __name__ == "__main__":
    main()
Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.

Both models fit the decades they were built from to within 0.10.1 billion. Only the forecasts tell them apart, and both fail, in opposite directions. The exponential model's assumption of a constant growth rate broke down: the rate peaked in the 1960s and has fallen since. Population researchers use far more detailed models, built from birth rates, death rates and age structure.

Now an experiment where we know the truth, because we make the data ourselves. The readings below are synthetic: generated from Newton's law of cooling with small random errors added.

A perfect fit that predicts nonsensePythonExperimental evidenceNumerical approximationComputationally tested
"""A perfect fit that predicts nonsense: overfitting a cooling cup of tea.

We generate SYNTHETIC data from Newton's law of cooling,
T(t) = 25 + 60 e^(-0.1 t) degrees C, add small seeded random "measurement
errors", and fit three models to the eight readings at t = 0, 2, ..., 14
minutes: a straight line, Newton's model (fitted through ln(T - 25)), and the
degree-7 polynomial that passes through every reading exactly. Then we ask
each model about t = 20 and t = 30 minutes, where the true curve is known.
"""

import math
import random

ROOM = 25.0

def true_temperature(t):
    """The synthetic 'truth': Newton's law with T0 = 85 C, room 25 C, k = 0.1."""
    return ROOM + 60.0 * math.exp(-0.1 * t)

def synthetic_readings(seed=7, noise=0.4):
    """Eight readings at t = 0, 2, ..., 14 with seeded Gaussian noise."""
    rng = random.Random(seed)
    ts = list(range(0, 16, 2))
    return ts, [true_temperature(t) + rng.gauss(0, noise) for t in ts]

def fit_line(xs, ys):
    """Least-squares line y = m x + c; returns (m, c)."""
    n = len(xs)
    mx, my = sum(xs) / n, sum(ys) / n
    m = (sum((x - mx) * (y - my) for x, y in zip(xs, ys))
         / sum((x - mx) ** 2 for x in xs))
    return m, my - m * mx

def fit_newton(ts, temps):
    """Fit T = ROOM + D e^(-k t) via a line through (t, ln(T - ROOM))."""
    slope, intercept = fit_line(ts, [math.log(T - ROOM) for T in temps])
    return math.exp(intercept), -slope

def lagrange(xs, ys, x):
    """Value at x of the polynomial of degree < len(xs) through all points."""
    total = 0.0
    for i, (xi, yi) in enumerate(zip(xs, ys)):
        term = yi
        for j, xj in enumerate(xs):
            if j != i:
                term *= (x - xj) / (xi - xj)
        total += term
    return total

def rms(errors):
    """Root-mean-square of a list of errors."""
    return math.sqrt(sum(e * e for e in errors) / len(errors))

def main():
    ts, temps = synthetic_readings()
    m, c = fit_line(ts, temps)
    d, k = fit_newton(ts, temps)
    models = {
        "line": lambda t: m * t + c,
        "Newton": lambda t: ROOM + d * math.exp(-k * t),
        "degree 7": lambda t: lagrange(ts, temps, t),
    }
    print("SYNTHETIC readings (minutes, degrees C):")
    print("  " + "  ".join(f"{t}:{T:.1f}" for t, T in zip(ts, temps)))
    print(f"Newton fit: T = 25 + {d:.1f} e^(-{k:.4f} t)  (truth: 60, 0.1)")
    print()
    print(f"{'model':>9}  {'RMS error on data':>17}  {'T(20)':>8}  {'T(30)':>8}")
    for name, f in models.items():
        err = rms([f(t) - T for t, T in zip(ts, temps)])
        print(f"{name:>9}  {err:17.3f}  {f(20):8.1f}  {f(30):8.1f}")
    print(f"{'truth':>9}  {'':17}  {true_temperature(20):8.1f}"
          f"  {true_temperature(30):8.1f}")
    print()
    print("The degree-7 polynomial fits the data perfectly (error 0) and")
    print("forecasts temperatures no cup of tea could reach. The line looks")
    print("reasonable on the data but predicts the tea will cool below room")
    print("temperature. Newton's model, which encodes why tea cools, has small")
    print("errors on the data and forecasts well.")
    print()
    print("Epistemic status: one experiment on synthetic data with a known")
    print("truth and a fixed random seed. It illustrates overfitting and")
    print("extrapolation risk; it does not measure how often such models fail,")
    print("and real cooling also depends on things this model ignores.")

if __name__ == "__main__":
    main()
Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.

The polynomial with zero error on the data is the worst model of all. The model with a reason behind it, that tea cools in proportion to how much warmer it is than the room, extrapolates well.

Explore

Explore: fit the population data yourself

The points are the approximate population estimates (in billions) used above. Adjust AA and rr to fit the exponential model Aer(x−1950)A e^{r(x - 1950)}, and mm and cc for the linear model m(x−1950)+cm(x - 1950) + c. Try fitting only the first four points, then look at the rest. Can either model fit all eight points well? Which parameter values describe 1990–2020?

World population: two models

Move the sliders and trace along the curves to read off values.

  • exponential A·e^(r(x − 1950))
  • linear m(x − 1950) + c
  • exponential A·e^(r(x − 1950)): y = 2.0674
  • linear m(x − 1950) + c: y = 1.8
Exercises

Exercises

1. Mathematical modelling with functions

Warm-upHuman

A quantity measured every hour gives 5,15,45,135,4055, 15, 45, 135, 405. Which kind of model fits these values exactly?

Choose one

2. Mathematical modelling with functions

Warm-upHuman

A table of a quadratic model shows 2,5,10,17,262, 5, 10, 17, 26 at n=0,1,2,3,4n = 0, 1, 2, 3, 4. What value does the model give at n=5n = 5?

3. Mathematical modelling with functions

CoreHuman

A 33 km taxi ride costs 340340 shillings and an 88 km ride costs 740740 shillings. Assuming a fixed fee plus a constant charge per km, what is the fixed fee, in shillings?

4. Mathematical modelling with functions

CoreHuman

A town's population grows continuously at 3.5%3.5\% a year, P(t)=P0e0.035tP(t) = P_0 e^{0.035t}. About how long does it take to double?

Choose one

5. Mathematical modelling with functions

CoreHuman

In the model d(v)=v tr+v22ad(v) = v\,t_r + \dfrac{v^2}{2a}, the braking part is v22a\dfrac{v^2}{2a}. If a driver doubles the speed (same road, same brakes), what happens to the braking distance?

Choose one

6. Mathematical modelling with functions

StretchHuman

A student fits a polynomial of degree 99 through all 1010 data points of a sales record, with zero error. What can you conclude about its forecast for next year?

Choose one

7. Mathematical modelling with functions

StretchHuman

In the proof that the best intercept is rˉ\bar r we expand ∑(ri−c)2=∑(ri−rˉ)2+2(rˉ−c)∑(ri−rˉ)+n(rˉ−c)2\sum (r_i - c)^2 = \sum (r_i - \bar r)^2 + 2(\bar r - c)\sum (r_i - \bar r) + n(\bar r - c)^2. Why does the middle term vanish?

Choose one

8. Mathematical modelling with functions

CoreFormal

Which Lean statement says “if each value is bb times the previous one, the model is exponential”? (Here u : ℕ → ℝ lists the measurements.)

Choose one

9. Mathematical modelling with functions

StretchComputational

Write fit_line(xs, ys) that returns the least-squares line as a pair (m, c), using m=Sxy/Sxxm = S_{xy}/S_{xx} and c=yˉ−mxˉc = \bar y - m\bar x, where Sxx=∑(xi−xˉ)2S_{xx} = \sum (x_i - \bar x)^2 and Sxy=∑(xi−xˉ)(yi−yˉ)S_{xy} = \sum (x_i - \bar x)(y_i - \bar y).

Runs entirely in your browser, in an isolated worker. Nothing is sent to a server.
Common misconceptionsH

Common misconceptions

Misconception

Treating a model's output as fact. "The model says the population will be 9.59.5 billion in 2020" is a statement about the model's assumptions (here, a constant growth rate), not about the world. Always report predictions together with the assumptions they rest on.

Misconception

Choosing the model that best fits past data, whatever it is. A model with enough parameters can fit any data perfectly, and the fit says nothing about its forecasts. Prefer models with a plausible mechanism and few parameters, and test them on data they were not fitted to.

Misconception

"A good fit inside the data means it is safe to extrapolate far outside." Extrapolation is where models most often fail. The data constrain a model only where the data are, and the extrapolation theorem shows they cannot pin down predictions anywhere else.

Misconception

"Exponential growth can go on for ever." Every real exponential process eventually meets a limit — food, space, money or people. The exponential model describes a phase, not a destiny.

ViewpointsH

Many ways of seeing

Geometry— Least squares is a perpendicular

List the observations as one vector y=(y1,…,yn)\mathbf{y} = (y_1, \dots, y_n) in nn-dimensional space. The predictions of all lines, (mx1+c,…,mxn+c)(mx_1 + c, \dots, mx_n + c), form a plane through the origin, spanned by 1=(1,…,1)\mathbf{1} = (1, \dots, 1) and x=(x1,…,xn)\mathbf{x} = (x_1, \dots, x_n). The least-squares line picks the point of this plane closest to y\mathbf y, which is the foot of the perpendicular from y\mathbf y to the plane. Perpendicularity to 1\mathbf 1 says the residuals add up to zero (the "means" theorem above); perpendicularity to x\mathbf x gives the slope formula. Fitting is projection (see linear algebra).

Probability— Why squares?

If each measurement equals the true value plus an independent random error with a normal ("bell curve") distribution, then the least-squares line is the line under which the observed data are most likely. Gauss justified least squares in roughly this way in 1809. With other kinds of error, for instance occasional wild outliers, other criteria such as minimising the sum of absolute residuals can be better. Statistics also says how uncertain the fitted parameters are, which is the next question a careful modeller asks (see statistical reasoning).

Physics— Models from mechanisms, and units as a check

Physicists prefer models derived from a mechanism. Newton's law of cooling assumes that heat flows out at a rate proportional to the temperature difference T−TroomT - T_{\text{room}}, and that assumption forces the exponential form Troom+(T0−Troom)e−ktT_{\text{room}} + (T_0 - T_{\text{room}})e^{-kt}. A cheap test of any model is units: in d=vtr+v22ad = v t_r + \tfrac{v^2}{2a}, both terms are lengths (m/s × s, and (m/s)² ÷ m/s²). A formula that adds metres to seconds is wrong before any data are consulted.

History & global context

History and global context

  • Interpolation in astronomy. Calendar makers needed values between their observations. In China, Liu Zhuo (around 600 CE) used second-order interpolation, equivalent to fitting quadratics through equally spaced values. Guo Shoujing's Shoushi calendar (1281) used third-order differences. In India, Brahmagupta's Khaṇḍakhādyaka (665 CE) gave a second-order interpolation rule for sine tables.
  • Least squares. Adrien-Marie Legendre published the method of least squares in 1805. Carl Friedrich Gauss, who said he had used it since 1795, published his probabilistic justification in 1809, after famously predicting where the dwarf planet Ceres would reappear.
  • Population and growth. Thomas Malthus (1798) contrasted populations that grow geometrically with food supplies that grow arithmetically. Pierre-François Verhulst (1838) proposed the logistic model, in which growth slows as a population approaches a limit. In 1927, William Kermack and Anderson McKendrick founded mathematical epidemiology with their model of epidemics.
  • Newton's law of cooling was published, anonymously, by Isaac Newton in 1701.
Further reading

Further reading

  • Edward A. Bender, An Introduction to Mathematical Modeling (1978) — many short, varied models with honest discussion of their assumptions.
  • Frank R. Giordano, Maurice D. Weir and William P. Fox, A First Course in Mathematical Modeling (several editions) — a standard textbook organised around the modelling cycle.
  • COMAP and SIAM, Guidelines for Assessment and Instruction in Mathematical Modeling Education (GAIMME) (2016) — a free guide to modelling at school level.
  • George E. P. Box, "Science and Statistics", Journal of the American Statistical Association (1976) — on the role of models in science.