Numerical Treatment of ODE

The population models built earlier each came with a closed-form solution: Malthusian growth gave a clean exponential, the logistic model an explicit S-curve, the saturation model an exponential approach to a limit. Being able to write the solution down as a formula is the comfortable case, and also the rare one. The overwhelming majority of ordinary differential equations have no closed-form solution at all — there is simply no finite combination of elementary functions that satisfies them. The moment a model grows past a textbook example, this is what happens, and the only way forward is to compute an approximate solution numerically, step by step. This page is about how that is done, and about the ways it can go wrong.

Initial value problems and boundary value problems

A differential equation on its own does not pin down a single solution — it describes a whole family of curves, one for each way of fixing the constants of integration. To single out the one solution we actually want, the equation has to be paired with side conditions. The population-dynamics models are exactly this: one or several ODEs together with enough extra conditions to make the solution unique. There are two standard ways to supply those conditions, and they split ODE problems into two families.

The first fixes the solution at the start of the time interval — we are told the state at the beginning and asked how the system develops from there.

An initial value problem (IVP) is an ODE together with the value of its solution at the start of the time interval. In the scalar case,

y˙(t)=f(t,y(t)),y(a)=ya,ta,\dot y(t) = f(t, y(t)), \qquad y(a) = y_a, \qquad t \ge a,

and for a system of nn coupled ODEs,

y˙i(t)=fi(t,y1(t),,yn(t)),yi(a)=yi,a,ta,i=1,,n.\dot y_i(t) = f_i(t, y_1(t), \dots, y_n(t)), \qquad y_i(a) = y_{i,a}, \qquad t \ge a, \quad i = 1, \dots, n.

The condition y(a)=yay(a) = y_a is the initial value: the known state at the starting time t=at = a, from which the solution is propagated forward.

This is the population-dynamics setting from before. The initial value yay_a plays exactly the role the starting population p0p_0 did — the headcount we measure at the outset and then watch evolve. So y(a)=yay(a) = y_a is not just a piece of notation; it is the concrete starting datum, the analog of p0p_0, that turns the whole family of solution curves into the single one we care about.

The second family fixes the solution at both ends of the interval — we are told where the trajectory starts and where it must end, and asked to find the path between.

A boundary value problem (BVP) is an ODE together with values of its solution at both endpoints of the interval, rather than at the start alone — for instance y(a)=yay(a) = y_a at the left end and y(b)=yby(b) = y_b at the right.

The optimal trajectory of a space shuttle is the standard example: the craft leaves a known launch point and must arrive at a prescribed target, and the problem is to find the flight path joining the two. Most of what follows concerns initial value problems, which are the natural shape for population dynamics; boundary value problems are taken up later, once the machinery for IVPs is in place.

Reading the prototype

A few features of the prototype y˙(t)=f(t,y(t))\dot y(t) = f(t, y(t)) are worth drawing out, because the notation hides some choices.

The right-hand side ff is not the ff of the population models. There, f(p,q)f(p, q) named the per-capita growth rate — the factor multiplying the population in p˙=f(p,q)p\dot p = f(p, q)\, p. Here ff is the entire right-hand side of the ODE: the whole expression for y˙\dot y, the rate of change itself. Same letter, a different job.

The unknown yy may be a single scalar function of time or a vector of several functions stacked together. The system form above is the vector case written out component by component: nn populations evolving at once, where — as is typical in these models — each species’ rate of change depends on all the others. The coupled predator–prey and competition systems are of exactly this shape, nn first-order equations sharing one set of unknowns.

And every equation here is first-order: only the first derivative y˙\dot y appears, never y¨\ddot y or anything higher. That is a genuine simplification, and it is not as restrictive as it looks. A higher-order equation — the damped oscillator’s p¨\ddot p, for instance — can be folded back into this first-order form by introducing the lower derivatives as extra unknowns, a reduction taken up later in this chapter. So the first-order IVP is not a special case; it is the standard form everything else is converted into.

One last case is worth flagging. If ff happens to depend only on tt and not on yy — that is, y˙=f(t)\dot y = f(t) — then the ODE is no longer really a differential equation to be solved but a plain integration, y(t)=ya+atf(s)dsy(t) = y_a + \int_a^t f(s)\,ds, with nothing to do beyond evaluating an integral. The interesting case, and the one that forces the numerical machinery of this page, is the usual one: ff depends on the unknown yy itself, tying the rate of change at each instant to the current state, so the solution cannot be had by integrating a known function of tt alone.

A short excursus: discretization

Before any method can run, there is a gap to bridge. A model lives in the continuum — real-valued quantities, smooth functions, an unbroken interval of time — but a computer is a finite machine that can hold only finitely many numbers and perform only finitely many operations. Every numerical method therefore begins with the same move.

Discretization is the transition from the continuum to the discrete and finite: replacing objects that take uncountably many values — the real numbers, a continuous function, an interval of time — with finite, machine-representable stand-ins.

This happens at several levels, and it is worth seeing each one, because the approximations they introduce are the ultimate source of the errors the rest of this page has to control.

Representing numbers

The first thing to discretize is the real number line itself. A machine cannot store an arbitrary real number — between any two reals lie uncountably many others, and only finitely many fit in memory. There are three standard schemes, differing in how they trade off range (how large and small the representable numbers go) against resolution (how finely spaced they are):

  • Integer arithmetic keeps only whole numbers, so the spacing is always exactly one. Nothing between two integers can be stored: a value like 2.52.5 has no slot, and a division such as 5/25 / 2 silently drops its remainder. For continuous quantities that is hopelessly coarse.
  • Fixed-point arithmetic narrows the spacing to let fractions in. It pins the radix point at a fixed position and keeps a fixed number of digits after it, so the machine really stores an integer such as 123123 but reads it as 1.231.23 (the point is just understood to sit two places in). Now fractions fit — but the spacing it buys is uniform across the whole range: choose a step of 0.010.01 and every representable number sits 0.010.01 from its neighbor, near zero and near a million alike. That is the wrong place to spend precision. Out among large values the fixed step is far finer than anyone needs, while near zero that same step is too coarse to resolve anything smaller than itself. What you actually want is fine steps for small numbers and coarse steps for large ones, and a single absolute spacing can never be both.
  • Floating-point arithmetic fixes that imbalance by letting the radix point “float” to wherever the digits are needed, so both the range and the resolution vary with the number’s size. The spacing now scales with magnitude — tight near zero, wide out among large values — but the relative precision stays the same everywhere: the same handful of significant digits whether the quantity is tiny or huge. This is what makes it possible to hold both very large and very small numbers sensibly, and it is what essentially all scientific computation uses.

The floating-point idea has a precise definition.

The floating-point numbers in base BB with tt mantissa digits form the set

FB,t={MBE  :  M=0  Bt1M<Bt,  M,EZ}.\mathbb{F}_{B,t} = \{\, M \cdot B^E \;:\; M = 0 \ \lor\ B^{t-1} \le |M| < B^t,\ \ M, E \in \mathbb{Z} \,\}.

The integer MM is the mantissa, carrying the significant digits; the integer EE is the exponent, which scales them; and BB is the base. The bound Bt1M<BtB^{t-1} \le |M| < B^t forces a nonzero mantissa to have exactly tt digits — no leading zeros — so every number has one normalized representation.

A real machine cannot store arbitrarily large or small exponents, so it keeps only the machine numbers, the floating-point numbers whose exponent lies in a fixed range aEba \le E \le b:

F=FB,t,a,b={fFB,t  :  aEb}.\mathbb{F} = \mathbb{F}_{B,t,a,b} = \{\, f \in \mathbb{F}_{B,t} \;:\; a \le E \le b \,\}.

Because the exponent is bounded, this set is finite and has a smallest and a largest element. The smallest positive machine number is the smallest mantissa at the smallest exponent, Bt1BaB^{t-1} \cdot B^{a}, and the largest machine number is the largest mantissa at the largest exponent, (Bt1)Bb(B^{t} - 1) \cdot B^{b}. A value beyond either bound cannot be stored: it underflows to zero if it is too small, or overflows if it is too large.

Take B=10B = 10 and t=3t = 3, so the mantissa always carries three decimal digits (100M<1000100 \le |M| < 1000 when nonzero) and the exponent slides the decimal point. Then 1.23=1231021.23 = 123 \cdot 10^{-2}, 123=123100123 = 123 \cdot 10^{0}, and 456,000=456103456{,}000 = 456 \cdot 10^{3} are all representable, each with the same three significant digits. With the exponent bounded by, say, 9E9-9 \le E \le 9, the smallest positive machine number is then Bt1Ba=100109B^{t-1} \cdot B^{a} = 100 \cdot 10^{-9} and the largest is (Bt1)Bb=999109(B^{t} - 1) \cdot B^{b} = 999 \cdot 10^{9}. Outside that window a value cannot be stored at all — it underflows to zero or overflows.

Resolution — accuracy is relative

The single most important property of a floating-point system is how finely it resolves numbers, and the key fact is that it does so relatively, not absolutely.

The resolution of a floating-point system is the maximal relative distance between neighboring representable numbers,

ρ=B1t.\rho = B^{1-t}.

Here is where it comes from. Take two neighboring machine numbers with the same exponent EE. Their mantissas differ by one, so the numbers themselves are MBEM \cdot B^E and (M+1)BE(M+1) \cdot B^E, and subtracting leaves an absolute gap of just BEB^E between them. To turn that into a relative gap, divide it by the value itself, MBEM \cdot B^E. This fraction is largest where the value is smallest, and the smallest a normalized mantissa is allowed to be is M=Bt1|M| = B^{t-1} (the lower bound built into the definition). Putting that worst case in, the BEB^E cancels top and bottom:

BEBt1BE=B1t=ρ.\frac{B^E}{B^{t-1} \cdot B^E} = B^{1-t} = \rho.

So the relative gap never exceeds ρ\rho. The absolute spacing BEB^E itself grows as the numbers grow, so neighbors far out on the line are spaced far apart while neighbors near zero are packed tightly together, but the relative spacing stays capped at ρ\rho everywhere. This is exactly the behavior one wants: precision is proportional to magnitude, the same handful of significant digits whether the quantity is tiny or huge.

With B=10B = 10 and t=3t = 3, the resolution is ρ=1013=102\rho = 10^{1-3} = 10^{-2}, one part in a hundred. Near 11, consecutive machine numbers 1.00, 1.01, 1.02,1.00,\ 1.01,\ 1.02,\dots sit an absolute 0.010.01 apart. Near 10001000 they are 1000, 1010, 1020,1000,\ 1010,\ 1020,\dots, an absolute gap of 1010, a thousand times wider. Yet the relative gap is 1%1\% in both places: three significant digits, wherever you look on the line.

Beyond numbers

Discretization does not stop at the numbers. The same move — replace an infinite or continuous object with a finite one — is applied to functions and to the operations of calculus:

  • An infinite series is cut off after finitely many terms, turning it into a polynomial. The sine, for example, is computed in practice not from its defining infinite series but from a truncated polynomial that agrees with it closely on the range of interest.
  • A continuous interval of time is replaced by a finite grid of discrete points t0,t1,t2,t_0, t_1, t_2, \dots, and the solution is sought only at those points rather than at every instant.
  • A derivative — defined as a limit, an inherently continuous notion — is replaced by a difference quotient taken over a small but finite step.

The difference quotient approximates the derivative of yy at tt by the slope of a chord over a small finite step hh, instead of the limiting slope of the tangent:

y˙(t)y(t+h)y(t)h.\dot y(t) \approx \frac{y(t+h) - y(t)}{h}.

The derivative is the limit of this ratio as h0h \to 0; keeping hh small but positive is what makes it computable. This last substitution is the seed of the whole numerical treatment of ODEs — it turns y˙=f(t,y)\dot y = f(t, y) from a statement about instantaneous rates into a rule that steps the solution forward one finite hh at a time. But every one of these discretizations — rounded numbers, truncated series, finite steps in place of limits — buys that computability at the price of some error. Keeping those errors identified and under control is what the next two sections are about, and what the rest of the page ultimately depends on.

Rounding and round-off error

A numerical algorithm picks up error from several sources. The most basic one is built into floating-point arithmetic itself: a real number that is not a machine number cannot be stored exactly and must be replaced by one that can.

Every real xx that is not itself a machine number falls strictly between two that are — its nearest representable neighbors below and above,

fl(x)=max{fF:fx},fr(x)=min{fF:fx}.f_l(x) = \max\{\, f \in \mathbb{F} : f \le x \,\}, \qquad f_r(x) = \min\{\, f \in \mathbb{F} : f \ge x \,\}.

If xx happens to be a machine number, both neighbors coincide with xx itself; otherwise xx sits between fl(x)f_l(x) and fr(x)f_r(x), and storing it means choosing one of the two. That choice is made by a rounding map.

Rounding is a map rd:RF\mathrm{rd} : \mathbb{R} \to \mathbb{F} that replaces each real number with a machine number. It has three defining properties:

  • surjective (every target value is hit) — each machine number is the rounding of some real, if only of itself, so no representable value is left unreachable;
  • idempotent (applying it twice changes nothing the second time) — a number that is already a machine number is left alone, rd(f)=f\mathrm{rd}(f) = f for every fFf \in \mathbb{F}, so rd(rd(x))=rd(x)\mathrm{rd}(\mathrm{rd}(x)) = \mathrm{rd}(x);
  • monotonic (order-preserving) — if xyx \le y then rd(x)rd(y)\mathrm{rd}(x) \le \mathrm{rd}(y), so rounding never flips the order of two numbers.

Which of the two neighbors rd(x)\mathrm{rd}(x) actually returns is fixed by the rounding mode:

  • rounding up always takes the upper neighbor, rd(x)=fr(x)\mathrm{rd}(x) = f_r(x), i.e. it rounds toward ++\infty;
  • rounding down always takes the lower neighbor, rd(x)=fl(x)\mathrm{rd}(x) = f_l(x), i.e. it rounds toward -\infty;
  • correct rounding (round to nearest) takes whichever neighbor is closer, with a fixed tie-breaking rule for the exact midpoint between them;
  • truncating (round toward zero) simply cuts the number off, keeping the leading digits and throwing away the rest. Since dropping digits only ever shrinks the magnitude, this lands on the neighbor nearer to zero. That matches rounding down for positive values, but for negative ones it rounds the opposite way: 2.7-2.7 truncates to 2-2, whereas rounding down would give 3-3.

Whatever the mode, the price of storing xx is the gap between it and the machine number chosen for it.

The round-off error is the discrepancy rd(x)x\mathrm{rd}(x) - x introduced when a real number is replaced by its machine representation. Measured relative to xx, it is bounded by the resolution: at most ρ\rho for the directed modes (up, down, truncation), and at most ρ/2\rho/2 for correct rounding to the nearest — which, by always taking the closer neighbor, can never be off by more than half the spacing to a neighbor.

Arithmetic on machine numbers

Rounding is not a one-time cost paid only on input. Even when both operands are machine numbers, the result of an arithmetic operation usually is not — multiply two three-digit numbers and the exact product can need six digits — so it has to be rounded back into F\mathbb{F} as well. The idealized model of this is clean:

Under ideal arithmetic, each elementary operation is carried out as if in exact arithmetic and only its result is rounded to a machine number:

a~b=rd(ab),{+,,,/},a,bF.a \mathbin{\tilde{*}} b = \mathrm{rd}(a * b), \qquad * \in \{+, -, \cdot, /\}, \quad a, b \in \mathbb{F}.

The tilde marks the machine operation ~\tilde{*} as distinct from the exact one * it approximates.

This is the behavior the IEEE floating-point standard prescribes — though, as a technological matter, not every computer implements it faithfully. For error analysis it is handier to weaken the exact equality into a bound: every machine operation returns the true result perturbed by a small relative amount,

a~b=(ab)(1+ε(a,b)),ε(a,b)<ε~=O(ρ).a \mathbin{\tilde{*}} b = (a * b)\,\bigl(1 + \varepsilon(a, b)\bigr), \qquad |\varepsilon(a, b)| < \tilde{\varepsilon} = O(\rho).

The perturbation ε(a,b)\varepsilon(a, b) depends on the operands, but its size is always controlled. The O(ρ)O(\rho) here is big-O notation, read “on the order of ρ\rho”: it means the bound ε~\tilde{\varepsilon} is at most some fixed constant times the resolution ρ\rho, so it stays proportional to ρ\rho and shrinks right along with it as the precision improves. A single rounding, then, is utterly harmless. The real question is what happens when millions of them compound over a long computation: do the individual errors stay independent and roughly cancel, or do they reinforce and grow? Estimating that accumulated influence is the job of round-off error analysis, and the standard worked example is Horner’s method for evaluating a polynomial — the classic case for tracing how round-off builds up across a long chain of multiply–add steps.

Further sources of errors

Round-off is the most basic source of error, but it is not the only one, and on modern hardware it is rarely the worst. Double-precision arithmetic carries so many significant digits that the resolution ρ\rho is minuscule, so the errors that actually dominate a real computation usually come not from the number format but from the deliberate approximations a method makes. Three of these are worth naming.

The discretization error is the error from solving a problem on a discrete set of points instead of on the underlying continuum — sampling a continuous function at grid points, or stepping an ODE forward in finite jumps rather than continuously.

This is the direct cost of the discretization that every method on this page relies on, and it is the one these methods spend most of their effort controlling. Once a concrete method is in hand, the discretization error splits into a local part (made in a single step) and a global part (accumulated across all steps), and the central questions become how fast each shrinks as the step size goes to zero.

The truncation error is the error from stopping an iterative process after finitely many steps, instead of running it to its (often infinite) completion.

This takes a few common shapes. A series computed term by term has to be cut off after NN terms, leaving out the infinite tail — the same series-to-polynomial truncation seen earlier, now viewed as a source of error. Root-finding by Newton’s method likewise produces only a sequence of ever-better approximations to a true root; the iteration has to be stopped somewhere, either after a fixed number NN of steps or once the change from one step to the next becomes insignificant, and whatever distance remains to the exact root is truncation error.

The data error is the error already present in the input: the data fed to a computation are often measurements, and measurements are inexact.

Data error is different in kind from the other two — it has nothing to do with the algorithm or the machine, but is baked into the problem before any computation starts. It cannot be reduced by a better method or finer precision, only carried along, and how much it grows or shrinks as it passes through a computation turns out to be a property of the problem itself, taken up later on this page.

Round-off, discretization, truncation, data — each is a separate way the computed answer can drift from the true one, and a sound numerical method has to keep an eye on all of them at once.

One-step methods

With the machinery in place, we can build the first actual solvers. They all share one shape: start from the known initial value and march forward, producing the solution at one grid point after another. The simplest family computes each new point using only the single point just before it.

A one-step method advances the solution of an initial value problem one grid point at a time, computing the next value yk+1y_{k+1} from the current value yky_k alone — no earlier points yk1,yk2,y_{k-1}, y_{k-2}, \dots enter the step. Each step has the form

yk+1=yk+δtΦ(tk,yk,δt),y_{k+1} = y_k + \delta t \cdot \Phi(t_k, y_k, \delta t),

where δt\delta t is the step size and the increment function Φ\Phi is the method’s estimate of the average rate of change across the step.

The whole design problem is bottled up in Φ\Phi: every one-step method on this page — Euler, Heun, Runge–Kutta — keeps the skeleton new value = old value + step size ×\times estimated growth and differs only in how it estimates that growth. Keep that template in mind; it is the single thread running through everything below. (Methods that do reuse older points yk1,yk2,y_{k-1}, y_{k-2}, \dots — the multistep methods — come later in this chapter.)

Two symbols that look alike: versus

One notational point underlies everything that follows. The function y(t)y(t), with its time argument in parentheses, is always the exact solution — the true trajectory the ODE and initial value pin down uniquely. We never actually have it, except at the starting point y(a)=yay(a) = y_a; in the interesting cases there is no formula to evaluate it with. The subscripted yky_k, with an index and no parentheses, is always the computed approximation at the grid point tkt_k, and yky(tk)y_k \approx y(t_k) — close, but in general not equal.

Each approximation is built on the previous one. Only the first value is exact, y0=ya=y(a)y_0 = y_a = y(a); every step after that feeds the method its own previous output, so yk+1y_{k+1} is built from the approximation yky_k, not from the true y(tk)y(t_k). That is how error compounds. The exact y(t)y(t) never enters the computation — it appears only in the analysis, as the yardstick the computed yky_k is judged against.

The Euler method

The starting point is the difference quotient from the discretization excursus — replacing a derivative by a slope over a small finite step is the finite-differences approximation. It says the derivative over that step is approximately the slope of the chord,

y˙(t)y(t+δt)y(t)δt.\dot y(t) \approx \frac{y(t + \delta t) - y(t)}{\delta t}.

The prototype IVP y˙(t)=f(t,y(t))\dot y(t) = f(t, y(t)) says that this same derivative equals f(t,y(t))f(t, y(t)). Setting the two equal and solving for the value one step ahead turns a statement about instantaneous rates into a recipe for moving forward:

y(a+δt)y(a)+δtf(a,y(a)).y(a + \delta t) \approx y(a) + \delta t \cdot f\bigl(a, y(a)\bigr).

Read it in the running template: the population a short time δt\delta t from now equals the population now, y(a)y(a), plus the time elapsed times the growth rate f(a,y(a))f(a, y(a)) estimated at the current instant. Here aa is the start of the time interval and y(a)y(a) the known initial value — the headcount we measure at the outset. We do not know the true rate over the whole step, so we use the only rate we have: the one at the point we are standing on.

Nothing forces us to stop after one step. Having landed on an estimate at a+δta + \delta t, we treat it as the new “current” point and repeat, and repeat again. That iteration is the Euler method.

The Euler method (also called the explicit or forward Euler method) solves the IVP y˙=f(t,y)\dot y = f(t, y), y(a)=yay(a) = y_a by stepping

yk+1=yk+δtf(tk,yk),tk=a+kδt,k=0,1,2,y_{k+1} = y_k + \delta t \cdot f(t_k, y_k), \qquad t_k = a + k\,\delta t, \qquad k = 0, 1, 2, \dots

starting from y0=yay_0 = y_a. Each step follows the slope f(tk,yk)f(t_k, y_k) of the solution at the current point for one step of length δt\delta t. It is the one-step method whose increment function is simply Φ=f(tk,yk)\Phi = f(t_k, y_k).

This is the move from continuum to discrete made concrete: one continuous equation, valid at every instant, becomes a sequence of discrete equations, one per grid point. The grid covers a finite range t[a,b]t \in [a, b], the spacing between neighboring points is the step size δt\delta t, and the method hands back the solution only at t0,t1,t2,t_0, t_1, t_2, \dots — never in between.

That the Euler method runs — that the recurrence mechanically produces a value at every grid point — is clear. Whether what it produces is any good is not, and three questions decide it. They organize the rest of this section.

  • Does it converge? As the step size shrinks, δt0\delta t \to 0, do the computed points close in on the true solution of the ODE? A method that refuses to improve no matter how fine the grid is worthless; this is the bare minimum we demand, and the next section makes it precise as consistency and convergence.
  • How fast does it converge? Suppose it does converge. Halving the step size doubles the work — does it buy enough accuracy to be worth it? If halving δt\delta t only halves the error, progress is painfully slow; if it cuts the error to a quarter or a sixteenth, refinement pays off fast. This is the method’s order, and it is the single number that separates Euler from Heun from Runge–Kutta.
  • Is there a largest safe step? Going finer is accurate but expensive; going coarser is cheap but risky. Is there a sharp threshold — a maximal δt\delta t below which the method behaves and above which it falls apart? The tension is real: forecasting two seconds ahead in one step is fine, leaping two months ahead in one step is meaningless, and somewhere between lies a limit. Pinning that limit down is the subject of stability and stiffness, later on this page.

The first two questions are answered next. The third has to wait — it turns out to depend on the method and the equation together, in ways that take real work to untangle.

Discretization error: local, global, and what “convergence” means

There is a second route to the Euler formula that comes not from the difference quotient but from calculus, and it has the advantage of exposing the error directly. Take the exact solution y(t)y(t) and expand it one step ahead with a Taylor series:

y(tk+1)=y(tk)+(tk+1tk)y˙(tk)+Ry(tk)+(tk+1tk)f(tk,yk).y(t_{k+1}) = y(t_k) + (t_{k+1} - t_k)\,\dot y(t_k) + R \approx y(t_k) + (t_{k+1} - t_k)\,f(t_k, y_k).

The remainder RR collects every higher-order term — the ones carrying y¨\ddot y and beyond. Over a short step those terms are small, so we drop them; and we replace y˙(tk)\dot y(t_k) with f(tk,yk)f(t_k, y_k), since the ODE says the rate equals ff and yky_k is the value we actually have at that step. What is left is exactly the Euler step. The same method, reached two ways: the difference quotient is the numerical route, the truncated Taylor series the analytic one. The Taylor route makes plain what we threw away — the remainder RR — and that discarded remainder is the error. Measuring it is the whole game.

That error comes in two flavors, and telling them apart is the key conceptual step.

The local discretization error measures the error made in a single step, under the assumption that the step starts from a point exactly on the true solution. Feeding the exact solution yy into one step of the scheme and comparing the resulting difference quotient with the true rate f(t,y(t))f(t, y(t)),

l(δt)=max[a,b]{y(t+δt)y(t)δtf(t,y(t))}.l(\delta t) = \max_{[a, b]} \left\{ \frac{y(t + \delta t) - y(t)}{\delta t} - f\bigl(t, y(t)\bigr) \right\}.

The maximum runs over the whole interval [a,b][a, b], so l(δt)l(\delta t) is the worst single-step defect anywhere on it. Crucially, yy here is the exact solution, not the computed approximation — the local error asks how well one clean step would do, with no inherited error from before.

Reading the formula: the fraction is the scheme’s difference quotient — the slope Euler would use if it sat exactly on the true curve at time tt — and f(t,y(t))f(t, y(t)) is the rate the ODE actually prescribes there. Their difference is how far the finite-step slope deviates from the true derivative, sampled on the exact solution. The maximum over [a,b][a, b] then takes the largest such deviation anywhere on the interval, so l(δt)l(\delta t) is a single worst-case figure for the whole run.

The global discretization error measures the actual gap between the computed solution and the exact one, accumulated across all steps from the start:

e(δt)=max[a,b]{yky(tk)}.e(\delta t) = \max_{[a, b]} \bigl\{ \lvert y_k - y(t_k) \rvert \bigr\}.

Unlike the local error, this carries no “assume we start on the true curve” caveat — yky_k is the approximation our method actually produced, inheriting every error made on every step before it, and y(tk)y(t_k) is the true solution at the same time.

The distinction is the difference between the error this one step introduces (local, measured as if the past were perfect) and the error you are actually stuck with (global, the real distance from truth at tkt_k). One would expect the global error to be vastly larger than the local one — after all, it is a running total of many local mistakes, so naively it should pile up step after step without bound. It does not, and the reason is the whole reason numerical methods are usable at all: the per-step errors do not march in lockstep. Some push the estimate above the true curve, some below; they partly cancel rather than purely accumulate. If they really did add up relentlessly, the global error would explode and no amount of computing would give a trustworthy answer. (Exactly when this benign cancellation fails — when errors reinforce instead — is the stability question flagged above.)

Both definitions need the exact solution y(t)y(t), which in general we do not have. So to actually measure the error, we supply a stand-in for y(t)y(t): either rerun the same problem on a much finer grid and treat that high-resolution result as the “truth,” or test the method on one of the rare problems whose exact solution is known in closed form (the population models from earlier are exactly such cases). Comparing the computed yky_k against that reference is how l(δt)l(\delta t) and e(δt)e(\delta t) get evaluated in practice.

Now the two minimum demands on a method can be stated cleanly.

A method is consistent if its local discretization error vanishes as the step size shrinks,

l(δt)0asδt0.l(\delta t) \to 0 \quad \text{as} \quad \delta t \to 0.

This is the necessary minimum: in the limit of infinitely fine steps, a single step makes asymptotically no error. A method that fails even this is approximating the wrong equation.

Consistency is the weak condition — it only controls one step at a time, and says nothing about what happens once many steps are chained together. It guarantees that each individual step, taken in isolation, can be made as accurate as we like by shrinking δt\delta t. But many small errors can still combine into a large one, so “every step is nearly right” does not by itself force “the final answer is nearly right.” For that we need the accumulated error to vanish — the stronger property, convergence.

A method is convergent if its global discretization error vanishes as the step size shrinks,

e(δt)0asδt0.e(\delta t) \to 0 \quad \text{as} \quad \delta t \to 0.

This is the property that actually matters: refine the grid and the computed solution closes in on the true one everywhere on [a,b][a, b], accumulated error and all. Convergence is strictly stronger than consistency — and, as a warning sounded later, consistency alone does not imply it.

Both properties say the error eventually disappears, but neither says how fast. That rate is the practically decisive quantity, and it has a name.

The order of a method is the power kk controlling how fast its discretization errors shrink with the step size:

l(δt)=O(δtk),e(δt)=O(δtk).l(\delta t) = O(\delta t^k), \qquad e(\delta t) = O(\delta t^k).

A higher order means faster convergence. Concretely, halving the step size shrinks the error by a factor of 2k2^k: order 11 turns a halved step into a halved error, order 22 into a quarter, order 44 into a sixteenth. This is the precise answer to “how much does halving the step buy?” — and the number that ranks one method against another.

The OO here is ordinary big-O notation — the same one met earlier for round-off, read “on the order of.” Writing l(δt)=O(δtk)l(\delta t) = O(\delta t^k) just says the error stays below some fixed constant times δtk\delta t^k once δt\delta t is small. The two senses of order then line up exactly: the order of a method is the exponent kk sitting inside that big-O bound, so it is the same notion as big-O’s “order of magnitude,” not a clashing second use of the word.

Comparing the two errors, step by step

A picture makes the local-versus-global distinction concrete. Plot time along the horizontal axis at the grid points t0=a,t1,t2,t_0 = a, t_1, t_2, \dots and the solution value y(t)y(t) along the vertical. The red curve is the true solution of the original IVP, the one passing through the initial value y0=yay_0 = y_a. The Euler points y1,y2,y3,y4y_1, y_2, y_3, y_4 are obtained by the calculation rule, each one a tangent-line step: from the current point, follow the local slope straight for one step of width δt\delta t.

The other colored curves are the subtle part, and the source of the “where did those come from?” confusion. Each one is the exact solution of the same ODE, but launched from a computed point rather than from yay_a. The green curve is the true trajectory that would unfold if the system were restarted exactly at (t1,y1)(t_1, y_1); the blue one is the true trajectory restarted at (t2,y2)(t_2, y_2); the black one restarted at (t3,y3)(t_3, y_3). The label "y(t), y(t1)=y1y(t),\ y(t_1) = y_1" means precisely “the solution curve y(t)y(t) satisfying y(t1)=y1y(t_1) = y_1.” They are not extra numerical outputs — they are the exact-solution family, one member through each point the method actually produced.

With that, the two errors are read straight off the figure:

  • Local discretization error (the short bars): at each step, the vertical gap between the Euler point yk+1y_{k+1} and the exact curve that passes through the previous point yky_k. It is “local” because it measures one Euler step against the true trajectory launched from where that step began — exactly the “assume the past was perfect” definition above.
  • Global discretization error (the tall bars): the vertical gap between the Euler point yky_k and the red true solution. It is the error you are really carrying — the full distance from the original trajectory, every inherited mistake included.

A last caution about the figure. Drawn this way, the tall global-error bars seem to grow without limit, step after step. That impression is an artifact of one particular example. In general the global error can grow, shrink, or oscillate as you march along — the partial cancellation of local errors discussed above means it need not balloon. If it genuinely grew without bound, numerical integration would be pointless; that it usually does not is what makes the whole enterprise work.

Order, and getting more of it: the Heun method

For the Euler method, as long as the second derivative y¨\ddot y and the partial derivative fy=f/yf_y = \partial f / \partial y (how the rate ff responds to a change in the state yy) stay bounded — never running off to infinity anywhere on [a,b][a, b] — the analysis above pins down the order exactly: it is consistent of first order, l(δt)=O(δt)l(\delta t) = O(\delta t), and convergent of first order, e(δt)=O(δt)e(\delta t) = O(\delta t). In the language just built, O(δt)O(\delta t) means halving the step size only halves the error — honest, but slow. And the warning attached to the convergence definition bites here too: there exist methods that are perfectly consistent yet do not converge, so first-order consistency is reassuring but not the end of the story.

Slow convergence is the practical problem. To reach a target accuracy with Euler you may need a punishingly small step, and small steps mean many of them — high computational cost. The cure is a higher order: a method with e(δt)=O(δt2)e(\delta t) = O(\delta t^2) reaches the same accuracy with far larger steps, because halving now quarters the error. So we want order, and the question is how to manufacture it.

One route is staring at us. The Taylor derivation of Euler dropped the remainder RR after the first-derivative term; keep the next term instead and you get a higher-order method directly. The trouble is that the next term carries y¨\ddot y, which by the chain rule unpacks into derivatives of ff itself — and the term after that into still more. The formulas grow ugly fast, and they demand that you supply derivatives of ff by hand. For a real model that is often impractical.

The alternative sidesteps derivatives entirely: instead of evaluating higher derivatives of ff at one point, evaluate ff itself at several points within the step and combine the results. Methods built this way are the Runge–Kutta methods, and the simplest one above Euler is the method of Heun.

The idea follows the running template — new value = old value + step size ×\times estimated growth — and improves only the growth estimate. Euler uses a single slope, f(tk,yk)f(t_k, y_k), sampled at the start of the step. But the true rate changes across the step, and trusting the starting slope alone systematically lags. The natural fix is to average the slope at the start with the slope at the end of the step — an arithmetic mean of the two derivatives, which tracks the curve far better than either endpoint alone.

There is one obstruction: the slope at the end is f(tk+1,yk+1)f(t_{k+1}, y_{k+1}), and yk+1y_{k+1} is the very thing we are trying to compute — we do not know it yet. Heun’s resolution is to predict the endpoint with a cheap Euler step, then use that prediction to evaluate the ending slope:

The Heun method (also called the improved or explicit trapezoidal Euler method) averages the slope at the start of the step with the slope at a predicted endpoint:

yk+1=yk+δt2(f(tk,yk)+f(tk+1,yk+δtf(tk,yk))).y_{k+1} = y_k + \frac{\delta t}{2}\Bigl( f(t_k, y_k) + f\bigl(t_{k+1},\, y_k + \delta t\, f(t_k, y_k)\bigr) \Bigr).

The inner expression yk+δtf(tk,yk)y_k + \delta t\, f(t_k, y_k) is an ordinary Euler step — a provisional guess at yk+1y_{k+1} used only to estimate the ending slope. Averaging the two slopes makes the method consistent and convergent of second order, l(δt),e(δt)=O(δt2)l(\delta t), e(\delta t) = O(\delta t^2), which means halving the step size now cuts the error to a quarter rather than only to a half.

The payoff is concrete: where Euler’s error halves when you halve the step, Heun’s quarters. The cost is two evaluations of ff per step instead of one — a trade almost always worth making.

Runge–Kutta methods

Heun is the first rung of a ladder. Push the same idea further — sample ff at more points inside the step and take a cleverly weighted average — and you climb to higher and higher order. The whole family is the Runge–Kutta methods, and its most famous member, often meant when someone says simply “the Runge–Kutta method,” uses four slope samples.

The classical fourth-order Runge–Kutta method (RK4) advances one step by a weighted average of four slope samples:

yk+1=yk+δt6(T1+2T2+2T3+T4),y_{k+1} = y_k + \frac{\delta t}{6}\bigl( T_1 + 2T_2 + 2T_3 + T_4 \bigr),

with

T1=f(tk,yk),T2=f ⁣(tk+δt2,  yk+δt2T1),T3=f ⁣(tk+δt2,  yk+δt2T2),T4=f ⁣(tk+1,  yk+δtT3).\begin{aligned} T_1 &= f(t_k, y_k), \\ T_2 &= f\!\left(t_k + \tfrac{\delta t}{2},\; y_k + \tfrac{\delta t}{2} T_1\right), \\ T_3 &= f\!\left(t_k + \tfrac{\delta t}{2},\; y_k + \tfrac{\delta t}{2} T_2\right), \\ T_4 &= f\!\left(t_{k+1},\; y_k + \delta t\, T_3\right). \end{aligned}

It is consistent and convergent of fourth order, l(δt),e(δt)=O(δt4)l(\delta t), e(\delta t) = O(\delta t^4), which means each halving of the step cuts the error to a sixteenth.

The four samples read as a sequence of ever-better guesses at the slope across the step: T1T_1 is the slope at the start (plain Euler); T2T_2 is the slope at the midpoint, reached by stepping there with T1T_1; T3T_3 is the midpoint slope again, but reached using the improved T2T_2; and T4T_4 is the slope at the endpoint, reached using T3T_3. The weighting 1:2:2:11 : 2 : 2 : 1 leans on the two midpoint estimates, which carry the most information about the step’s average behavior. Fourth order is the headline: RK4 reaches accuracies with modest steps that Euler could only dream of — which is why it is the workhorse default for non-stiff problems (what makes a problem stiff is taken up later on this page, alongside stability). The price is four evaluations of ff per step, and the same template still holds: new value = old value + step size ×\times (weighted) estimated growth.

Euler, Heun, and Runge–Kutta are numerical integration rules in disguise. Integrating the ODE y˙=f(t,y)\dot y = f(t, y) across one step turns it from a differential statement into an integral one,

y˙=f(t,y)y(tk+1)=y(tk)+tktk+1f(t,y(t))dt,\dot y = f(t, y) \quad \Rightarrow \quad y(t_{k+1}) = y(t_k) + \int_{t_k}^{t_{k+1}} f\bigl(t, y(t)\bigr)\, dt,

so producing the next value is really a matter of approximating that integral — a problem called quadrature (numerical integration). Each method corresponds to a classical quadrature rule for the area under ff: Euler is the rectangle rule (one sample, the left endpoint), Heun is the trapezoidal rule (average of the two endpoints), and RK4 mirrors Simpson’s rule (endpoints plus a doubly-weighted midpoint). This is why some texts speak of integrating an ODE rather than solving it — the two phrasings name the same act, viewed through the difference quotient or through the integral.

Where the integral form comes from

Integrate both sides of y˙=f(t,y)\dot y = f(t, y) over the step [tk,tk+1][t_k, t_{k+1}]. The left side is the integral of a derivative, which by the fundamental theorem of calculus is just the net change y(tk+1)y(tk)y(t_{k+1}) - y(t_k); the right side is tktk+1f(t,y(t))dt\int_{t_k}^{t_{k+1}} f(t, y(t))\, dt. Rearranging gives the boxed identity. Every one-step method is then a choice of how to approximate that one integral from samples of ff — which is exactly what a quadrature rule is.

Multistep methods

The Runge–Kutta ladder buys order by sampling ff at more points inside each step — RK4 calls ff four times per step. That accounting hides where the real cost lives. The arithmetic that combines the samples T1,,T4T_1, \dots, T_4 is trivial; the expense is the evaluations of ff themselves. When ff is a tidy formula — a polynomial, a sine — an evaluation is nearly free and four of them cost nothing to worry about. But ff is the entire right-hand side of the model, and for a serious model it can be enormous: if a single evaluation of ff is itself a multi-hour supercomputer run (a weather forecast, say), then RK4’s four-evaluations-per-step is not the difference between 10 and 40 milliseconds but between six hours and a full day. At that scale the natural question is whether order can be bought without paying for extra evaluations of ff.

It can — by looking the other way in time. Runge–Kutta manufactures its extra slope samples by probing forward into the step, and every probe is a fresh evaluation. But the method has already evaluated ff at the previous grid points tk1,tk2,t_{k-1}, t_{k-2}, \dots on earlier steps, and those numbers are just sitting there. Reusing that history instead of computing new samples buys higher order essentially for free.

Before writing one down, a shorthand the rest of this page leans on: write

fk:=f(tk,yk)f_k := f(t_k, y_k)

for the slope the method computes at grid point tkt_k. Once yky_k is known, fkf_k is one evaluation of ff, and a method that reuses history is one that keeps the old fkf_k values around rather than recomputing them.

A multistep method (also called an ss-step method) advances an initial value problem using not just the current point but several previous ones: yk+1y_{k+1} is computed from yk,yk1,,yks+1y_k, y_{k-1}, \dots, y_{k-s+1} and their already-evaluated slopes fk,fk1,f_k, f_{k-1}, \dots. This is the contrast to a one-step method, which uses yky_k alone. The explicit interpolatory family built this way is the Adams–Bashforth method.

The prominent representative is the second-order Adams–Bashforth method, which reaches one step into the past:

yk+1=yk+δt2(3f(tk,yk)f(tk1,yk1))=yk+δt2(3fkfk1).y_{k+1} = y_k + \frac{\delta t}{2}\bigl( 3 f(t_k, y_k) - f(t_{k-1}, y_{k-1}) \bigr) = y_k + \frac{\delta t}{2}\bigl( 3 f_k - f_{k-1} \bigr).

It still fits the running template — new value = old value + step size ×\times estimated growth — and differs only in the growth estimate: it extrapolates from the last two slopes fkf_k and fk1f_{k-1} instead of probing new ones. The only fresh slope a step needs is fkf_k — the other one, fk1f_{k-1}, is reused from the previous step — so a whole step costs one evaluation of ff, the same as Euler — yet the method is second order, the same as Heun, which costs two.

Where does the 3fkfk13 f_k - f_{k-1} come from? The integral form from the remark above is the key. Producing yk+1y_{k+1} means approximating tktk+1f(t,y(t))dt\int_{t_k}^{t_{k+1}} f(t, y(t))\, dt, and the multistep idea is to replace the integrand ff by a polynomial that is cheap to integrate exactly.

Given pp already-computed grid points (ti,fi)(t_i, f_i), i=kp+1,,ki = k - p + 1, \dots, k, let P(t)P(t) be the unique polynomial of degree p1p - 1 that passes through all of them — it interpolates the slope history. A multistep method then steps by integrating PP in place of the true integrand ff:

yk+1=yk+tktk+1y˙dtyk+tktk+1f(t,y(t))dtyk+tktk+1P(t)dt.y_{k+1} = y_k + \int_{t_k}^{t_{k+1}} \dot y\, dt \approx y_k + \int_{t_k}^{t_{k+1}} f\bigl(t, y(t)\bigr)\, dt \approx y_k + \int_{t_k}^{t_{k+1}} P(t)\, dt.

The polynomial is fixed by data we already have, and integrating a polynomial is exact and instant, so the only cost is the interpolation itself — no new evaluation of ff. The number pp of points sets the order:

  • p=1p = 1: PP is a constant through the single point (tk,fk)(t_k, f_k), and integrating it recovers yk+1=yk+δtfky_{k+1} = y_k + \delta t\, f_k — the Euler method.
  • p=2p = 2: PP is the line through (tk1,fk1)(t_{k-1}, f_{k-1}) and (tk,fk)(t_k, f_k), and integrating it over the next step gives exactly the 3fkfk13 f_k - f_{k-1} rule above.
  • general pp: order pp, at the fixed price of one evaluation of ff per step.

That is the payoff Runge–Kutta could not offer: more order without more evaluations.

Starting up: the first few steps

The cheapness comes with a debt at the very beginning. A pp-step method needs pp past points before it can take a step, but at the start of the integration only the initial value y0y_0 is on hand — there is no history yet to reuse.

The fix is to bootstrap. Run a one-step method (or a smaller multistep method) for the first few steps, just long enough to generate the missing y1,,yp1y_1, \dots, y_{p-1} and their slopes, and then switch to the full multistep method once enough history has accumulated. With a sufficiently accurate starter this preserves the method’s order: the startup phase is a handful of steps and does not spoil the asymptotics.

There is also a disadvantage shared by every method so far, explicit one-step and multistep alike: the step size δt\delta t often has to be kept very small — not for accuracy, but to stop the computation from going unstable. Push δt\delta t too large and the computed solution sprouts spurious oscillations that bear no resemblance to the true trajectory. Why this happens, and why it is a property of the method rather than the problem, is the subject of the stability discussion later on this page; for now the consequence is what matters. Small steps mean many steps, and many steps mean high computational cost. The remedy — methods that stay well-behaved at large step sizes — is implicit methods.

Implicit methods

Every method up to here shares a structural feature that is easy to overlook because it is so convenient: each computes yk+1y_{k+1} from a formula whose right-hand side contains only things already known. Plug in yky_k (and any history) and read off yk+1y_{k+1} directly.

A method is explicit if its step formula computes the new value yk+1y_{k+1} directly from quantities already known — the current and past points and their slopes. The unknown yk+1y_{k+1} appears only on the left-hand side, so a single evaluation of the formula produces it. Euler, Heun, RK4, and Adams–Bashforth are all explicit.

The alternative is to let yk+1y_{k+1} appear on the right-hand side as well — to use the not-yet-known new slope fk+1=f(tk+1,yk+1)f_{k+1} = f(t_{k+1}, y_{k+1}) in the very formula meant to produce yk+1y_{k+1}.

A method is implicit if its step formula uses the new value yk+1y_{k+1} on the right-hand side too, typically through the new slope fk+1=f(tk+1,yk+1)f_{k+1} = f(t_{k+1}, y_{k+1}). The formula no longer hands yk+1y_{k+1} over directly; it states a (generally nonlinear) equation that yk+1y_{k+1} must satisfy, which then has to be solved.

Applying this to the interpolation idea — interpolating through the previous points and the new grid point tk+1t_{k+1} — gives the implicit multistep family.

The Adams–Moulton method is the implicit counterpart of Adams–Bashforth: it interpolates the slope through the previous grid points together with the new point (tk+1,fk+1)(t_{k+1}, f_{k+1}), then integrates. Including the new point raises the order for the same number of stored points, at the cost of making yk+1y_{k+1} implicit. Its members for the first few orders are

1st order:yk+1=yk+δtfk+1,2nd order:yk+1=yk+δtfk+fk+12,4th order:yk+1=yk+δt24(fk25fk1+19fk+9fk+1).\begin{aligned} \text{1st order:}\quad & y_{k+1} = y_k + \delta t\, f_{k+1}, \\ \text{2nd order:}\quad & y_{k+1} = y_k + \delta t\, \frac{f_k + f_{k+1}}{2}, \\ \text{4th order:}\quad & y_{k+1} = y_k + \frac{\delta t}{24}\bigl( f_{k-2} - 5 f_{k-1} + 19 f_k + 9 f_{k+1} \bigr). \end{aligned}

The first-order member is important enough to have its own name.

The implicit Euler method (also called the backward Euler method) is the implicit one-step scheme

yk+1=yk+δtf(tk+1,yk+1).y_{k+1} = y_k + \delta t\, f(t_{k+1}, y_{k+1}).

It mirrors the explicit Euler method but evaluates the slope at the end of the step, fk+1f_{k+1}, instead of the start. Because yk+1y_{k+1} sits inside ff on the right, each step requires solving an equation for yk+1y_{k+1}.

The second-order Adams–Moulton member, averaging the start and end slopes, is the implicit trapezoidal method — the same trapezoidal average the Heun method used, but with the exact end slope fk+1f_{k+1} rather than a predicted one.

That “solving an equation” is the catch. Since ff is generally nonlinear, yk+1y_{k+1} is the unknown of a nonlinear equation, and there are two ways to extract it:

  • Brute force: solve the nonlinear equation directly each step, e.g. by a Newton iteration for the root yk+1y_{k+1}. Accurate, but a full root-find per step is costly.
  • Predictor–corrector: the easier and far more common route, taken up next.

The trade-off is the whole reason to go implicit. A single implicit step costs much more than an explicit one — you are solving an equation, not just “stepping further.” But, as the stability and stiffness sections will show, implicit methods stay well-behaved at far larger step sizes (sometimes a hundred times larger) with no loss of accuracy, so they need far fewer steps. Whether that is a net win depends on the problem: many cheap steps versus a few expensive ones. There is no universal rule, but for the hard cases coming up — stiff problems — implicit methods turn from merely attractive into necessary.

Predictor–corrector

The cheap way around the implicit equation is almost obvious once stated. If the obstruction is that we do not yet know yk+1y_{k+1} to put into the right-hand side, then estimate it first with an explicit method and use that estimate wherever the true yk+1y_{k+1} was needed. An implicit step becomes two explicit ones — approximating the approximation.

A predictor–corrector method evaluates an implicit scheme in two explicit stages:

  • the predictor computes a preliminary value y~k+1\tilde y_{k+1} with a suitable explicit method;
  • the corrector substitutes y~k+1\tilde y_{k+1} into the right-hand side of the implicit rule — wherever the unknown fk+1f_{k+1} was needed — and evaluates it to get the final yk+1y_{k+1}.

Both stages are explicit, so the whole step is computed directly; yet the result inherits the character of the underlying implicit method.

This is not a new trick — it is exactly what the Heun method already did: its predictor is a plain Euler step and its corrector is the implicit trapezoidal rule evaluated at that prediction,

y~k+1=yk+δtfk,yk+1=yk+δt2(fk+f(tk+1,y~k+1)).\tilde y_{k+1} = y_k + \delta t\, f_k, \qquad y_{k+1} = y_k + \frac{\delta t}{2}\bigl( f_k + f(t_{k+1}, \tilde y_{k+1}) \bigr).

Seen this way, Heun is the predictor–corrector pairing of explicit Euler with the second-order Adams–Moulton rule.

The accounting comes out well. A predictor–corrector step costs about twice an explicit step — one evaluation for the predictor, one for the corrector — instead of the open-ended cost of a full Newton solve, while keeping the large-step-size advantage of the implicit method it imitates. So the assessment of implicit methods, fairly stated: a single step is more expensive (solving an equation, or running two explicit stages, rather than just “going further”), but the number of steps is generally much smaller because larger step sizes are admissible, and the total cost can come out lower.

Higher-order equations and systems

Two loose ends remain before leaving the methods behind, and both reduce to one move: everything so far was written for a scalar first-order equation, and both the vector case and the higher-order case fold back into it.

Systems first, because they need almost nothing new. The population models with several interacting species are systems of first-order ODEs, and the entire machinery above — Euler, Heun, RK4, multistep, implicit — carries over verbatim. The recipe is identical; only the objects change. Wherever a scalar yky_k appeared, read a vector yk\mathbf{y}_k; wherever the scalar slope appeared, read the vector-valued right-hand side. The step formulas stay the same symbol for symbol, and everything provable for one ODE holds for a system by reading the scalars as vectors.

Higher-order equations take one extra step. The population models only ever involved the first derivative, but mechanics routinely produces the second — acceleration, hence force — as in the damped oscillator p¨+μp˙+ω2(pp)=0\ddot p + \mu \dot p + \omega^2 (p - p_\infty) = 0 derived among the population models. The prototype on this page is first-order by design, and the first-order paragraph earlier promised that higher-order equations fold back into it. Here is how.

An ODE of order nn is reduced to a system of nn first-order ODEs by naming the lower derivatives as auxiliary variables:

y1=y,y2=y˙,y3=y¨,,yn=y(n1).y_1 = y, \quad y_2 = \dot y, \quad y_3 = \ddot y, \quad \dots, \quad y_n = y^{(n-1)}.

An nn-th order equation y(n)=f(t;y,y˙,y¨,,y(n1))y^{(n)} = f(t;\, y, \dot y, \ddot y, \dots, y^{(n-1)}) then becomes the first-order system

y˙1=y2,y˙2=y3,    y˙n1=yn,y˙n=f(t;y1,y2,,yn).\begin{aligned} \dot y_1 &= y_2, \\ \dot y_2 &= y_3, \\ &\;\;\vdots \\ \dot y_{n-1} &= y_n, \\ \dot y_n &= f(t;\, y_1, y_2, \dots, y_n). \end{aligned}

The first n1n - 1 equations are just the definitions of the auxiliary variables (y˙i=yi+1\dot y_i = y_{i+1}); only the last carries the original dynamics.

The reduction trades order for size: an nn-th order scalar equation becomes a first-order system of nn equations. Nothing is lost and no new solver is needed — the order is gone, absorbed into a slightly larger system, which the methods above already handle. This is why the first-order IVP is not a narrow special case but the standard form every ODE is converted into.

What makes an ODE hard to solve

Convergence and order describe a method working as intended: refine the grid and the answer improves, at a predictable rate. But several things can still derail a real computation, and they are worth separating because they have different owners. Some trouble belongs to the problem — baked in before any method is chosen — and some belongs to the method. The rest of this page works through three such phenomena: ill-conditioning, instability, and stiffness. Keeping straight which is a property of the problem and which of the method is the whole point, and a summary table at the end lays them side by side.

Ill-conditioning

The first phenomenon has nothing to do with the algorithm at all. It is a property of the problem.

The condition of a problem measures the sensitivity of its solution to changes in the input data — how far the output can move when the input is perturbed slightly. It is a property of the problem itself, not of any algorithm used to solve it. A problem is well-conditioned if small input changes cause only small output changes, and ill-conditioned if a tiny blur in the input can produce a totally different result.

Ill-conditioning is the close quantitative cousin of an ill-posed problem: an ill-posed problem fails outright to depend continuously on its data, while an ill-conditioned one depends on them continuously but so steeply that, in finite precision, it behaves almost as badly. Such problems are very hard to treat numerically, because exact input is almost never available — and an example makes the danger concrete.

Consider the second-order ODE

y¨(t)Ny˙(t)(N+1)y(t)=0,\ddot y(t) - N \dot y(t) - (N + 1) y(t) = 0,

with the two initial conditions a second-order equation needs,

y(0)=1,y˙(0)=1.y(0) = 1, \qquad \dot y(0) = -1.

Its exact solution is the gentle decay y(t)=ety(t) = e^{-t} — substitute and check: y˙=et\dot y = -e^{-t}, y¨=et\ddot y = e^{-t}, so etN(et)(N+1)et=et(1+NN1)=0e^{-t} - N(-e^{-t}) - (N+1)e^{-t} = e^{-t}(1 + N - N - 1) = 0, with y(0)=1y(0) = 1 and y˙(0)=1\dot y(0) = -1 as required. Many different ODEs share this same plain exponential decay as their solution; this is one engineered to.

Now blur the input by the tiniest amount. Suppose the first initial condition is off by an infinitesimal ε\varepsilon — say ε=10100\varepsilon = 10^{-100}, a perturbation so small the problem is, for every practical purpose, unchanged:

yε(0)=1+ε.y_\varepsilon(0) = 1 + \varepsilon.

Solving again gives

yε(t)=(1+N+1N+2ε)et+εN+2e(N+1)t.y_\varepsilon(t) = \left(1 + \frac{N + 1}{N + 2}\varepsilon\right) e^{-t} + \frac{\varepsilon}{N + 2}\, e^{(N + 1)t}.

At first glance this looks reassuringly close to the original. The first term is (1+tiny)etet(1 + \text{tiny})\, e^{-t} \approx e^{-t}, and the second carries the microscopic coefficient ε/(N+2)\varepsilon/(N + 2). Surely negligible.

It is not. The second term hides a positive exponential, e(N+1)te^{(N + 1)t}, and in the long run that changes everything. No matter how minuscule its coefficient, an exponentially growing factor eventually overwhelms an exponentially decaying one: there is always a time beyond which εN+2e(N+1)t\frac{\varepsilon}{N + 2} e^{(N + 1)t} dwarfs the ete^{-t} term, however small ε\varepsilon is (and the larger NN is, the faster that runaway term grows). So the two solutions do not merely differ slightly — they part ways completely:

limty(t)=0,limtyε(t)=.\lim_{t \to \infty} y(t) = 0, \qquad \lim_{t \to \infty} y_\varepsilon(t) = \infty.

The true solution decays to zero; the infinitesimally perturbed one blows up to infinity.

This is ill-conditioning in its purest form, and it is exactly why numerical methods have to worry. The exact input is rarely available — initial data come from measurements (the data error), and intermediate results pick up round-off along the way — so some perturbation is unavoidable. For a well-conditioned problem that is harmless; for an ill-conditioned one it is fatal, and no choice of method or precision can rescue it, because the trouble lives in the problem, not the algorithm. It is also why careful people working from slightly different data can reach completely opposite conclusions — and why ill-conditioning is such a convenient excuse: when nothing can be done, nothing is anyone’s fault.

Stability

Ill-conditioning lets the problem off the hook for the method. The next phenomenon is the reverse: a well-conditioned problem, no excuses available, that a method still gets badly wrong. This is where the gap left open earlier finally gets filled.

Recall the warning attached to convergence: a method can be consistent — each isolated step accurate in the limit — without being convergent, because consistency says nothing about whether inherited errors stay under control as steps are chained. The property that controls them, and so bridges consistency and convergence, is stability. The sharpest way to see why it is needed is a method that is consistent and yet not convergent.

To pin the blame on the method, take a problem that is impeccably well-conditioned:

y˙=2y+1,y(0)=1,\dot y = -2y + 1, \qquad y(0) = 1,

whose exact solution

y(t)=e2t+12y(t) = \frac{e^{-2t} + 1}{2}

starts at 11 and decays smoothly to the limit 12\tfrac12 as tt \to \infty, the e2te^{-2t} term dying away. The problem is well-conditioned: perturbing the start to yε(0)=1+εy_\varepsilon(0) = 1 + \varepsilon shifts the solution by only yε(t)y(t)=εe2t|y_\varepsilon(t) - y(t)| = \varepsilon\, e^{-2t}, an error that itself shrinks with time. Small input error, small and shrinking output error — the good case.

Now solve it with the midpoint rule.

The midpoint rule is the explicit two-step method

yk+1=yk1+2δtf(tk,yk),y_{k+1} = y_{k-1} + 2\delta t\, f(t_k, y_k),

obtained by approximating the derivative at tkt_k with a symmetric difference quotient over the double step, y˙(tk)yk+1yk12δt\dot y(t_k) \approx \frac{y_{k+1} - y_{k-1}}{2\delta t}, centered on tkt_k rather than reaching forward from it. Spanning two steps, it needs two starting values, y0y_0 and y1y_1.

Substituting f(tk,yk)=2yk+1f(t_k, y_k) = -2 y_k + 1 turns it into the explicit recurrence

yk+1=yk1+2δt(2yk+1)=yk14δtyk+2δt,y0=1,y_{k+1} = y_{k-1} + 2\delta t(-2 y_k + 1) = y_{k-1} - 4\delta t\, y_k + 2\delta t, \qquad y_0 = 1,

started from y0y_0 and the exact second value y(δt)y(\delta t). Mechanically it runs without complaint. But watch what it produces deep into the integration:

step sizecomputed values
δt=1.0\delta t = 1.0y9=4945.5,y10=20953.9y_9 = -4945.5, \quad y_{10} = 20953.9
δt=0.1\delta t = 0.1y79=1725.3,y80=2105.7y_{79} = -1725.3, \quad y_{80} = 2105.7
δt=0.01\delta t = 0.01y999=154.6,y1000=158.7y_{999} = -154.6, \quad y_{1000} = 158.7

The true solution sits placidly at 12\tfrac12. The computed one swings between large negative and large positive values, growing in amplitude — and, crucially, refining the step size does not cure it. Shrinking δt\delta t from 1.01.0 to 0.010.01 only postpones the blow-up; the oscillation still takes over and the values still explode. At every step size, eventually, it goes wrong.

The picture makes the failure unmistakable:

The exact solution is the flat curve resting at 12\tfrac12. The midpoint approximations start out tracking it, then each peels away into growing oscillations — the larger the step size, the sooner the departure. Even the finest step (δt=0.01\delta t = 0.01) only holds the line longer before a swelling oscillation envelope takes over. A method for which this kind of blow-up simply cannot happen is called stable; one for which it can is unstable. And the lesson of the figure is that instability is not a flaw in the model or the data — the problem here is flawless — but purely a matter of having chosen the wrong algorithm.

A numerical method is stable if errors already present — round-off, inherited discretization error — stay bounded as the computation proceeds, rather than being amplified from step to step. Instability is the failure of this: small errors get magnified each step until the computed solution is overrun by strong, growing oscillations whose shape bears no resemblance to the true trajectory. Such a result is not acceptable — it cannot even be read as the exact solution of a slightly perturbed problem. Stability is a property of the method (together with its step size), not of the problem.

The midpoint rule is the cautionary case laid bare. It is consistent — its symmetric difference quotient genuinely approximates the first derivative as δt0\delta t \to 0 — yet it is not convergent, as the blow-up showed, and on an interval unbounded to the right the failure persists no matter how small δt\delta t is. Consistency without stability is not enough.

What is enough is the two together. This is the relation that closes the gap consistency left open:

Consistency + Stability = Convergence. Neither half suffices alone. Consistency makes each isolated step accurate; stability keeps inherited errors from being amplified; only together do they force the accumulated error to vanish — that is, convergence — and at that with the order preserved (a consistent, stable method of order kk is convergent of order kk). The midpoint rule fails precisely because it has the first property without the second.

Stability usually comes at a price — frequently a condition that the step size be small enough, sometimes very small (this is the source of the small-step requirement flagged back at the multistep methods). Which methods are stable is partly settled wholesale:

  • all explicit one-step methods (Euler, Heun, RK4) are stable;
  • the midpoint rule is not;
  • the Adams–Bashforth and Adams–Moulton multistep families are stable as ss-step methods for s>1s > 1.

Stiffness

The third phenomenon is the subtlest, because it strikes a method that has every good property and still fails. Consider another well-conditioned problem:

y˙=1000y+1000,y(0)=y0=2,\dot y = -1000 y + 1000, \qquad y(0) = y_0 = 2,

with exact solution

y(t)=e1000t+1.y(t) = e^{-1000t} + 1.

This solution is almost boring. The e1000te^{-1000t} term collapses to nothing almost instantly — by t=0.01t = 0.01 it is already about e100.00005e^{-10} \approx 0.00005 — so the curve drops from 22 to 11 in a razor-thin initial layer and then sits flat at 11 for the entire rest of the domain. Practically a horizontal line.

Apply the explicit Euler method:

yk+1=yk+δt(1000yk+1000)=(11000δt)yk+1000δt=(11000δt)k+1+1.y_{k+1} = y_k + \delta t(-1000 y_k + 1000) = (1 - 1000\delta t) y_k + 1000\delta t = (1 - 1000\delta t)^{k+1} + 1.

The behavior is governed entirely by the factor q=11000δtq = 1 - 1000\delta t raised to the (k+1)(k+1)-th power. If q<1|q| < 1 the term decays and yk1y_k \to 1 as it should (at δt=0.002\delta t = 0.002 exactly, q=1q = -1 and the term oscillates forever without decaying); but q>1|q| > 1 — which happens as soon as δt>0.002\delta t > 0.002 — makes qk+1q^{k+1} grow in magnitude and flip sign each step, so the computed solution oscillates and diverges, instead of resting at the flat line 11.

Here is the unsettling part. Explicit Euler is consistent; it is stable, like every explicit one-step method; and consistency plus stability give convergence. It has the full set of guarantees — and it diverges anyway.

A problem is stiff if an unimportant component of its solution — typically a fast transient that quickly dies away — forces a numerical method to use a ridiculously small step size across the entire domain, making the computation absurdly expensive even though the solution is almost everywhere trivial. Stiffness is a property of the problem (rooted in its solution carrying widely separated time scales), not of the method.

That is exactly what happens here: the negligible, short-lived term e1000te^{-1000t} — utterly insignificant after the first sliver of the interval — dictates a step size δt<0.002\delta t < 0.002 over the whole domain, just to keep the method from exploding. To reproduce what is essentially the constant 11, the method is forced into thousands of tiny steps. Enormous effort, trivial answer.

The resolution of the paradox is that consistency, stability, and convergence are asymptotic properties: they all carry the silent qualifier “for sufficiently small δt\delta t.” Convergence is a statement in big-O form — like a complexity bound, it describes the limit δt0\delta t \to 0 and promises nothing for a finite δt\delta t above the threshold. None of the guarantees is violated by the blow-up; the computation has simply not yet entered the asymptotic regime where they take effect, and for this problem the entry ticket — δt<0.002\delta t < 0.002 — is punishingly small. This is the case for all explicit methods, which makes them unsuitable for stiff ODEs.

The remedy is implicit methods. Apply the implicit Euler method to the same problem, evaluating the slope at the new point:

yk+1=yk+δtf(tk+1,yk+1)=yk+δt(1000yk+1+1000)=yk+1000δt1+1000δt=1(1+1000δt)k+1+1.y_{k+1} = y_k + \delta t\, f(t_{k+1}, y_{k+1}) = y_k + \delta t(-1000 y_{k+1} + 1000) = \frac{y_k + 1000\delta t}{1 + 1000\delta t} = \frac{1}{(1 + 1000\delta t)^{k+1}} + 1.

Now the controlling factor is 11+1000δt\frac{1}{1 + 1000\delta t}, which lies strictly between 00 and 11 for every positive δt\delta t. The term always decays, the solution always settles to 11 — no oscillations, convergent at any step size. The tiny-step tyranny is gone, and one can take large steps to trace the flat line cheaply.

The reason is structural. An explicit method builds its approximation out of polynomials in δt\delta t — and a polynomial (other than a constant) always runs off to ±\pm\infty as its argument grows, so it cannot mimic a bounded, decaying solution once the step is large. An implicit method instead produces rational functions, and a rational function like 11+x\frac{1}{1 + x} stays bounded — it tends to 00 as xx \to \infty rather than blowing up — so it can track confined behavior at large step sizes. That is the deeper reason large steps work for implicit methods and fail for explicit ones. Ergo: for stiff ODEs, always use implicit methods.

The three phenomena at a glance

Three separate things, then, can make the numerical solution of an ODE hard, and the practical first question for each is always the same: whose fault is it — the problem’s or the method’s?

  • Bad conditioning is a threatening property of the underlying problem, with nothing to do with the method at all. In the extreme it leaves very few numerical options, because no algorithm can undo a sensitivity baked into the problem.
  • Instability is a threatening property of the method — it can, for instance, force tiny steps or blow up outright. Here implicit methods are often superior to explicit ones.
  • Stiffness is a threatening property of the problem, but a tractable one: implicit methods handle it, and in fact for a stiff problem they are not just preferable but absolutely necessary.

The same split organizes the method properties met along the way. The table below gathers everything, sorted by what it is a property of — because that is what tells you whether a better algorithm can fix it (method properties) or whether you are stuck coping with it (problem properties).

ConceptProperty ofWhat it capturesCan the algorithm fix it?
consistencythe methodeach single step’s error vanishes as δt0\delta t \to 0yes — pick a consistent method
orderthe methodthe rate O(δtk)O(\delta t^k) at which the error shrinksyes — pick a higher-order method
stabilitythe method (+ step size)errors stay bounded instead of being amplified step to stepyes — pick a stable method (or shrink δt\delta t)
convergencethe methodthe global error vanishes as δt0\delta t \to 0 — and equals consistency + stabilityyes — it follows from the two above
conditioningthe problemsensitivity of the solution to perturbations of the inputno — baked into the problem; cope, don’t cure
stiffnessthe problema fast, unimportant solution component forcing tiny stepspartly — implicit methods cope with it cheaply

The one relation worth carrying away from all of this binds the method properties together: consistency + stability = convergence. Consistency alone (the midpoint rule had it) is not enough; stability is the second ingredient that turns locally-accurate steps into a globally-correct answer.

Boundary value problems

Everything so far has been about initial value problems — the natural shape for population dynamics, where the state is known at the start and propagated forward. The boundary value problem (BVP) set aside at the very beginning is a different animal, and it needs a different machine.

The defining contrast is informational. An IVP hands you everything at one end and lets you march: from the state at tat_a you read off the slope and step forward, point by point. A BVP fixes the solution at both ends and asks for the path between, so there is nothing to march from — you cannot step left to right when the constraint that pins the trajectory down lives at the far end you have not reached yet. The fix is to stop marching and instead discretize the whole interval at once, turning the differential equation into a system of equations solved simultaneously.

A useful rule of thumb sets the stage: an ODE needs as many side conditions as its order to have a unique solution. A BVP supplies two conditions (one at each end), so it generically pairs with a second-order equation. The general second-order BVP is

y¨=f(t,y,y˙),tattb,y(ta)=ya,y(tb)=yb.\ddot y = f(t, y, \dot y), \qquad t_a \le t \le t_b, \qquad y(t_a) = y_a, \quad y(t_b) = y_b.

Rather than the general ff, take the linear special case — linear because, although it has products like a(t)y˙a(t)\dot y, the unknown yy and its derivatives appear only to the first power and never multiplied together:

y¨=a(t)y˙+b(t)y+c(t).\ddot y = a(t)\,\dot y + b(t)\, y + c(t).

The simplest sub-case, a(t)=0a(t) = 0 with b(t)>0b(t) > 0, is the one to build the method on; under exactly those conditions the BVP is guaranteed a unique solution.

Discretizing the second derivative

Lay a grid over the interval. With nn subintervals the step size is h=δt=(tbta)/nh = \delta t = (t_b - t_a)/n, and the grid points are

t0=ta,tn=tb,ti=ta+ih.t_0 = t_a, \quad t_n = t_b, \quad t_i = t_a + i\, h.

The first derivative already had a difference quotient; the second derivative needs its own.

The second difference quotient approximates the second derivative of yy at tt from three equally spaced samples a step hh apart:

y¨(t)y(t+h)2y(t)+y(th)h2.\ddot y(t) \approx \frac{y(t + h) - 2 y(t) + y(t - h)}{h^2}.

It is symmetric about tt, using the point itself and one neighbor on each side.

Where the three-point formula comes from

A second derivative is the derivative of the first derivative, so apply a difference quotient twice. A forward step gives the slope just after tt, and a backward step the slope just before it:

y˙ ⁣(t+h2)y(t+h)y(t)h,y˙ ⁣(th2)y(t)y(th)h.\dot y\!\left(t + \tfrac{h}{2}\right) \approx \frac{y(t + h) - y(t)}{h}, \qquad \dot y\!\left(t - \tfrac{h}{2}\right) \approx \frac{y(t) - y(t - h)}{h}.

The second derivative is the rate of change of the slope, so take the difference of these two and divide by hh again:

y¨(t)1h(y(t+h)y(t)hy(t)y(th)h)=y(t+h)2y(t)+y(th)h2.\ddot y(t) \approx \frac{1}{h}\left( \frac{y(t+h) - y(t)}{h} - \frac{y(t) - y(t-h)}{h} \right) = \frac{y(t+h) - 2 y(t) + y(t-h)}{h^2}.

Both neighbors y(t+h)y(t+h) and y(th)y(t-h) enter because the formula is built from one slope reaching forward and one reaching back — the second derivative measures how those two slopes differ.

For the special case a(t)=0a(t) = 0, the linear ODE reads y¨b(t)y=c(t)\ddot y - b(t)\, y = c(t). Writing the second difference quotient at each interior grid point tit_i and abbreviating bi=b(ti)b_i = b(t_i), ci=c(ti)c_i = c(t_i) turns the single ODE into a discrete equation at every interior point:

1h2(yi+12yi+yi1)biyi=ci,i=1,,n1.\frac{1}{h^2}\bigl( y_{i+1} - 2 y_i + y_{i-1} \bigr) - b_i\, y_i = c_i, \qquad i = 1, \dots, n - 1.

The index runs only over the interior points i=1,,n1i = 1, \dots, n-1: the two endpoints y0=yay_0 = y_a and yn=yby_n = y_b are already known — that is what “boundary value” means — so they need no equation. That leaves n1n - 1 equations for exactly the n1n - 1 unknown interior values y1,,yn1y_1, \dots, y_{n-1}: a square linear system. Each equation couples a point only to its immediate neighbors, so the system is tridiagonal, and whether it can be solved comes down to the properties of its matrix.

The tridiagonal system

Multiplying each interior equation through by h2-h^2 clears the denominators and produces clean integer-like coefficients. The difference part loses its h2h^2 (becoming 1,+2,1-1, +2, -1) while the bib_i term picks one up (becoming bih2b_i h^2):

yi1+(2+bih2)yiyi+1=h2ci.-y_{i-1} + (2 + b_i h^2)\, y_i - y_{i+1} = -h^2 c_i.

For the first interior point (i=1i = 1) the term y0=ya-y_0 = -y_a is a known number, so it moves to the right; likewise yn=yb-y_n = -y_b at i=n1i = n-1. That is why the first and last right-hand sides carry an extra boundary term. The whole system is

(2+b1h2112+b2h2112+bn1h2)(y1y2yn1)=(h2c1+yah2c2h2cn1+yb).\begin{pmatrix} 2 + b_1 h^2 & -1 & & \\ -1 & 2 + b_2 h^2 & -1 & \\ & \ddots & \ddots & \ddots \\ & & -1 & 2 + b_{n-1} h^2 \end{pmatrix} \begin{pmatrix} y_1 \\ y_2 \\ \vdots \\ y_{n-1} \end{pmatrix} = \begin{pmatrix} -h^2 c_1 + y_a \\ -h^2 c_2 \\ \vdots \\ -h^2 c_{n-1} + y_b \end{pmatrix}.

Every row has just three nonzero entries (two at the ends) — the diagonal and its two neighbors — the signature of a tridiagonal matrix.

The condition b(t)>0b(t) > 0 now pays off. It makes every diagonal entry 2+bih22 + b_i h^2 strictly larger than 22, while the off-diagonal magnitudes in each row sum to at most 22. The diagonal therefore dominates its row.

A square matrix A=(aij)A = (a_{ij}) is strictly diagonally dominant if in every row the diagonal entry outweighs all the off-diagonal entries combined in magnitude:

aii>jiaiji.|a_{ii}| > \sum_{j \ne i} |a_{ij}| \quad \forall i.

A strictly diagonally dominant matrix is always invertible, so the linear system has a unique solution for every right-hand side.

So the matrix here is strictly diagonally dominant, hence invertible — the discretized BVP can always be solved, for any data. Beyond that it is symmetric (the off-diagonals are all 1-1) and positive definite, the well-behaved combination that makes the system cheap and stable to solve. The eigenvalues of the matrix are what ultimately govern how fast the discrete solution converges to the true one as the grid is refined — here at second order.

First derivatives: central differences and upwinding

Dropping the assumption a(t)=0a(t) = 0 lets the first derivative back into the equation, and it has to be discretized too. The natural choice is symmetric, straddling the point.

The central difference approximates the first derivative by a symmetric quotient straddling the point, accurate to second order:

y˙(t)y(t+h)y(th)2h.\dot y(t) \approx \frac{y(t + h) - y(t - h)}{2 h}.

This is the very same symmetric quotient that defined the midpoint rule — and that rule was unstable as an IVP time-stepper, blowing up no matter how small the step. The interesting twist is that here it is perfectly acceptable: in the BVP setting the whole grid is solved at once as one linear system rather than marched forward in time, so the step-to-step error amplification that wrecked the midpoint rule never gets a chance to act.

Substituting the central difference for y˙\dot y and the second difference quotient for y¨\ddot y into the full linear ODE y¨=a(t)y˙+b(t)y+c(t)\ddot y = a(t)\dot y + b(t) y + c(t), then clearing denominators, gives the interior difference equation

(1aih2)yi1+(2+bih2)yi+(1+aih2)yi+1=h2ci.\left( -1 - \frac{a_i h}{2} \right) y_{i-1} + \left( 2 + b_i h^2 \right) y_i + \left( -1 + \frac{a_i h}{2} \right) y_{i+1} = -h^2 c_i.
Assembling the difference equation

Rearrange the ODE as y¨aiy˙biy=ci\ddot y - a_i \dot y - b_i y = c_i and insert both quotients at tit_i:

yi+12yi+yi1h2aiyi+1yi12hbiyi=ci.\frac{y_{i+1} - 2 y_i + y_{i-1}}{h^2} - a_i\, \frac{y_{i+1} - y_{i-1}}{2 h} - b_i\, y_i = c_i.

Multiply through by h2-h^2 and collect the three unknowns. The yi1y_{i-1} coefficient gathers 1-1 from the second-difference term and aih2-\tfrac{a_i h}{2} from the central-difference term; the yi+1y_{i+1} coefficient gathers 1-1 and +aih2+\tfrac{a_i h}{2}; the yiy_i coefficient gathers +2+2 and +bih2+b_i h^2. That is the displayed equation.

It still yields a tridiagonal matrix with three nonzero entries per row — but the off-diagonals are no longer a tidy 1-1. Diagonal dominance now needs the step small enough that aih2|a_i h| \le 2, so that 1±aih21 \pm \tfrac{a_i h}{2} stays nonnegative and the off-diagonals still sum to 22. Push the step past that and dominance — and with it the guarantee of a unique solution — can fail. Small steps again, the recurring tax.

The common escape is to discretize the first derivative one-sidedly instead, in the direction the coefficient points.

The upwind discretization approximates the first derivative by a one-sided difference whose direction is chosen by the sign of the coefficient aia_i:

y˙(t)1h{yi+1yiif ai<0,yiyi1if ai0.\dot y(t) \approx \frac{1}{h} \begin{cases} y_{i+1} - y_i & \text{if } a_i < 0, \\ y_i - y_{i-1} & \text{if } a_i \ge 0. \end{cases}

It always produces a strictly diagonally dominant, invertible system, whatever the step size — but it is only first-order accurate, a step down from the central difference’s second order.

So the trade is explicit: the central difference is more accurate but conditionally solvable, the upwind difference is unconditionally solvable but less accurate.

Kinds of boundary conditions

The conditions used so far prescribe the value of yy at each end. That is one of several standard kinds, and each reshapes the system’s first and last rows a little differently.

A Dirichlet boundary condition prescribes the value of the solution at a boundary point, e.g. y(ta)=yay(t_a) = y_a. The boundary value is known outright, so it is not an unknown of the system — it moves to the right-hand side.

A Neumann boundary condition prescribes the first derivative at a boundary point, y˙(ta)=y˙a\dot y(t_a) = \dot y_a, rather than the value. The boundary value y0y_0 itself is now unknown and stays in the system.

A Neumann condition is discretized with the help of a virtual point t1t_{-1} just outside the interval. Writing the central difference for the prescribed slope at tat_a and solving for the ghost value,

y˙ay1y12hy1=y12hy˙a,\dot y_a \approx \frac{y_1 - y_{-1}}{2 h} \quad \Rightarrow \quad y_{-1} = y_1 - 2 h\, \dot y_a,

and then substituting that into the second difference quotient at the boundary eliminates the virtual point:

y¨(ta)y12y0+y1h2=2y12y02hy˙ah2.\ddot y(t_a) \approx \frac{y_{-1} - 2 y_0 + y_1}{h^2} = \frac{2 y_1 - 2 y_0 - 2 h\, \dot y_a}{h^2}.

Because the boundary values y0y_0 and yny_n are no longer given, they become additional unknowns, and the system grows accordingly.

A periodic boundary condition identifies the two ends, requiring the solution to take the same value at both: y(ta)=y(tb)=y0y(t_a) = y(t_b) = y_0. The trajectory closes up on itself.

The shooting method

Discretizing the whole interval is not the only way. A completely different approach turns the BVP back into the kind of problem we already know how to solve.

The shooting method reduces a boundary value problem to a sequence of initial value problems. It replaces the unknown far-end condition with a guessed initial slope ss — the shooting angle — integrates the resulting IVP forward, and adjusts ss until the trajectory lands on the prescribed endpoint.

The name is literal. Imagine aiming a shell from your position at a distant target: you know where you are firing from and where you want to land, but not the launch angle that connects them. So you pick an angle, fire, see where the shot falls, correct, and fire again — converging on the angle that hits. Replacing the endpoint constraint by a launch angle is exactly the trick.

Concretely, the BVP

y¨=f(t,y,y˙),y(ta)=ya,y(tb)=yb\ddot y = f(t, y, \dot y), \qquad y(t_a) = y_a, \quad y(t_b) = y_b

is replaced by the IVP with the known start value but an unknown starting slope,

y¨=f(t,y,y˙),y(ta)=ya,y˙(ta)=s,\ddot y = f(t, y, \dot y), \qquad y(t_a) = y_a, \quad \dot y(t_a) = s,

and the task becomes: find the value sˉ\bar s whose forward solution y(t;sˉ)y(t; \bar s) satisfies the missing end condition,

y(tb;sˉ)=yb.y(t_b; \bar s) = y_b.

This is reminiscent of an inverse problem — we are hunting for the input (the angle ss) that produces a desired output (hitting yby_b) — and it is solved by iteration, much like a Newton iteration, with one IVP integrated per iteration step. Like the predictor–corrector idea, it trades a single hard solve for a few cheaper, well-understood ones.

Summary

Population dynamics has served as the first fully worked continuous example, and it splits into the two halves the whole course turns on.

The modeling half — developed among the models for population dynamics — derived a sequence of models of increasing fidelity for different scenarios, from unconstrained Malthusian growth through the logistic and saturation models to coupled multi-species systems, all expressed as initial value problems of ordinary differential equations, and studied for what they say qualitatively: existence of solutions, stationary states, long-term behavior.

The simulation half is this page. Because the direct analytic route generally fails — most ODEs have no closed-form solution — the models have to be solved numerically, and that demanded a careful look at how: discretization and its errors; the one-step, multistep, and implicit methods that march an initial value problem forward; the conditioning, stability, and stiffness phenomena that decide whether the march succeeds; and finally the finite-difference and shooting techniques that handle boundary value problems. Modeling poses the equations; simulation extracts the numbers when no formula will.