Enter a matrix or pick an example, apply elementary row operations one at a time (or reduce everything at once) and watch the system of equations change. Entries can be integers, fractions and expressions with letters, such as 1/3, a, (a+1)/(a-1) and sqrt(2).
Matrix and elementary operations
input, reduction and undo
Use * to multiply. Symbolic computation is generic: dividing by an expression with parameters assumes that expression is nonzero.
Elementary operations
The system as equations
updated after every operation
Geometric interpretationlines in 2D, planes in 3D
Available for numeric square systems 2×2 and 3×3. The solution set is highlighted when it is a point, a line or a plane.
Matrix analysisdeterminant, rank, null space, inverse and spectrum
ExportLaTeX and matrices for CAS
Generate LaTeX as:
Generate matrices for a CAS:
Symbolic engine: Algebrite 1.4.0 (MIT). Graphs: Plotly.js 3.3.1 (MIT). Both are loaded when needed from a public CDN; your matrices are never sent to a calculation server.
A plate, fixed temperatures on its boundary, and one question: how do we find the temperature at every interior point? The Gauss–Seidel method answers it one update at a time. Below the animation is a short course that takes you from Fourier's law to the convergence rate of the method, with numbers you can check on the page.
Temperature distribution
The plate as it evolves
Ready to start
Click the plate to inspect a node.
COMPLETED ITERATIONS0
CHANGE \(\|T^{(k+1)}-T^{(k)}\|_\infty\)—
RESIDUAL \(\|r^{(k+1)}\|_\infty\)—
The boundary stays fixed in every iteration.
INSIDE THE METHOD
The neighbors give the answer
Node (i, j)
With no internal heat sources, the Laplace equation is \(\dfrac{\partial^2 T}{\partial x^2}+\dfrac{\partial^2 T}{\partial y^2}=0\). On this mesh, \(\Delta x = \Delta y\): each interior node receives the average of its four neighbors.
The sweep goes left to right and top to bottom, and new values are used immediately. Neighbors on the boundary keep their prescribed value.
WATCH THE CONVERGENCE
Closer and closer
We use the max norm: \(\|v\|_\infty=\max_m |v_m|\). The change compares two iterations; the residual measures how far the equilibrium equations are from holding.
CHANGENormalized residual
Logarithmic vertical scaleIterations →
When does the animation stop?
At the end of a sweep, when \(\|T^{(k+1)}-T^{(k)}\|_\infty\leq\varepsilon\). It suffices to take the largest absolute difference between the new and old temperatures. The dashed line marks \(\varepsilon\).
At each node, \(r_{i,j}=T_{i,j}-(T_{i-1,j}+T_{i+1,j}+T_{i,j-1}+T_{i,j+1})/4\). Residual and change are not the error with respect to the exact solution.
THEORY AGAINST MEASUREMENT
Predicted factor per sweep, \(\rho_{GS}\)
—
Measured factor (last sweeps)
—
Sweeps still needed (estimate)
—
Estimated distance to the exact solution
—
The factor is \(\rho_{GS}=\big(\tfrac12\cos\tfrac{\pi}{N_x}+\tfrac12\cos\tfrac{\pi}{N_y}\big)^2\) for a mesh with \(N_x\times N_y\) cells. The estimates use \(\|e\|\approx\rho\,\delta/(1-\rho)\), where δ is the last change. See “Reading the page” in the course below.
A SHORT COURSE
The whole theory, step by step
Each question opens a short lesson tied to something on this page. Read them in order, or jump to the one you need; the last group has experiments and exercises with the numbers you should obtain.
1. The physics
What exactly is being animated?
The successive approximations of an iterative method for the steady-state problem. The counter measures Gauss–Seidel sweeps, not physical time: this animation does not solve the transient heat equation. We assume a homogeneous plate of constant conductivity, with no internal heat sources, thin enough that the temperature depends only on the position (x, y) in the plate. The colors are the temperature, the numbers under the plate are the current approximation, and the three counters tell how far the iteration has come.
From heat flow to Laplace's equation
Heat flows from hot to cold, at a rate proportional to the temperature gradient (Fourier's law): the flux is \(\mathbf q=-k\nabla T\), with k the conductivity. Conservation of energy in a small piece of plate says that the temperature rises at the rate at which flux enters minus the rate at which it leaves, which gives the heat equation
If the edges are kept at fixed temperatures for a long time, the plate stops changing: \(\partial T/\partial t=0\). The steady state then satisfies Laplace's equation \(\Delta T=0\) in the plate, with the prescribed temperatures on the boundary (a Dirichlet problem). The page solves exactly this problem: its answer is where the physical plate would settle, and the iteration is a computational path to it, not the physical path.
Why can the answer be trusted? The maximum principle
Solutions of Laplace's equation have the mean value property: the temperature at a point is the average of the temperatures on any circle around it. Two consequences follow. First, the maximum principle: the hottest and coldest points are on the boundary, never inside. Check it on the page: the color scale always goes from the smallest to the largest boundary value, and no interior color falls outside it (the “hot interior, cold boundary” example just cools down to uniform 0 °C). Second, uniqueness: two solutions with the same boundary values differ by a solution with zero boundary values, which must be zero everywhere. So the temperature field the method converges to is the answer, not one among several.
The discrete version is the very formula the method uses: each interior value is the average of its four neighbors. Averages never leave the range of the numbers averaged, so every Gauss–Seidel iterate stays between the smallest and the largest boundary value, too.
An exact solution to compare with
Take the example “Sinusoidal top edge”: the plate is \(0\le x\le a=1.5\), \(0\le y\le b=1\), the top edge has \(T=A\sin(\pi x/a)\) with A = 100 °C and the other three edges are at 0 °C. Separation of variables gives the exact solution (y measured from the bottom edge)
At the center of the plate, \(x=0.75,\ y=0.5\), it gives \(T=100\cdot0.3124\ldots=31.2444\) °C. This is the number the numerical solution should approach as the mesh is refined. Probe the central node of the 31 × 21 and 61 × 41 meshes (click on the plate) and compare: Experiment 5 below has the numbers.
2. From the equation to a linear system
The mesh and the central difference
We cover the plate with a grid of equally spaced nodes, \(x_i=i\,\Delta x\), \(y_j=j\,\Delta y\), and look for the temperatures \(T_{i,j}\) at the nodes. A second derivative becomes a difference, by Taylor's theorem:
The formula is correct up to an error proportional to \(\Delta x^2\): it is a second-order approximation. Halving the spacing divides the error of the formula by four. The same holds in y.
The five-point formula and the linear system
Insert the two central differences in \(\Delta T=0\). With \(\Delta x=\Delta y\) (all meshes on this page) the equation at an interior node is
Neighbors on the boundary are known numbers and move to the right-hand side. We get one linear equation per interior node, hence a system \(A\,x=b\) with \(N=(n_x-2)(n_y-2)\) unknowns: 126, 551, 2 301, 9 401 and 38 001 for the five meshes of the page. Here is the smallest interesting case, a 2 × 2 interior (unknowns \(T_{1,1},T_{2,1},T_{1,2},T_{2,2}\) numbered row by row, with boundary values entering \(b\)):
The matrix is symmetric and positive definite, diagonally dominant, and has at most five nonzero entries per row, whatever the size: it is sparse. For N = 38 001 a dense matrix would have 1.4 billion entries; this one has fewer than 190 000 nonzeros. Sparsity is the reason iterative methods are attractive.
Two different errors: discretization and iteration
The picture on the screen is wrong for two unrelated reasons. The discretization error is the difference between the exact solution of the linear system and the true temperature of the plate: it comes from replacing derivatives by differences, behaves like \(\Delta x^2\), and does not depend on how long we iterate. The iteration error is the difference between the current iterate and the exact solution of the linear system: it goes to zero with the sweeps. Stopping tolerance and mesh must be chosen together: there is no point in iterating to 10⁻¹² on a coarse mesh whose discretization error is 0.1, and no point in a fine mesh stopped at the first sweeps.
Numbers for the sinusoidal edge, center of the plate (exact value 31.2444 °C): the 31 × 21 mesh gives 31.2677 (error 0.0233) and the 61 × 41 mesh gives 31.2502 (error 0.0058). The error was divided by 4.0 when the spacing was halved, exactly as second order predicts.
3. Solving the system
Why not solve the system directly?
Gaussian elimination on a dense N × N matrix costs about \(N^3/3\) operations and \(N^2\) memory. For the 31 × 21 mesh (N = 551) this is trivial, and for N = 38 001 it is hopeless if the sparsity is ignored (about \(2\times10^{13}\) operations). Exploiting the band structure (the unknowns are only coupled with those one row away) brings elimination in two dimensions down to a manageable cost, and in practice direct banded or sparse solvers are excellent for plates. The trouble appears in three dimensions, or when the mesh keeps being refined: elimination creates new nonzeros (fill-in) and its cost grows much faster than the number of unknowns. Iterative methods only multiply by the sparse matrix, never change it, need memory proportional to N, and give a usable answer after a few sweeps. Their problem is the speed of convergence, studied below.
Jacobi and Gauss–Seidel
Solve each equation for its own unknown and use the latest values available. Jacobi uses only values from the previous sweep: \(T^{(k+1)}_{i,j}=\tfrac14\big(T^{(k)}_{i-1,j}+T^{(k)}_{i+1,j}+T^{(k)}_{i,j-1}+T^{(k)}_{i,j+1}\big)\). Gauss–Seidel overwrites each value at once, so the neighbors already visited in this sweep (west and north, with the page's order of left to right and top to bottom) are new and the others (east and south) are old:
The “Inside the method” card shows this, node by node, if you choose “Node by node”. In matrix language, write \(A=D-L-U\) (diagonal, minus the strictly lower part, minus the strictly upper part). Jacobi is \(Dx^{(k+1)}=(L+U)x^{(k)}+b\) and Gauss–Seidel is \((D-L)x^{(k+1)}=Ux^{(k)}+b\). Gauss–Seidel needs no second array, which is why the code is so short, but the result depends on the order of the sweep.
When does it converge, and how fast?
Write the exact solution as \(x^*\) and the error as \(e^{(k)}=x^*-x^{(k)}\). Subtracting the iteration from the equation that \(x^*\) satisfies gives \(e^{(k+1)}=G\,e^{(k)}\), where G is the iteration matrix (\(G=(D-L)^{-1}U\) for Gauss–Seidel). So \(e^{(k)}=G^ke^{(0)}\), and the iteration converges for every starting guess if and only if the spectral radius satisfies \(\rho(G)<1\). Diagonal dominance (with irreducibility, true here) or symmetric positive definiteness are sufficient conditions, and both hold for our matrix.
For this problem the eigenvalues are known. With \(N_x=n_x-1\) and \(N_y=n_y-1\) cells in each direction,
The relation \(\rho_{GS}=\rho_J^2\) (Young, for “consistently ordered” matrices, as here) means that Gauss–Seidel converges twice as fast as Jacobi. Values for the meshes of the page: 0.9305 (16 × 11), 0.9823 (31 × 21), 0.9956 (61 × 41), 0.9989 (121 × 81), 0.9997 (241 × 161). They crowd toward 1: the finer the mesh, the slower the method.
How many sweeps will it take?
If the error shrinks by the factor ρ at each sweep, reducing it by a factor \(10^{-d}\) needs \(k\approx d\ln10/(-\ln\rho)\) sweeps. Since \(1-\rho_{GS}\) is proportional to \(1/N_x^2\), the number of sweeps grows like \(N_x^2\): refining the mesh by a factor 2 multiplies the unknowns by 4 and the sweeps by about 4, so the total work by about 16. Measured with the “four sides” example from zero and tolerance 0.001: 120 sweeps (16 × 11), 405 (31 × 21), 1 314 (61 × 41). The factor between the last two is 3.2 and tends to 4 as the tolerance is applied to ever slower modes.
This is the central weakness of the method, and the motivation for better ones, next.
Faster methods: SOR, conjugate gradients, multigrid
Successive over-relaxation (SOR) moves each value past the Gauss–Seidel value: \(T\leftarrow T+\omega\,(T_{GS}-T)\), with \(1\lt \omega\lt 2\). The best choice for this problem is \(\omega_{opt}=2/\big(1+\sqrt{1-\rho_J^2}\big)\), and then the spectral radius falls to \(\omega_{opt}-1\), so the number of sweeps grows like \(N_x\) instead of \(N_x^2\). For the 31 × 21 mesh \(\omega_{opt}=1.765\): the “four sides” example converges in 54 sweeps instead of 405 (and Jacobi needs 738). One extra line of code, a factor 7 in time.
Two further ideas, both topics for a later course. The conjugate gradient method, for symmetric positive definite matrices, also needs about \(N_x\) iterations. Multigrid methods use the fact that Gauss–Seidel kills jagged errors in a few sweeps and only smooth errors survive (watch the first sweeps on the page: the picture becomes smooth fast, and then very slowly drifts); smooth errors can be represented and corrected on a coarser mesh, at a fraction of the cost. A well-built multigrid solves the problem with work proportional to N, the best possible.
4. Reading the page
The three numbers: change, residual and error
Three different quantities are easy to confuse. The error \(\|e^{(k)}\|=\|x^*-x^{(k)}\|\) is what we want and cannot see, since \(x^*\) is unknown. The change \(\delta_k=\|x^{(k+1)}-x^{(k)}\|_\infty\) is the largest difference between two consecutive sweeps. The residual measures how badly the equations \(T=\text{average of neighbors}\) are violated.
The change is the stopping test of the page, and it is a good indicator, not a guarantee. If the error shrinks by ρ at each sweep, \(x^{(k+1)}-x^{(k)}=e^{(k)}-e^{(k+1)}\approx(1-\rho)\,e^{(k)}\), hence
For the 31 × 21 mesh \(\rho/(1-\rho)\approx55\): when the change falls under 0.001 °C the temperatures can still be off by about 0.055 °C. I checked it: the “four sides” example stops after 405 sweeps with a true error of 0.0546 °C (the factor 55 predicted 0.055). On the finest mesh the factor is 3 591. This is why the page shows an estimate of the distance to the exact solution next to the chart, and why serious codes use stricter tests than “nothing moved”.
What the chart and the theory box tell
Since \(e^{(k)}=G^ke^{(0)}\), after a short transient the error is dominated by the eigenvector of the largest eigenvalue, and it is multiplied by about ρ at each sweep. The logarithm of the change then falls along a straight line with slope \(\log_{10}\rho\) per sweep: a straight line on the logarithmic chart is the signature of geometric (linear) convergence. The “theory” box compares four numbers: the predicted factor \(\rho_{GS}\) from the formula above, the factor measured from the last sweeps (geometric mean of the ratios of consecutive changes: it should settle on the prediction), the number of additional sweeps predicted to reach the tolerance, \(\ln(\varepsilon/\delta_k)/\ln\rho\), and the estimate \(\rho\,\delta_k/(1-\rho)\) of the distance to the exact solution. Watch the measured factor approach the predicted one as the sweeps proceed.
The slowest-decaying error is the smoothest one: a single hump over the whole plate (the product \(\sin\sin\)). That is why, late in the run, the colors seem frozen while the change is still falling: only a very smooth, small correction remains.
What happens at the corners?
When two sides have different values at the same corner, we display their average. The four corners are not direct neighbors of any interior node, so this convention does not change the system being solved. (Mathematically the Dirichlet data jump at such a corner, and the exact solution has a singular behavior there; the solution is still fine away from the corner, but the discretization error is larger near it.)
5. Try it yourself
Guided experiments
Count the sweeps. Example “Four sides, four temperatures”, mesh 31 × 21, tolerance 0.001, start 0. Press Start and note the number of sweeps when it stops (405). Does the theory box predict it well during the run?
Refine the mesh. Repeat with 16 × 11 (120 sweeps) and 61 × 41 (1 314 sweeps). Compute the ratios and compare with “about 4 per refinement”.
Change the start. With 31 × 21, start the interior at 50 °C instead of 0: 264 sweeps. At 100 °C: 413. Why is 50 the best of the three?
Superposition. The solution is linear in the boundary data. Probe a node in “Four sides”, then run “Heated top edge only” and the same plate with only the left edge at 100 (type 0, 0, 100, 0) and with only the right at 100. Combine \(1\cdot\)top\(+0.75\cdot\)left\(+0.25\cdot\)right and compare with the first probe.
Compare with the exact solution. Example “Sinusoidal top edge”. Probe the center node: 31.268 (31 × 21), 31.250 (61 × 41); exact value 31.244. Refine again and watch the error fall by a factor 4.
Tolerance against accuracy. On 31 × 21, tolerance 0.001 stops with a true error of about 0.055; tolerance 10⁻⁶ takes 791 sweeps and leaves an error of about 5·10⁻⁵. Read the estimate in the theory box at each stop.
Maximum principle. Example “Hot interior, cold boundary”: no interior value ever leaves the range, and everything cools to 0 °C.
Order of updates. Choose “Node by node” and follow the Inside-the-method card: which neighbors are new and which are old?
Exercises for the class
Derive the five-point formula for unequal spacings: \(T=\dfrac{w_x(T_W+T_E)+w_y(T_N+T_S)}{2(w_x+w_y)}\) with \(w_x=1/\Delta x^2\), \(w_y=1/\Delta y^2\). (The downloadable code uses it.)
For the 2 × 2 interior system above with \(b=(100,100,0,0)\), do two Jacobi and two Gauss–Seidel sweeps by hand from zero and compare with the exact solution (which is \((37.5,\,37.5,\,12.5,\,12.5)\)).
In the downloadable code change one line to obtain Jacobi (read from a copy of the old array) and check that it needs about 1.8 times as many sweeps (738 against 405).
Add SOR: \(t[p]=t[p]+\omega\,(\text{value}-t[p])\). Try \(\omega=1.765\) on 31 × 21 and find the best \(\omega\) experimentally; compare with \(2/(1+\sqrt{1-\rho_J^2})\).
Check that the measured contraction factor settles on \(\rho_{GS}\), and that \(\rho_{GS}=\rho_J^2\) by running Jacobi too.
Richardson extrapolation: with the center values \(T_h=31.2677\) (31 × 21) and \(T_{h/2}=31.2502\) (61 × 41) compute \((4\,T_{h/2}-T_h)/3\). Compare with the exact 31.2444. Why does it work?
Show that the matrix of the system is symmetric positive definite, and explain why this guarantees the convergence of Gauss–Seidel.
Two rods, two masses, and Newton's laws. Release pendulums that differ by a millionth of a radian and watch them stay together, or fly apart. The motion is computed live from the differential equations, with methods you can read in a few lines of plain JavaScript: Euler, Heun, the classical Runge–Kutta method of order 4, and four symplectic integrators that keep the energy from drifting. An energy graph shows what each method does to a quantity that nature conserves.
Simulation
The pendulums
Ready to start
TIME0.00 s
ENERGY ERROR \(|E-E_0|/E_{\max}\)0
DIVERGENCE RATE \(\lambda\)—
Initial conditions
Positions and speeds
Angles are measured from the downward vertical, in degrees; angular velocities are in radians per second. Each row is one pendulum. Move a slider or type an exact value (a difference of 0.00006° is already a millionth of a radian). Any change restarts the motion from time 0.
Pendulum k gets the data of pendulum 1 plus (k−1) times this shift in θ₁: the quickest way to make nearly identical twins.
What a conserved quantity reveals
The energy graph
With no damping the true energy never changes, so any change in the computed energy is numerical error. Each view tells a different story: the error on a logarithmic scale, the exchange between kinetic and potential energy, or a race between all seven methods starting from pendulum 1.
time (s) →
Error = |E − E₀| divided by (m₁+m₂)gℓ₁ + m₂gℓ₂, the energy of the pendulum balanced upside down. A flat line near the bottom is the best a computer can do (about 10⁻¹⁶, rounding). A line that climbs steadily is drift; a line that wobbles without climbing is the signature of a symplectic method. Switching to the race view restarts the experiment.
Energy-friendly integrators
Symplectic methods
In Hamiltonian form the state is the pair (q, p) of angles and momenta, and the energy is the function H. A symplectic method respects the geometry of the flow in the (q, p) space, and for that reason its energy error does not drift: it only wobbles, for very long times. The price is that, for this H, each step requires solving equations (the methods are implicit).
Symplectic does not mean exactly energy-conserving: the energy stays within a thin band around its initial value, and the band shrinks like h² (or h⁴ for Gauss). The row of the table for the method now selected is highlighted. The counters count evaluations of f (or of the Hamiltonian derivatives) per step and fixed-point iterations per step.
Inside the method
One step of Runge–Kutta 4
The state y collects the two angles and the two angular velocities, and the motion obeys y′ = f(y). One step of size h evaluates the slope f four times and averages them with weights 1, 2, 2, 1. Below are the numbers of the latest step for pendulum 1.
Each curve is the distance between the lower masses of two pendulums, divided by the total length, on a logarithmic scale. A straight rising line means exponential growth: the signature of chaos. A flat line means order.
distance, logarithmic scaletime (s) →
The divergence rate λ shown above is the slope of ln(distance) over the stretch where the distance grows from 3 times its start to 5% of the total length.
The landscape of chaos
Which starts flip over?
Each point is a way to release the pendulum from rest, with θ₁ across and θ₂ up. The color tells when an arm first swings over the pivot. Dark regions never flip within 10 s: ordered motion. The tangle of colors is chaos. Click to move the selected pendulum there; scroll to zoom in.
Scroll or pinch to zoom, drag to pan; the map is recomputed for the window you see, so zooming reveals finer structure.
early fliplate flipno flip (order)
Computed in your browser with the same Runge–Kutta 4 code, step 0.01 s, up to 10 s; it is redone when the masses, lengths or damping change. The map always uses RK4, whatever method is selected above.
The physics
Equations of motion
With θ₁ and θ₂ measured from the vertical, Lagrange's equations give two coupled second-order equations. They are linear in the accelerations:
Solving the 2×2 system for the accelerations α₁, α₂ and adding the angular velocities as unknowns turns it into a first-order system y′ = f(y), the form Runge–Kutta needs:
The energy E is conserved when γ = 0, so its change in time is a free test of the numerical method. With damping (γ > 0) the energy really decays, and the graphs then show that physical loss, not an error.
A short course
What is going on here?
Open the questions in order or at random. Each one points to something you can see in the animation, and the last two lists suggest experiments and exercises.
1. From physics to equations
Where do the equations come from?
Put the pivot at the origin. With the angles θ₁ and θ₂ measured from the downward vertical, the two masses are at \(x_1=\ell_1\sin\theta_1,\ y_1=-\ell_1\cos\theta_1\) and \(x_2=x_1+\ell_2\sin\theta_2,\ y_2=y_1-\ell_2\cos\theta_2\). Newton's laws for each mass involve the unknown tensions in the rods. Lagrange's method avoids them by working directly with the two angles, the generalized coordinates.
With δ = θ₁ − θ₂, the kinetic and potential energies are
The Lagrangian is \(L=T-V\), and the motion obeys the Euler–Lagrange equations \(\frac{d}{dt}\frac{\partial L}{\partial\dot\theta_k}-\frac{\partial L}{\partial\theta_k}=0\) for k = 1, 2. Doing the derivatives gives the 2×2 system shown in “Equations of motion”. The cross term of T, which contains cos δ, is what couples the two angles; it is also what makes the equations nonlinear.
Why a first-order system, and what does the program really compute?
Euler's and Runge–Kutta methods solve first-order systems \(y'=f(y)\). A second-order equation becomes first-order when the velocity is added as an unknown: \(\dot\theta=\omega,\ \dot\omega=\alpha\). For two angles the state has four components, \(y=(\theta_1,\theta_2,\omega_1,\omega_2)\).
To evaluate \(f(y)\) the program must know the accelerations, so each evaluation solves the 2×2 linear system for \(\alpha_1,\alpha_2\) by Cramer's rule (see the function deriv in the downloadable code: about ten lines). The right-hand side does not contain t, so the system is autonomous, and that is why the code carries no time variable. Everything else in the page, the animation, the graphs and the map, is built on repeated calls to this one function.
Keeping two terms gives \(y_{n+1}=y_n+h\,f(y_n)\). The discarded term is the local truncation error, of size \(h^2\): the error made in one step. To reach a final time T we need \(T/h\) steps, and the local errors accumulate, so the global error is of size \(h\). Halving h doubles the work and halves the error, a poor bargain.
Geometrically, Euler follows the tangent line for a time h; but the true path curves, so the tangent step leaves it toward the outside. For an oscillator this makes the amplitude grow: for \(\ddot x=-\omega^2x\) one can check that each Euler step multiplies the energy by \(1+h^2\omega^2\). Select Euler above and watch the pendulum gain energy from nothing.
What does “order” mean, and how can it be measured?
A method has order p when its global error behaves like \(C\,h^p\) as \(h\to0\). Euler has p = 1, Heun p = 2, Runge–Kutta 4 has p = 4; among the symplectic methods, symplectic Euler has p = 1, Störmer–Verlet and the implicit midpoint rule p = 2, and the two-stage Gauss method p = 4 (the table in “Symplectic methods” lists them).
Order is measured by an experiment: solve the same problem with step h and with step h/2. If the errors are e and e′, then \(e/e'\approx2^p\), that is, \(p\approx\log_2(e/e')\). Exercise 1 below does it with a reference solution. The energy error shows a related, but not identical, exponent, because energy only sees the amplitude of the error, not its phase. For small oscillations the energy error of Heun grows like \(h^3\) and that of RK4 like \(h^5\) per unit time: in the “small oscillations” experiment, for the 20° pendulum (the top curve), RK4 at h = 0.05 ends 60 s with about 1.7·10⁻⁵, and at h = 0.02 with 1.8·10⁻⁷; the ratio is about 100, close to \(2.5^5\approx98\).
Heun and Runge–Kutta 4: where the weights come from
Solving \(y'=f\) over one step means computing \(\int_t^{t+h}f\), and every method is a quadrature rule in disguise. Euler uses the left-end rectangle. Heun uses the trapezoid rule, \(y_{n+1}=y_n+\tfrac h2\,[\,f(y_n)+f(y_n+h\,f(y_n))\,]\), where the unknown right-end slope is predicted by an Euler step. RK4 is Simpson's rule \(\tfrac h6(f_a+4f_m+f_b)\): the weight 4 at the midpoint is split between k₂ and k₃, two successively improved estimates of the midpoint slope, and k₄ is the slope at the end. The weights 1, 2, 2, 1 in the formula are exactly Simpson's 1, 4, 1.
The coefficients are collected in the Butcher tableau:
Choosing coefficients so that the method agrees with the Taylor series through \(h^4\) requires eight conditions; this tableau satisfies all of them. Four slopes per step cannot reach order 5: that needs six (a classical result of Butcher), which is why RK4 is the sweet spot taught in class.
Accuracy against cost
The real cost of a method is the number of times it evaluates f. Euler uses 1 per step, Heun 2, RK4 4; the line under the animation shows the measured number for the selected method, including the iterations of the implicit ones. A fair comparison fixes the cost, not the step: RK4 with h = 0.004 costs the same as Euler with h = 0.001, and its energy error is incomparably smaller. Higher order pays off because the error falls like \(h^p\) while the cost grows only like \(1/h\).
The comparison is subtler for the implicit methods: the Gauss method costs between about 13 and 20 evaluations per step in this page (more as h grows), three to five times RK4, and in a short chaotic run it may not be better. Its strength is a different one: the long-time behavior of the energy, discussed next.
3. Energy and geometry: symplectic methods
Energy as a test, and why RK4 drifts
Without damping, the true energy \(E\) is constant. The computed energy \(E(y_n)\) is not, and \(|E(y_n)-E(y_0)|\) is a free measure of error: no reference solution needed. It is a necessary check, not a sufficient one: a method could keep E and still have the wrong phase.
Run RK4 on the “small oscillations” start with a big step (h = 0.05) and look at the top curve of the energy graph (the 20° pendulum): the error rises steadily, a straight line on the linear scale, about 3·10⁻⁶ every ten seconds. For a harmonic oscillator one step of RK4 multiplies the amplitude by a factor slightly below 1 (the energy factor is \(1-(h\omega)^6/72+\cdots\)): a tiny numerical damping, steady and systematic, which accumulates. Euler does the opposite (factor \(1+h^2\omega^2\)) and explodes. Switch to the “race” view to see Euler off the scale, Heun rising fast, RK4 rising slowly and the symplectic methods staying level.
Hamilton's form: momenta and the function H
Define the momenta \(p_k=\partial L/\partial\dot\theta_k\). For the double pendulum, with \(q=(\theta_1,\theta_2)\) and \(c=m_2\ell_1\ell_2\),
These are Hamilton's equations, equivalent to Lagrange's. With \(\omega=M^{-1}p\) the derivatives are \(\partial H/\partial\theta_1=c\sin\delta\,\omega_1\omega_2+(m_1+m_2)g\ell_1\sin\theta_1\) and \(\partial H/\partial\theta_2=-c\sin\delta\,\omega_1\omega_2+m_2g\ell_2\sin\theta_2\). Notice that H mixes q and p (the matrix M depends on q): this is why the simple explicit leapfrog of textbooks does not apply here.
What is a symplectic method?
The flow of a Hamiltonian system preserves a geometric structure of the (q, p) space, the symplectic form \(\sum_k dq_k\wedge dp_k\). With one degree of freedom this is simply area: a blob of initial conditions is deformed by the motion but keeps its area (Liouville's theorem is a consequence). A numerical method is symplectic if its one-step map \((q_n,p_n)\mapsto(q_{n+1},p_{n+1})\) has the same property.
Why does it help the energy? Backward error analysis shows that the computed points \(z_n\) are, to very high accuracy, exact points of the flow of a slightly different Hamiltonian \(\tilde H=H+h^pH_{p+1}+\cdots\). The numerical solution conserves \(\tilde H\), so H itself can only wobble by about \(h^p\), and does not drift, for extremely long times when h is small enough. A non-symplectic method has no such nearby Hamiltonian, so its small errors accumulate in one direction. The shape of the “race” graph is this theorem made visible.
Symplecticity does not make the trajectory more accurate: the phase error still accumulates, and in chaotic motion the path eventually differs anyway. It guarantees good qualitative and long-time behavior.
The four symplectic methods in this page
Write \(H_q=\partial H/\partial q\), \(H_p=\partial H/\partial p\), \(z=(q,p)\) and \(F(z)=(H_p,-H_q)\).
Störmer–Verlet (order 2, the generalized form for non-separable H): \(p_{n+\frac12}=p_n-\tfrac h2H_q(q_n,p_{n+\frac12})\); \(q_{n+1}=q_n+\tfrac h2[H_p(q_n,p_{n+\frac12})+H_p(q_{n+1},p_{n+\frac12})]\); \(p_{n+1}=p_{n+\frac12}-\tfrac h2H_q(q_{n+1},p_{n+\frac12})\).
The implicit midpoint rule is the one-stage Gauss method. The Gauss family is symplectic for every number of stages (order 2s with s stages). All four act on (q, p); the page converts to and from the angular velocities \(\omega=M^{-1}p\) for drawing, and the damping, when present, is applied to p as a half-step factor \(e^{-\gamma h/2}\) before and after the step.
Implicit methods: solving an equation at every step
In an implicit method the new state appears on both sides, as in the midpoint rule \(z_{n+1}=z_n+hF(\tfrac{z_n+z_{n+1}}{2})\). Each step is therefore a nonlinear equation for \(z_{n+1}\). This page solves it by fixed-point iteration: guess \(z^{(0)}\) (an Euler step), then repeat \(z^{(k+1)}=z_n+hF(\tfrac{z_n+z^{(k)}}{2})\) until two iterates agree to 10⁻¹⁴. The iteration is a contraction, hence converges, when \(h\) times the Lipschitz constant of F is smaller than about 1; that is why very large steps may need many iterations (the counter under the animation shows the average). In stiff problems one uses Newton's method instead, the same one from the nonlinear-equations chapter of the course.
So implicit methods connect three chapters of numerical calculus at once: differential equations, nonlinear equations and the geometry of the flow.
Do symplectic methods really conserve energy?
No, and it is important to say it clearly. They keep the energy inside a thin band around its initial value; the numbers in the graph wobble but do not climb. The width of the band is about \(h^p\) times a constant that depends on the motion, so it is larger for chaotic starts and for large steps (try h = 0.05 on the chaotic twins: Störmer–Verlet stays near 10⁻² while RK4 climbs to about 0.2 of the energy scale).
A theorem of Ge and Marsden says that a fixed-step method cannot, in general, be symplectic and conserve energy exactly at the same time. There are methods that conserve energy exactly (“discrete gradient” methods, for example), but they give up symplecticity. And with damping (γ > 0) nothing should conserve energy: it truly decays, and the graph then shows the physical loss, which the symplectic methods reproduce faithfully.
4. Chaos
Why is a double pendulum chaotic?
Two masses on two rods already give four numbers of state, and the equations are nonlinear and coupled. When the energy is small the motion is a gentle mix of two regular oscillations (the normal modes: try the second preset). With more energy the arms can swing over the top, and tiny differences are amplified at every swing: nearby motions separate roughly like \(e^{\lambda t}\).
What does the divergence rate mean?
If a small distance grows like \(d_0e^{\lambda t}\), then \(\lambda\) is the largest Lyapunov exponent and \(\ln2/\lambda\) is the time to double the distance. For chaotic starts of the double pendulum \(\lambda\) is typically around one to a few per second, so a one-millionth difference reaches the size of the pendulum in about ten seconds. The number shown is estimated from the slope of \(\ln d\) against t over the stretch where the distance grows from three times its start to 5% of the total length.
The consequence is a prediction horizon: to predict until time T with an initial uncertainty \(\delta_0\) and tolerance \(\Delta\) we need \(T\lesssim\frac1\lambda\ln(\Delta/\delta_0)\). Each extra decimal digit of precision buys only \(\ln10/\lambda\approx1.5\) s more. That is why long-term prediction is hopeless even though the equations are deterministic.
Is the chaos real or an artifact of the computer?
It is real: the equations themselves amplify differences. The computer adds its own tiny errors (truncation and rounding), so after a while the computed path is not the true path. Yet it is still very close to a true path of a nearby experiment, which is why pictures and statistics of chaos computed with different methods and steps agree even when individual paths do not. Compare two steps or two methods on the same start: they separate exactly like the twins do, at the same rate, and the time at which they separate grows only logarithmically as the error shrinks (see the prediction horizon above). Before chaos sets in, the numerical error is what you can control, and that is what the energy graph measures.
How is the map computed, and what are the dark regions?
For each pair of starting angles, with both arms at rest, the program runs Runge–Kutta 4 with step 0.01 s until an arm passes over the pivot or 10 seconds elapse, and colors the point by that time. Nothing is looked up: thousands of small initial value problems are solved in your browser.
Starts with low energy cannot flip, and appear dark. Their motion is quasi-periodic and lives on invariant tori, which, according to the KAM theorem, survive small perturbations of an integrable system. As the energy grows the tori break down and give place to the tangle of colors: neighboring points flip at completely different times. Islands of order can still be found inside the chaotic sea.
5. Try it yourself
Guided experiments
Euler gains energy. Preset “Order: small oscillations”, method Euler, h = 0.02. Within ten seconds the energy error is of order 1 and the pendulums swing higher than where they were released. Switch to Heun and then to RK4 and compare.
Drift against wobble. Same preset, RK4, h = 0.05, energy graph “every pendulum”: the top curve (20°) rises steadily to about 10⁻⁵ at 60 s. Now select Gauss 2-stage: flat near 6·10⁻⁹. Störmer–Verlet stays flat too, about 10⁻⁴, wobbling. Which is “better”? Discuss accuracy against structure.
The race. Choose the race view with h = 0.02. Euler is off the top, Heun rises quickly, RK4 slowly, and the dashed/solid distinction separates non-symplectic from symplectic methods.
The exchange. Choose “kinetic, potential and total energy”: the total is flat while the two others trade energy. Repeat with Euler and watch the total climb.
Order and chaos. Preset “Order and chaos side by side”: compare the divergence curves. Then change h from 0.002 to 0.02 with RK4: the chaotic twins still separate at about the same rate.
Big steps. Chaotic twins, h = 0.05, race view: Euler diverges at once; RK4 reaches about 0.2 of the energy scale and Störmer–Verlet stays near 10⁻². Is the path of any of them to be trusted?
Damping. Set γ = 0.2. The energy now falls for all good methods: it is physics, not error. Compare the methods again.
Exercises for the class
Write Euler's method in ten lines for \(y'=-y\), \(y(0)=1\), and measure the order by halving h at \(t=1\). Repeat for Heun and RK4.
For \(\ddot x=-\omega^2x\) show that one Euler step multiplies \(E=\tfrac12(\dot x^2+\omega^2x^2)\) by \(1+h^2\omega^2\), and that \(|R(i\,h\omega)|^2=1-(h\omega)^6/72+\cdots\) for RK4, where \(R(z)=1+z+z^2/2+z^3/6+z^4/24\).
Check that \(\det M=m_2\ell_1^2\ell_2^2(m_1+m_2\sin^2\delta)\) and invert M by hand.
For the harmonic oscillator \(\dot q=p,\ \dot p=-q\) solve the implicit midpoint equation exactly (a 2×2 linear system) and show that \(q^2+p^2\) is preserved at every step.
From the divergence graph read two points of the straight part and estimate \(\lambda=\ln(d_2/d_1)/(t_2-t_1)\); compare with the number shown.
Download the code, change the tolerance of the fixed-point iteration (1e-14) to 1e-6, and see how the iteration count and the energy band change.
Lagrange interpolation: the polynomial through the points
Given \(n+1\) points with different abscissas there is exactly one polynomial of degree at most \(n\) through all of them. This app draws it, for a test function, a formula of your own or points you place yourself, and lets you look at everything the theory says about it. It starts from Lagrange's insight, that the polynomials \(\ell_j\) are themselves the interpolating polynomials of very simple tables, and that every other table is a combination of those; then come the error formula with the node polynomial, the Runge phenomenon, Chebyshev nodes, the Lebesgue constant, the Newton table, and why the Vandermonde matrix is a bad way to compute it. The computation uses the barycentric formula, in a few lines of plain JavaScript with no libraries, and a ready-made Python script lets you repeat everything on your own computer. A complete course follows below.
The interpolating polynomial
Function, nodes and p
Ready
f (function)p (polynomial)nodes \((x_j,y_j)\)
MAX ERROR \(\max|f-p_n|\)—
LEBESGUE CONSTANT \(\Lambda_n\)—
VANDERMONDE COND. \(\kappa_\infty(V)\)—
Max error: largest of \(|f-p|\) over 1500 sample points of \([a,b]\). Lebesgue constant: how much the nodes can amplify errors in the data. Condition number of the matrix of the monomial system.
Lagrange's idea
Every table is a combination of special tables
Fix the nodes \(x_0,\dots,x_n\). Lagrange's insight was that the whole problem comes down to \(n+1\) very simple tables. The special table \(e_j\) has the value \(1\) at the node \(x_j\) and \(0\) at all the other nodes. Its interpolating polynomial is, by definition, the Lagrange polynomial \(\ell_j\): the \(\ell_j\) are not a trick of notation, they are interpolating polynomials, of the special tables made from the very same \(x_k\) and from special values \(y_k=\delta_{jk}\). Now any table \(y\) is a sum of special tables, \(y=y_0e_0+\dots+y_ne_n\), and interpolation is linear (twice the table gives twice the polynomial, the sum of two tables gives the sum of the polynomials). Hence
No system of equations is solved: the answer for one table is assembled from the answers for the special tables. Move the controls below to see both halves of the idea.
Left: a special table and its polynomial
Right: assembling p from the pieces
Why \(\ell_j\) has the formula it has: it must vanish at the \(n\) nodes \(x_k\), \(k\ne j\), and have degree at most \(n\), so it is a constant times \(\prod_{k\ne j}(x-x_k)\), and the constant is fixed by \(\ell_j(x_j)=1\). Nothing else could interpolate the special table, by the root-counting argument of uniqueness. The group Lagrange's idea of the course below goes further (matrices, history, an exercise).
In one glance
The four formulas to remember
Lagrange form: \(\ell_j\) is the interpolating polynomial of the special table that is \(1\) at \(x_j\) and \(0\) at the other nodes; every table is a combination of these.
Each \(\ell_j\) has degree \(n\), equals \(1\) at \(x_j\) and \(0\) at the other nodes. They add up to \(1\) (the dashed line) and \(p\) is the combination of them with weights \(y_j\). The Lebesgue function is the sum of their absolute values.
Error
Where the polynomial is wrong, and why
The error formula says \(f-p=\dfrac{f^{(n+1)}(\xi)}{(n+1)!}\,\omega(x)\). Compare the three node families, look at \(\omega\) itself, or see the same polynomial computed through the Vandermonde matrix.
Convergence
What happens as n grows
Maximum error against the degree for the three node families, for the function chosen above, or the Lebesgue constants of the families on \([-1,1]\). The dashed line marks the current \(n\).
degree n
Forms
Newton table and five ways to evaluate
The divided differences (for \(n\le8\)) give the Newton form; the top row, highlighted, are its coefficients. Below, the same value \(p(x)\) is computed by five algorithms.
All five are the same polynomial, so the differences are rounding errors. They stay near \(10^{-16}\) for small \(n\), and the Vandermonde route is the first to degrade as \(n\) grows. The Newton form has its own app, next to this one.
Complete course
Polynomial interpolation, from the definition to the theorems
Seven groups of short lessons. Each one points to something you can see in the app above; the numbers quoted were measured with the very code that runs here (sampling 4000 points of the interval).
1 · The problem
What is polynomial interpolation, and why bother?
You know the values of a function \(f\) at \(n+1\) different points \(x_0\lt x_1\lt \dots\lt x_n\), the nodes, say \(y_j=f(x_j)\). The values may come from a measurement, or from a formula that is expensive to evaluate. Polynomial interpolation looks for a polynomial \(p\) of degree at most \(n\) that takes the same values at the nodes:
\[p(x_j)=y_j,\qquad j=0,1,\dots,n.\]
Why polynomials? They are the simplest functions there are: evaluating one takes only additions and multiplications, and it can be differentiated and integrated exactly. Weierstrass' theorem says that every continuous function on a closed interval is a uniform limit of polynomials. And interpolation is the engine inside many other methods: Simpson's rule and all quadrature formulas, finite differences, the Adams methods for differential equations, the secant method and inverse interpolation for roots, Richardson extrapolation and Romberg integration.
The questions of this course are the natural ones. Does such a \(p\) exist, and is it unique? How do we compute it, and which way is best? How large is \(f-p\) between the nodes? Can the nodes be chosen well? What happens when \(n\) grows?
Existence and uniqueness
Theorem. If the nodes \(x_0,\dots,x_n\) are distinct, then for any values \(y_0,\dots,y_n\) there is exactly one polynomial \(p\) of degree at most \(n\) with \(p(x_j)=y_j\) for all \(j\).
Uniqueness. If \(p\) and \(q\) both interpolate, then \(p-q\) has degree at most \(n\) and vanishes at the \(n+1\) distinct nodes. A nonzero polynomial of degree at most \(n\) has at most \(n\) roots, so \(p-q=0\).
Existence. Write \(p(x)=a_0+a_1x+\dots+a_nx^n\). The conditions form a linear system \(Va=y\) with the Vandermonde matrix
Uniqueness says that \(Va=0\) only for \(a=0\), so \(V\) is invertible and the system has a solution. (The next group builds the solution explicitly, which is a second proof.)
Consequences. If the data come from a polynomial of degree at most \(n\), the interpolant is that very polynomial: choose “polynomial x⁵ − 2x³ + x/2” in the app and raise \(n\) from 4 to 5; the error jumps from 0.11 to about \(10^{-15}\). The degree of \(p\) can be smaller than \(n\) (all \(y_j\) equal gives a constant). The nodes must be distinct; with repeated nodes one must prescribe derivatives instead (Hermite interpolation, group 6).
Interpolation as an operator
Fix the nodes and call \(\mathcal I_nf\) the interpolating polynomial of \(f\). Three facts organize everything that follows.
\(\mathcal I_n\) is linear: \(\mathcal I_n(\alpha f+\beta g)=\alpha\,\mathcal I_nf+\beta\,\mathcal I_ng\), because the system \(Va=y\) is linear in \(y\).
\(\mathcal I_n\) is a projection: \(\mathcal I_np=p\) for every polynomial \(p\) of degree at most \(n\), hence \(\mathcal I_n^2=\mathcal I_n\).
\(\mathcal I_nf\) depends on \(f\) only through the \(n+1\) numbers \(f(x_j)\), so it knows nothing about \(f\) between the nodes. The size of \(f-\mathcal I_nf\) there must come from smoothness (group 3), and the sensitivity to errors in the \(y_j\) is the norm of \(\mathcal I_n\), the Lebesgue constant (group 4).
The polynomial itself does not depend on how we write it. The five algorithms of the next group (Lagrange, Newton, Neville, monomial, barycentric) produce the same function, with very different costs and rounding behavior.
1b · Lagrange's idea: the basis polynomials interpolate special tables
The insight, in three sentences
Fix the nodes \(x_0,\dots,x_n\) (distinct). For \(j=0,\dots,n\) call \(e_j\) the table that takes the value \(1\) at \(x_j\) and \(0\) at the other \(n\) nodes: \(e_j(x_k)=\delta_{jk}\). Each such table has an interpolating polynomial, and it is unique; call it \(\ell_j\). Every table \(y\) satisfies \(y=\sum_jy_je_j\) (compare the two sides node by node), and the map table \(\mapsto\) interpolating polynomial is linear, so
\[p=\sum_{j=0}^{n}y_j\,\ell_j .\]
The point that cannot be overstated is the first one: the Lagrange polynomials are not just a formula that happens to work, they are the interpolating polynomials of the simplest possible tables, built on the same nodes. The general problem has been reduced to these \(n+1\) special problems, and the special problems have an explicit answer.
Why the special tables have the explicit answer
Look for a polynomial \(q\) of degree at most \(n\) with \(q(x_k)=0\) for \(k\ne j\) and \(q(x_j)=1\). Having the \(n\) roots \(x_k\) (\(k\ne j\)) and degree at most \(n\), it must be \(q(x)=c\prod_{k\ne j}(x-x_k)\) for some constant \(c\): dividing out those \(n\) linear factors leaves a polynomial of degree at most \(0\). The condition \(q(x_j)=1\) gives \(c=1/\prod_{k\ne j}(x_j-x_k)\), that is
So the formula is not guessed, it is derived from the special table. The same root-counting argument proves uniqueness in general (two interpolants differ by a polynomial of degree \(\le n\) with \(n+1\) roots), so it is the only polynomial that works. Try it in the card above: choose the special table \(e_j\) and compare its stems (the values \(0,\dots,1,\dots,0\)) with the curve; the status line below the plot states the largest value of \(|\ell_j|\), which can be much bigger than \(1\) between the nodes.
Any table is a combination: the decomposition in the app
With \(n\le6\) the card shows the table of the pieces \(y_je_j\): column \(j\) is zero except in row \(j\), where it holds \(y_j\), and the last column, the sum of each row, is the original table \(y\). On the right of the card the curves \(y_j\ell_j\) are the interpolating polynomials of those columns, and the slider adds them one by one: the partial sums are the interpolating polynomials of the partial tables \(y_0e_0+\dots+y_me_m\), and after the last piece the curve passes through every node. In “your data” mode, drag one point up and watch exactly one piece scale, and the sum follow.
Three identities are the same idea read with other tables. The table of all ones is \(e_0+\dots+e_n\) and is interpolated by the constant \(1\), so \(\sum_j\ell_j=1\). The table \((x_0^k,\dots,x_n^k)\) is interpolated by \(x^k\) when \(k\le n\), so \(\sum_jx_j^k\ell_j(x)=x^k\). And since \(\ell_j\) has degree \(n\) while the \(n+1\) functions \(\ell_0,\dots,\ell_n\) are independent (look at their values at the nodes), they form a basis of the polynomials of degree at most \(n\), whose coordinate vector of \(p\) is just the table \((y_0,\dots,y_n)\).
The matrix view: three bases, three matrices
Choose a basis \(b_0,\dots,b_n\) of the polynomials of degree at most \(n\) and write \(p=\sum_ka_kb_k\). The interpolation conditions become the linear system \(Ba=y\) with \(B_{ik}=b_k(x_i)\): the matrix of the basis evaluated at the nodes. The three classical forms are three choices:
Basis
Matrix \(B\)
Solving \(Ba=y\)
Lagrange \(\ell_k\)
identity: \(\ell_k(x_i)=\delta_{ik}\)
nothing to solve, \(a=y\)
Newton \(\prod_{m<k}(x-x_m)\)
lower triangular (the \(k\)-th function vanishes at \(x_0,\dots,x_{k-1}\))
forward substitution; that is the divided-difference table
monomials \(x^k\)
full Vandermonde matrix \(V\)
\(O(n^3)\) elimination, badly conditioned
The more structure the basis has with respect to the nodes, the less work is left. Lagrange's basis is the extreme case. It also tells you what \(V^{-1}\) is: since \(\ell_j(x)=\sum_ka_{kj}x^k\) is the polynomial of the table \(e_j\), the \(j\)-th column of \(V^{-1}\) holds the monomial coefficients of \(\ell_j\), and the interpolation operator, as a map from tables to coefficient vectors, is multiplication by \(V^{-1}\). Its size in the sup norm is the Lebesgue constant of group 4: \(|p(x)|\le\Lambda_n\max_j|y_j|\) because \(|p(x)|\le\sum_j|y_j||\ell_j(x)|\le(\max_j|y_j|)\sum_j|\ell_j(x)|\).
A little history: Waring, Euler, Lagrange, Gauss
The name suggests that Lagrange invented the formula, but it is older. Edward Waring published it in 1779, and Euler derived the same formula a few years later (around 1783). Lagrange's own presentation came in his lectures at the École Normale around 1795. What the name does honour is a way of seeing: with Lagrange the formula appears inside a general theory of \(\ell_j\) as the building blocks, the viewpoint of this card, and that is the form in which it spread. A generation later Gauss, in a lecture of 1812 that was published only afterwards, showed that the formulas of Newton, Euler and Waring–Lagrange are one and the same polynomial written in different ways, which is the uniqueness theorem of group 1 in disguise. Newton's own form, older than all of these, is the subject of the next app of this section.
Sources: J. Meijering, A chronology of interpolation: from ancient astronomy to modern signal and image processing, Proc. IEEE 90 (2002) 319–342, for the dates; the exact year of Lagrange's lectures is the one given there, as far as I could check.
2 · Five ways to write the same polynomial
Lagrange's form
Instead of solving a system, build \(p\) directly. For each \(j\) let
Each \(\ell_j\) is a product of \(n\) linear factors, so it has degree \(n\). At a node \(x_i\ne x_j\) the numerator contains the factor \(x_i-x_i=0\); at \(x_j\) every fraction equals 1. Hence \(\ell_j(x_i)=\delta_{ij}\) (the Kronecker delta), and \(p(x_i)=\sum_jy_j\delta_{ij}=y_i\). This is the explicit proof of existence.
The conceptual reading, that \(\ell_j\) is the interpolant of the special table \(e_j\) and that linearity assembles everything else, is the subject of group 1b.
Example. Nodes \(-1,0,1\): \(\ell_0=\tfrac12x(x-1)\), \(\ell_1=1-x^2\), \(\ell_2=\tfrac12x(x+1)\). For the values \(y=(1,0,1)\) we get \(p=\ell_0+\ell_2=x^2\).
Two identities. The constant 1 interpolates the values \((1,\dots,1)\), so by uniqueness \(\sum_j\ell_j(x)=1\) for all \(x\) (the dashed line in the basis card). More generally \(\sum_jx_j^k\ell_j(x)=x^k\) for \(k\le n\).
Assessment. The Lagrange form is wonderful for theory, since the basis \(\ell_j\) is tied to the nodes and the coefficients are the data themselves. As an algorithm it is poor: evaluating \(p(x)\) costs \(O(n^2)\), and adding a node changes every \(\ell_j\).
Newton's form and divided differences
Newton writes \(p\) in the basis \(1,\ (x-x_0),\ (x-x_0)(x-x_1),\dots\):
They fill a triangular table (see the Forms card; the coefficients are the highlighted top row). Example. Nodes \(0,1,2,3\), values \(1,2,0,5\): the columns are \(1,2,0,5\); then \(1,-2,5\); then \(-\tfrac32,\tfrac72\); then \(\tfrac53\). So
and indeed \(p(0)=1,\ p(1)=2,\ p(2)=0,\ p(3)=5\) and \(p(1.5)=0.75\).
Why it is useful. The coefficient \(c_k\) uses only the first \(k+1\) nodes, so adding a node adds one new term and leaves the old ones alone (Lagrange cannot do that). Evaluation is nested like Horner's rule, \(p(x)=c_0+(x-x_0)\bigl(c_1+(x-x_1)(c_2+\cdots)\bigr)\), with \(n\) multiplications. The table costs about \(n^2/2\) divisions. Divided differences are symmetric in their arguments, and for smooth \(f\) one has \(f[x_0,\dots,x_k]=f^{(k)}(\xi)/k!\) for some \(\xi\) (a consequence of the error formula of group 3); when all the nodes merge the Newton form becomes the Taylor polynomial.
Neville's algorithm
If you need \(p\) at a single point \(t\), you can skip the coefficients altogether. Let \(P_{i,\dots,i+k}(t)\) be the value at \(t\) of the polynomial that interpolates only the nodes \(x_i,\dots,x_{i+k}\). Starting from \(P_i=y_i\),
Proof. Both sides are polynomials in \(t\) of degree at most \(k\). At \(t=x_i\) the right side gives \(P_{i,\dots,i+k-1}(x_i)=y_i\); at \(t=x_{i+k}\) it gives \(P_{i+1,\dots,i+k}(x_{i+k})=y_{i+k}\); at an inner node both pieces equal \(y\) and the weights \((t-x_i)-(t-x_{i+k})=x_{i+k}-x_i\) add up to the denominator. By uniqueness the two sides coincide.
The cost is \(O(n^2)\) per point and only a vector of storage. The successive columns are the interpolants of increasing degree, which is exactly what Richardson extrapolation and Romberg integration use: extrapolating to \(h=0\) means evaluating an interpolating polynomial at 0.
The monomial form and the Vandermonde matrix: a trap
The most obvious algorithm solves \(Va=y\) by Gaussian elimination. It costs \(O(n^3)\) and, worse, \(V\) is badly conditioned. The condition number \(\kappa_\infty(V)\) (nodes in \([-1,1]\)) grows geometrically:
n
5
10
15
20
25
equispaced
1.9·10²
6.4·10⁴
1.8·10⁷
5.4·10⁹
1.6·10¹²
Chebyshev
1.3·10²
1.7·10⁴
1.7·10⁶
1.6·10⁸
1.4·10¹⁰
With 16 digits of arithmetic, a condition number \(\kappa\) costs you about \(\log_{10}\kappa\) digits in the coefficients. The “Barycentric vs Vandermonde” view of the Error card shows the effect: two computations of the same polynomial, whose difference should be zero, differ by \(3.6\cdot10^{-14}\) (n = 10), \(2.9\cdot10^{-11}\) (n = 20) and \(3.1\cdot10^{-8}\) (n = 30) for the Runge function on Chebyshev nodes. For \(e^x\) on equispaced nodes the difference is \(8\cdot10^{-13}\) at n = 20, \(5\cdot10^{-10}\) at n = 30, \(3\cdot10^{-7}\) at n = 40 and \(10^{-4}\) at n = 50.
(The app already helps the Vandermonde route: it rescales the interval to \([-1,1]\) and pivots. On a raw interval like \([0,100]\) it is catastrophically worse.) The moral: the monomial basis is perfect for proofs and for tiny \(n\), and a poor basis for computing.
The barycentric formulas
Put \(\omega(x)=\prod_k(x-x_k)\) and \(w_j=1/\prod_{k\ne j}(x_j-x_k)\) (which equals \(1/\omega'(x_j)\)). Then \(\ell_j(x)=\omega(x)\,\dfrac{w_j}{x-x_j}\), and Lagrange's form becomes the first barycentric form
Interpolating the constant 1 gives \(1=\omega(x)\sum_jw_j/(x-x_j)\). Dividing the first form by this identity eliminates \(\omega\) and gives the second (true) barycentric form
At \(x=x_j\) the formula is \(0/0\) and one simply returns \(y_j\). Since the weights appear in numerator and denominator, any common factor is irrelevant, which is why simple closed forms exist:
equispaced nodes: \(w_j=(-1)^j\binom nj\);
Chebyshev points (zeros of \(T_{n+1}\)): \(w_j=(-1)^j\sin\frac{(2j+1)\pi}{2n+2}\);
Chebyshev–Lobatto points (extrema of \(T_n\), endpoints included): \(w_j=(-1)^j\), halved for \(j=0\) and \(j=n\).
For other nodes the weights cost \(O(n^2)\) once. The app uses the closed forms and checks them against the general formula (agreement to \(5\cdot10^{-15}\)).
Properties. Evaluating costs \(O(n)\) per point; adding a node costs \(O(n)\); the formulas work for any nodes. Higham (2004) proved that the second form is forward stable when the Lebesgue constant of the nodes is small, as for Chebyshev nodes, which makes it the method of choice (it is what Chebfun uses). With equispaced nodes and large \(n\) the interpolation problem itself is ill-conditioned (group 4), and no algorithm can fix that.
Which form for which job?
Form
Build
Evaluate
Add a node
Use it for
Lagrange
none
\(O(n^2)\)
start over
theory, proofs, quadrature weights
Newton
\(O(n^2)\)
\(O(n)\)
\(O(n)\)
nodes arriving one by one, error analysis, multistep methods
Neville
none
\(O(n^2)\)
not applicable
one value at a point, extrapolation
Monomial
\(O(n^3)\)
\(O(n)\)
\(O(n^3)\)
tiny \(n\) only: ill-conditioned
Barycentric
\(O(n)\) closed form, else \(O(n^2)\)
\(O(n)\)
\(O(n)\)
evaluating interpolants in practice
The Forms card evaluates the same \(p(x)\) with five algorithms. For small \(n\) the five values agree to 15 or 16 digits; push \(n\) up with equispaced nodes and the Vandermonde value is the first to go wrong.
3 · How wrong is it? The error formula and Chebyshev nodes
The error formula, with proof
Theorem. Let \(f\in C^{n+1}[a,b]\), let \(x_0,\dots,x_n\in[a,b]\) be distinct and let \(p\) be the interpolating polynomial. For every \(x\in[a,b]\) there is a point \(\xi\) in the smallest interval containing \(x,x_0,\dots,x_n\) such that
Proof. If \(x\) is a node both sides are zero. Otherwise fix \(x\), and set \(c=\dfrac{f(x)-p(x)}{\omega(x)}\) and \(g(t)=f(t)-p(t)-c\,\omega(t)\). Then \(g\) vanishes at the \(n+1\) nodes (there \(f-p=0\) and \(\omega=0\)) and at \(t=x\) by the choice of \(c\): that is \(n+2\) distinct zeros. By Rolle's theorem \(g'\) has at least \(n+1\) zeros, \(g''\) at least \(n\), and so on until \(g^{(n+1)}\) has at least one zero \(\xi\). But \(p^{(n+1)}=0\) and \(\omega^{(n+1)}=(n+1)!\), so
Corollaries. \(\|f-p\|_\infty\le\dfrac{\|f^{(n+1)}\|_\infty}{(n+1)!}\,\|\omega\|_\infty\). If \(f\) is a polynomial of degree at most \(n\), the error is zero. For linear interpolation (\(n=1\), spacing \(h\)) the maximum of \(|\omega|\) is \(h^2/4\), so the error is at most \(h^2\|f''\|/8\). There is also a version that needs no derivatives: \(f(x)-p(x)=\omega(x)\,f[x_0,\dots,x_n,x]\), which is the Newton form with one more node.
Reading the formula: a function factor and a nodes factor
The error is a product of two things with different owners. The factor \(f^{(n+1)}(\xi)/(n+1)!\) belongs to the function; we cannot change it. The factor \(\omega(x)\) belongs to the nodes; we choose it. Use the “Node polynomial” view of the Error card.
On \([-1,1]\) the largest \(|\omega|\) is
n
5
10
20
equispaced
6.9·10⁻²
8.5·10⁻³
2.3·10⁻⁴
Chebyshev
3.1·10⁻²
9.8·10⁻⁴
9.5·10⁻⁷
At \(n=20\) the Chebyshev \(\omega\) is about 245 times smaller. For equispaced nodes \(\omega\) is tiny in the middle and large near the two ends, so the error piles up there; for Chebyshev nodes the \(n+2\) lobes of \(|\omega|\) all have the same height.
The derivative factor can also be dangerous. For \(1/(1+25x^2)\) the Taylor coefficients at 0 are \(\pm25^k\), so \(f^{(n+1)}(0)/(n+1)!\) is about \(5^{n+1}\) in size. The factor \(\omega\) of equispaced nodes shrinks only by about 0.7 per step (8.5·10⁻³ at \(n=10\), 2.3·10⁻⁴ at \(n=20\)), so the bound grows with \(n\), and so does the actual error (group 3, Runge).
A worked example: eˣ and how many nodes you need
Take \(f=e^x\) on \([-1,1]\), where \(\|f^{(n+1)}\|_\infty=e\). With Chebyshev nodes \(\|\omega\|=2^{-n}\), so
\[\|f-p\|_\infty\le\frac{e}{(n+1)!\,2^{n}}.\]
n
4
8
12
bound
1.4·10⁻³
2.9·10⁻⁸
1.1·10⁻¹³
measured, Chebyshev
6.4·10⁻⁴
1.2·10⁻⁸
4.4·10⁻¹⁴
measured, equispaced
1.1·10⁻³
5.8·10⁻⁸
7.0·10⁻¹³
The bound holds and is sharp within a factor of about 2.4 for the Chebyshev nodes. Use it to choose \(n\): to guarantee error below \(10^{-6}\) we need \(e/((n+1)!2^n)\lt 10^{-6}\), which fails for \(n=6\) (\(8.4\cdot10^{-6}\)) and holds for \(n=7\) (\(5.3\cdot10^{-7}\)). Since \(e^x\) is entire, the error falls faster than any geometric rate until it reaches rounding level, about \(10^{-15}\) from \(n=15\) on.
The Runge phenomenon
Choose “Runge” with equispaced nodes. At \(n=5\) the polynomial is a poor but sane approximation; at \(n=10\) it swings far above the function near the ends; at \(n=20\) it is off the chart. The maximum errors on \([-1,1]\) for \(1/(1+25x^2)\):
n
5
10
20
30
40
equispaced
0.43
1.92
59.8
2.4·10³
1.05·10⁵
At \(x=0.95\) and \(n=10\) the polynomial has the value 1.92 while \(f(0.95)=0.042\). More points made it worse. Runge found this in 1901: the interpolants converge to \(f\) only for \(|x|\lt 0.726\) and diverge for \(|x|>0.726\) (approximately).
The reason lies in the complex plane: \(1/(1+25z^2)\) has poles at \(\pm i/5\), close to the interval. Equispaced interpolation converges only for functions analytic in a lens-shaped region around \([-1,1]\) that crosses the imaginary axis at about \(\pm0.53\,i\); the poles at \(\pm0.2\,i\) are inside it. The same story for “1/(1 + x²) on [−5, 5]”: it is the same function after rescaling, and the numbers are identical, which shows that only the relative position of the singularity matters. Chebyshev nodes cure it (next lesson, and group 5).
Chebyshev nodes: the best possible ω
Since \(\omega\) is the part we control, choose the nodes to make \(\|\omega\|_\infty\) as small as possible. Theorem (Chebyshev). Among all monic polynomials of degree \(n+1\), the smallest sup-norm on \([-1,1]\) belongs to \(T_{n+1}(x)/2^n\), where \(T_{n+1}(x)=\cos((n+1)\arccos x)\); its norm is \(2^{-n}\). So the best nodes are the zeros of \(T_{n+1}\),
Proof. \(T_{n+1}\) has leading coefficient \(2^n\), so \(\omega^\ast=T_{n+1}/2^n\) is monic, and it alternates between \(+2^{-n}\) and \(-2^{-n}\) at the \(n+2\) points \(\cos(k\pi/(n+1))\). If a monic \(q\) had \(\|q\|_\infty\lt 2^{-n}\), then \(r=\omega^\ast-q\) would have degree at most \(n\) and, at those \(n+2\) points, the signs of \(\omega^\ast\): \(n+1\) sign changes, hence \(n+1\) zeros, hence \(r=0\), contradicting \(\|q\|\lt 2^{-n}\). \(\blacksquare\)
On \([a,b]\) the nodes are mapped affinely and the bound of the previous lesson becomes
The Chebyshev–Lobatto points (extrema of \(T_n\), endpoints included) give an \(\omega\) of nearly the same size and are often more convenient. Measured on the Runge function, maximum errors for Chebyshev nodes at \(n=10,20,40\) are \(0.109,\ 0.0153,\ 2.9\cdot10^{-4}\); for Lobatto \(0.132,\ 0.0177,\ 3.4\cdot10^{-4}\). One curiosity: the Chebyshev error at \(n=15\) (0.083) is larger than at \(n=12\) (0.069). For odd \(n\) there is an even number of nodes and none sits at \(x=0\), exactly where the peak of the function is.
4 · Stability: the Lebesgue constant
Lebesgue function and constant
Now perturb the data. If every \(y_j\) changes by at most \(\delta\), the interpolant changes by \(\sum_j\delta_j\ell_j(x)\), whose size is at most \(\delta\lambda_n(x)\), where
The bound is attained by taking \(\delta_j=\delta\,\mathrm{sign}\,\ell_j(x)\). So \(\Lambda_n\), the Lebesgue constant, is exactly the worst-case amplification of data errors: the condition number of interpolation. It depends only on the nodes. See the Lebesgue function in the basis card. Measured on \([-1,1]\):
n
5
10
20
40
equispaced
3.1
29.9
1.1·10⁴
4.7·10⁹
Chebyshev
1.69
2.07
2.48
2.90
Lobatto
1.99
2.42
2.87
3.31
In the app, take \(\sin(2\pi x)\), \(n=20\), and add noise \(10^{-3}\) to the values: with equispaced nodes the maximum error becomes about 1.8, with Chebyshev nodes about \(1.2\cdot10^{-3}\), the size of the noise itself.
Near-best approximation
Let \(E_n(f)=\min_{q\in\mathcal P_n}\|f-q\|_\infty\) be the error of the best polynomial of degree at most \(n\) (it exists and is unique, by Chebyshev's equioscillation theorem). Interpolation is never much worse:
Proof. Let \(q\) be any polynomial of degree at most \(n\). Because \(\mathcal I_nq=q\) and \(\mathcal I_n\) is linear, \(f-\mathcal I_nf=(f-q)-\mathcal I_n(f-q)\), so \(\|f-\mathcal I_nf\|\le(1+\|\mathcal I_n\|)\|f-q\|\). Take \(q\) to be the best polynomial. \(\blacksquare\)
So two things control interpolation: how well polynomials can approximate \(f\) at all (\(E_n(f)\), a question of smoothness, next group) and how good the nodes are (\(\Lambda_n\)). With Chebyshev nodes \(1+\Lambda_n\) is 3.9 at \(n=40\) and about 5.4 even at \(n=400\): you lose at most a small factor against the very best polynomial, and you get it with an \(O(n)\) formula and no iteration. With equispaced nodes the factor \(1+\Lambda_n\) is astronomical and the guarantee is worthless.
How fast does Λₙ grow? Why equispaced nodes fail
The asymptotics, all visible in the “Lebesgue constant against n” plot:
equispaced nodes: \(\Lambda_n\approx\dfrac{2^{n+1}}{e\,n\ln n}\) (about \(1.1\cdot10^{4}\) measured at \(n=20\), \(4.7\cdot10^{9}\) at \(n=40\)): exponential growth, roughly doubling at each step;
Chebyshev–Lobatto nodes: \(\Lambda_n\approx\dfrac2\pi\ln n+0.9625\), checked numerically to four digits at \(n=100\) (3.894) and \(n=400\) (4.777); Chebyshev zeros are the same up to a constant: logarithmic growth.
Is there a perfect set of nodes with bounded \(\Lambda_n\)? No. Erdős (1961) proved that for every choice of nodes \(\Lambda_n\ge\frac2\pi\ln(n+1)-C\) for a constant \(C\). So Chebyshev nodes are optimal up to a bounded additive constant, the best one can hope for.
Look at the Lebesgue function \(\lambda_n(x)\) for equispaced nodes at \(n=20\): it is of order 1 to 10 in the middle of the interval and shoots up to about \(10^4\) in the first and last subintervals. Nodes too far apart near the ends are what makes a polynomial through them swing wildly there; Chebyshev nodes cluster near the ends with density \(1/(\pi\sqrt{1-x^2})\) to prevent exactly this.
Rounding errors are data errors too
Even if \(f\) is evaluated perfectly, each \(y_j\) is stored with relative error up to about \(\varepsilon\approx1.1\cdot10^{-16}\). That is a perturbation of the data, and interpolation can multiply it by \(\Lambda_n\). Two measurements:
\(f=e^x\), equispaced, \(n=40\): \(\Lambda_n=4.7\cdot10^9\), expected damage \(\sim5\cdot10^{-7}\), measured error \(3.3\cdot10^{-7}\), although the theoretical interpolation error is far below \(10^{-30}\) (the function is entire).
The polynomial \(x^5-2x^3+x/2\) is reproduced exactly for every \(n\ge5\) in exact arithmetic. In floating point, equispaced nodes give errors \(1.1\cdot10^{-15}\) (n = 10), \(5.5\cdot10^{-13}\) (n = 20), \(2.4\cdot10^{-10}\) (n = 30) and \(1.6\cdot10^{-7}\) (n = 40), while Chebyshev nodes stay below \(10^{-15}\) throughout.
This is a property of the problem, not of the formula: it afflicts barycentric, Newton and Lagrange alike. For large \(n\), equispaced interpolation is therefore doubly wrong, inaccurate for functions like Runge's and noisy even for polynomials.
5 · Convergence theory
Does the interpolant converge? Faber and Bernstein
The tempting guess is that \(\mathcal I_nf\to f\) uniformly for every continuous \(f\) if the nodes are decent. It is false. Faber's theorem (1914): for every triangular array of nodes there is a continuous function \(f\) for which \(\|f-\mathcal I_nf\|_\infty\) does not tend to 0. The reason is the uniform boundedness principle: \(\Lambda_n=\|\mathcal I_n\|\to\infty\) for every array (Erdős's bound of group 4), so some \(f\) is hit by the growth.
For equispaced nodes things are worse than “some function”: Bernstein (1918) showed that for \(f(x)=|x|\) the interpolants diverge at every point of \([-1,1]\) except \(-1,0,1\). Try “|x|” with equispaced nodes: the maximum errors are 0.66, 95 and \(1.5\cdot10^7\) for \(n=10,20,40\). With Chebyshev nodes they are 0.055, 0.029 and 0.015: convergence, slowly.
The positive side: with Chebyshev nodes \(\Lambda_n=O(\ln n)\), so \(\|f-\mathcal I_nf\|\le(1+\Lambda_n)E_n(f)\) tends to 0 whenever \(E_n(f)\ln n\to0\), in particular for every Lipschitz function, and every smoother one. (Marcinkiewicz showed that for each continuous \(f\) some node array gives convergence, but the array depends on \(f\), which is of no practical use.)
How fast? Smoothness decides (Jackson and Bernstein)
By the near-best property, the speed is that of the best approximation \(E_n(f)\), up to a factor \(1+\Lambda_n\). And \(E_n(f)\) is governed by smoothness:
(Jackson) if \(f\in C^k[-1,1]\) then \(E_n(f)\le C_k\,\|f^{(k)}\|_\infty/n^k\): a power law, the faster the smoother;
if \(f\) is analytic in a neighborhood of \([-1,1]\), then \(E_n(f)\) decays geometrically, \(O(\rho^{-n})\) (next lesson);
a corner costs one power: for \(f=|x|\), \(nE_n(f)\to0.2802\) (Bernstein's constant). Chebyshev interpolation gives \(0.0546,\ 0.0285,\ 0.0146\) at \(n=10,20,40\), about twice \(0.28/n\), as the factor \(1+\Lambda_n\approx3\) allows.
The conclusion is practical. The accuracy you can reach with a polynomial of degree \(n\) is a property of the function, and Chebyshev interpolation gets within a small factor of it. Nothing smarter can beat \(E_n\); it can only avoid the factor \(1+\Lambda_n\).
Analytic functions: the Bernstein ellipse
Let \(E_\rho\) be the ellipse with foci \(\pm1\) and semi-axes \((\rho+\rho^{-1})/2\) and \((\rho-\rho^{-1})/2\). Theorem (Bernstein; see Trefethen). If \(f\) is analytic inside \(E_\rho\) with \(|f|\le M\) there, then the interpolant at \(n+1\) Chebyshev points satisfies
Geometric convergence, at a rate fixed by the nearest singularity. Check with Runge's function. Its poles at \(\pm i/5\) lie on \(E_\rho\) when \((\rho-\rho^{-1})/2=1/5\), that is \(\rho=\frac{1}{5}+\sqrt{1+\frac1{25}}=1.2198\). The prediction: the error shrinks by \(\rho^{-10}=0.137\) every 10 steps of \(n\) and by \(\rho^{-20}=0.0188\) every 20. Measured with Chebyshev nodes: from \(n=10\) to 20 the factor is 0.140, from \(n=20\) to 40 it is 0.0189. The theory predicts the slope.
The rate shows up as a straight line on the logarithmic convergence plot. For \(\sin(2\pi x)\) and \(e^x\), entire functions with no singularity at all, the line bends downward faster than any straight line (superexponential convergence) until it meets the rounding floor near \(10^{-15}\). The same theory explains equispaced failure: those nodes behave as if singularities near the imaginary axis (the poles of Runge's function) were far closer than they are.
A catalogue: smoothness against rate
Maximum error with Chebyshev nodes, measured in the app (choose the function and set n):
function
smoothness
n = 10
n = 20
n = 40
sign(x)
jump
of order 1: no continuous \(p\) can be closer than half the jump
|x|
corner, Lipschitz
5.5·10⁻²
2.9·10⁻²
1.5·10⁻²
√(x+1)
singular at the end
6.4·10⁻²
3.4·10⁻²
1.7·10⁻²
smooth bump
\(C^\infty\), not analytic
4.9·10⁻³
1.6·10⁻⁴
4.1·10⁻⁶
Runge
analytic, poles at \(\pm0.2i\)
1.1·10⁻¹
1.5·10⁻²
2.9·10⁻⁴
sin(2πx)
entire
7.2·10⁻³
7.1·10⁻¹⁰
1.3·10⁻¹⁵
Reading the rows: for the jump, nothing helps; for the corner and the endpoint singularity the error halves when \(n\) doubles (\(O(1/n)\)); for the bump it falls faster than any power of \(1/n\) but slower than any geometric rate; for Runge it falls geometrically at rate \(1.22^{-n}\); for entire functions it falls superexponentially until rounding. The “Maximum error against n” plot shows exactly these shapes.
Why Chebyshev points work: the circle
Put \(x=\cos\theta\). The Chebyshev points are \(x_j=\cos\theta_j\) with equally spaced angles \(\theta_j=(2j+1)\pi/(2n+2)\): they are the shadows, on the diameter, of equally spaced points on the unit circle. A polynomial of degree \(n\) in \(x\) is the same thing as an even trigonometric polynomial \(\sum_{k\le n}a_k\cos k\theta\), because \(T_k(\cos\theta)=\cos k\theta\).
So interpolating in Chebyshev points is interpolating a trigonometric polynomial at equispaced angles, and that is the discrete Fourier transform: orthogonal, perfectly conditioned, and the source of the \(\frac2\pi\ln n\) growth of \(\Lambda_n\). Points equispaced in \(x\) are not equispaced in \(\theta\), and that is the whole difference between Chebyshev and Runge.
Practical bonus: the Chebyshev coefficients \(a_k\) of the interpolant can be computed in \(O(n\log n)\) by the fast Fourier transform, which is the basis of spectral methods and of the Chebfun system.
6 · Extensions and applications
Integration and differentiation from interpolation
Replace \(f\) by \(p\) and integrate: \(\displaystyle\int_a^bf\approx\int_a^bp=\sum_jW_jy_j\) with \(W_j=\int_a^b\ell_j\). This is how every interpolatory quadrature rule is born. For equispaced nodes on \([0,n]\) (spacing 1):
For \(n=8\) the weights are \(0.279,\ 1.662,\ -0.262,\ 2.962,\ -1.281,\ 2.962,\ -0.262,\ 1.662,\ 0.279\): some are negative, so \(\sum|W_j|>b-a\) and rounding errors in \(y_j\) are amplified, the same disease as the Lebesgue constant. That is why high-order Newton–Cotes rules are abandoned. Remedies: composite rules (low degree on many small panels), Clenshaw–Curtis (integrate the Chebyshev interpolant), and Gauss quadrature, where the nodes are chosen to make the rule exact up to degree \(2n+1\).
Differentiating the error formula gives rules for \(f'\): from \(n=2\) one gets the central difference \((f(x+h)-f(x-h))/(2h)\) with error \(-h^2f'''(\xi)/6\). Differentiation is intrinsically ill-posed: the norm of the Chebyshev differentiation matrix grows like \(n^2\), so noise is amplified much more than in interpolation. Adams–Bashforth multistep methods for ODEs integrate the interpolant of \(f\) through the last few steps.
Hermite interpolation and Taylor's theorem
Prescribe values and first derivatives at \(n+1\) nodes: \(2n+2\) conditions, and exactly one polynomial \(H\) of degree at most \(2n+1\) satisfies them. The proof of the error formula goes through verbatim (each node is a double zero of \(f-H\)) and gives
Hermite interpolation is computed with the Newton table by repeating nodes and putting \(f[x_j,x_j]=f'(x_j)\) in the slots where a division by zero would occur. For \(n=1\) it is the cubic Hermite interpolant, the building block of splines in computer graphics and of monotone “PCHIP” interpolation.
The extreme case is a single node repeated \(n+1\) times: the Newton form becomes the Taylor polynomial \(\sum_kf^{(k)}(x_0)(x-x_0)^k/k!\), and the error formula becomes Lagrange's remainder \(f^{(n+1)}(\xi)(x-x_0)^{n+1}/(n+1)!\). So Taylor's theorem is the special case of this lesson in which all the nodes collide, and interpolation is its natural generalization to separated nodes, which is why it is far better behaved on a whole interval.
When degree is the problem: piecewise polynomials and splines
A single polynomial of high degree is a global object: a bump at one end can shake the whole curve. The alternative is low degree on many small pieces.
Piecewise linear interpolation is continuous with error at most \(h^2\|f''\|_\infty/8\), from the error formula with \(n=1\). Stable (\(\Lambda=1\)), but only first-order smooth.
Cubic splines are piecewise cubics with two continuous derivatives. For smooth \(f\) the error is \(O(h^4)\), and the Lebesgue constant stays bounded as the mesh is refined, unlike global polynomial interpolation.
A working rule: if \(f\) is analytic (or very smooth) on the whole interval and you may choose the nodes, global interpolation at Chebyshev points converges geometrically and wins. If the data are given at arbitrary points, or \(f\) has corners and jumps, or you only need a few digits, low-degree pieces are the safe choice. In the free-data mode, put twelve points on a gentle zigzag: the global polynomial oscillates between and beyond them, which a spline would not do.
Noisy data: interpolate or fit?
If the \(y_j\) are measurements with errors of size \(\sigma\), interpolation reproduces the errors faithfully, and can amplify them up to \(\Lambda_n\sigma\) in between. With equispaced data and \(n\) around 20 this is a catastrophe (use the noise control with “equispaced”, then switch the node family to see the contrast).
What one does in practice is least-squares fitting: choose a polynomial of low degree \(m\ll n\) minimizing \(\sum_j(p(x_j)-y_j)^2\), or a smoothing spline. The fit does not pass through the points, and that is a feature: it averages the noise instead of chasing it. Interpolation is the right tool when the data are exact (values of a known function, or nodes you choose) and the right question is “what is between the nodes?”.
7 · Guided experiments and exercises
Experiment 1: the Runge phenomenon
Choose “Runge: 1/(1 + 25x²)”, equispaced nodes, and press Grow n. The maximum error first stays near 0.4 (0.44 at \(n=4\), 0.43 at \(n=5\)) and then explodes: 0.43 at \(n=5\), 1.92 at \(n=10\), 59.8 at \(n=20\), \(2.4\cdot10^3\) at \(n=30\), \(1.05\cdot10^5\) at \(n=40\).
Tick “Fit the window to p” to see the whole polynomial, and open the Lebesgue-function view of the basis card.
Switch to Chebyshev: \(0.109\), \(0.0153\), \(2.9\cdot10^{-4}\) at \(n=10,20,40\). In the convergence card the Chebyshev curve is a straight line of slope \(\log_{10}1.22^{-1}\) per unit of \(n\).
Choose “1/(1 + x²) on [−5, 5]” and compare: same function after rescaling, identical numbers.
Question: why does the Chebyshev error at \(n=15\) exceed the one at \(n=12\)? (Lesson on Chebyshev nodes.)
Experiment 2: smoothness against rate
With Chebyshev nodes, run through “|x|”, “√(x + 1)”, “smooth bump”, “Runge” and “sin(2πx)” at \(n=10,20,40\) and compare with the table of the catalogue lesson. In the convergence card, notice the shapes: a flat-ish line for the corner (\(O(1/n)\) is a slow straight line only on a log–log plot, here it looks like a gentle bend), a straight line for Runge, a curve falling away for \(\sin\). Then switch to equispaced nodes for \(|x|\): the curve goes up after \(n\approx5\), which is Bernstein's divergence.
Question: the table suggests doubling \(n\) halves the error for \(|x|\). Which part of Jackson's theorem is that?
Experiment 3: uniqueness and the error formula, exactly
Choose “polynomial x⁵ − 2x³ + x/2”. For \(n\ge5\) the error is rounding only (about \(10^{-15}\)): uniqueness. For \(n=4\) it is not zero, and it can be predicted exactly. The function is monic of degree 5, so \(f^{(5)}/5!=1\) and the error formula says \(f-p=\omega(x)\) with no \(\xi\) left over. Therefore the maximum error equals \(\|\omega\|_\infty\): for equispaced nodes 0.113, and for Chebyshev nodes exactly \(2^{-4}=0.0625\), which is what the app reports. Check both against the “Node polynomial” view of the Error card.
Experiment 4: noise and the Lebesgue constant
Choose \(\sin(2\pi x)\), \(n=20\), noise \(10^{-3}\). Equispaced nodes: maximum error about 1.8, a thousand times the noise, close to the \(\Lambda_{20}\approx1.1\cdot10^4\) allowance. Chebyshev nodes: about \(1.2\cdot10^{-3}\), the size of the noise. Look at the polynomial with “Fit the window to p”: with equispaced nodes the damage sits in the first and last subintervals, exactly where the Lebesgue function is large. Try noise \(10^{-6}\) and \(10^{-2}\) and verify that the damage scales linearly with the noise.
Experiment 5: your own points
Choose “Your data”. Drag a point and watch the whole polynomial move: it is a global object. Add points to the right of the last one: the polynomial far to the left changes too. Put two points very close together with different heights: the polynomial needs a huge slope between them and \(\Lambda_n\) leaps. Press Add a random point repeatedly and watch the Vandermonde condition number in the metrics. In the Forms card (for \(n\le8\)) follow the divided-difference table as you drag: the top row, the Newton coefficients, changes only from the dragged point on to the right.
Experiment 7: Lagrange's idea, by hand and on the screen
Choose “Your data” and place four points. In the card Lagrange's idea pick \(e_0\) and look at the left plot: the polynomial is \(1\) at the first point and \(0\) at the other three. Do the same with \(e_1,e_2,e_3\).
Drag the slider on the right from \(0\) to the last piece. Which piece is largest? Drag the corresponding point upward and watch that piece, and only that piece, grow.
Choose “Your function f(x)”, type sin(3x) on \([-1,1]\) with \(n=3\) equispaced nodes, and compare the sum of the four pieces with \(f\).
With the Python script below, change NODES to "cheb" and compare the pictures lagrange_pieces.png for the two families: the pieces \(y_j\ell_j\) are much smaller with Chebyshev nodes, which is the Lebesgue constant at work.
Exercises, with answers
Find the polynomial through \((-1,1),(0,0),(1,1)\) by Lagrange's formula and by uniqueness. Answer: \(\ell_0+\ell_2=x^2\); and \(x^2\) has degree 2 and fits, so by uniqueness it is the one.
Show that \(\sum_j\ell_j(x)\equiv1\) and \(\sum_jx_j^k\ell_j(x)=x^k\) for \(k\le n\). Answer: \(1\) and \(x^k\) are polynomials of degree at most \(n\), so each equals its own interpolant, whose Lagrange form is the stated sum.
Show that \(w_j=1/\omega'(x_j)\). Answer: write \(\omega=(x-x_j)\prod_{k\ne j}(x-x_k)\); then \(\omega'(x_j)=\prod_{k\ne j}(x_j-x_k)\).
Bound the error of the linear interpolation of \(\ln x\) at the nodes 1 and 2, at \(x=1.5\). Answer: \(|f''|=1/x^2\le1\), \(|\omega(1.5)|=0.25\), so the error is at most \(\tfrac12\cdot0.25=0.125\). The actual error is \(\ln1.5-\tfrac12\ln2=0.0589\).
How many Chebyshev nodes guarantee error below \(10^{-6}\) for \(e^x\) on \([-1,1]\)? Answer: \(e/((n+1)!2^n)\lt 10^{-6}\) first holds at \(n=7\), that is 8 nodes.
Derive Simpson's rule from \(\int\ell_j\). Answer: on nodes \(0,1,2\): \(\ell_0=\tfrac12(x-1)(x-2)\), \(\ell_1=x(2-x)\), \(\ell_2=\tfrac12x(x-1)\), with integrals over \([0,2]\) equal to \(\tfrac13,\tfrac43,\tfrac13\). So \(\int_0^2f\approx\tfrac13(f_0+4f_1+f_2)\).
Prove that the Newton coefficient \(c_k\) depends only on \(x_0,\dots,x_k\) and \(y_0,\dots,y_k\). Answer: \(c_k\) is the leading coefficient of the polynomial of degree at most \(k\) interpolating the first \(k+1\) data, since the Newton terms \(c_j\prod_{i\lt j}(x-x_i)\) with \(j\lt k\) have degree below \(k\).
Try it yourself in Python
A self-contained script that does with your own formula or table what the app does: it builds the Lagrange polynomials, checks the identity and the interpolation property, compares with the barycentric formula, computes the Lebesgue constant and the Vandermonde condition number, and saves two figures. It needs numpy and matplotlib (pip install numpy matplotlib). Change the settings at the top (SOURCE, the formula f, the interval, N, NODES, DATA) and run it again.
# Lagrange interpolation: the polynomial through points, built from special tables.
#
# Lagrange's insight: for the SAME nodes x_0..x_n, the polynomial l_j of degree <= n through the special
# table (x_k, delta_jk) -- value 1 at x_j and 0 at every other node -- is l_j(x) = prod_{k != j} (x - x_k)/(x_j - x_k).
# Any table y is the sum of y_j times these special tables, and interpolation is linear, so
# p(x) = y_0 l_0(x) + y_1 l_1(x) + ... + y_n l_n(x).
#
# Needs: pip install numpy matplotlib Run: python lagrange_interp.py
# Edit the settings just below and run again.
import numpy as np
import matplotlib.pyplot as plt
# ------------------------------------------------------------------ settings: play with these
SOURCE = "function" # "function": y_k = f(x_k) or "data": your own points below
def f(x): # any numpy expression of x, for example np.exp(x), np.abs(x), np.sin(2*np.pi*x)
return 1 / (1 + 25 * x**2) # Runge's function
A, B = -1.0, 1.0 # the interval
N = 8 # degree n: there are n + 1 nodes
NODES = "chebyshev" # "equispaced", "chebyshev" (zeros of T_{n+1}) or "lobatto" (extrema of T_n)
DATA = [(0, 1), (2, 3), (4, 2.5), (6, 4.5), (8, 3), (10, 6)] # (x, y) pairs, used when SOURCE == "data"
SAVE = True # also write the pictures as lagrange_*.png
# ------------------------------------------------------------------
def make_nodes(kind, n, a, b):
m, h, i = (a + b) / 2, (b - a) / 2, np.arange(n + 1)
if kind == "chebyshev":
return m - h * np.cos((2 * i + 1) * np.pi / (2 * n + 2))
if kind == "lobatto" and n > 0:
return m - h * np.cos(i * np.pi / n)
return np.linspace(a, b, n + 1)
def lagrange_basis(xs, j, t):
"""l_j(t) straight from the definition: the product of the n factors (t - x_k)/(x_j - x_k), k != j."""
out = np.ones_like(t, dtype=float)
for k, xk in enumerate(xs):
if k != j:
out *= (t - xk) / (xs[j] - xk)
return out
def interpolate(xs, ys, t):
"""p(t) = sum_j y_j l_j(t): the data table written as a sum of special tables."""
return sum(yj * lagrange_basis(xs, j, t) for j, yj in enumerate(ys))
def barycentric(xs, ys, t):
"""Same polynomial, second barycentric formula: O(n) per point and numerically stable."""
w = np.array([1 / np.prod(xj - np.delete(xs, j)) for j, xj in enumerate(xs)])
out = np.empty_like(t, dtype=float)
for i, ti in enumerate(t):
d = ti - xs
if np.any(d == 0):
out[i] = ys[np.argmin(np.abs(d))]
else:
q = w / d
out[i] = np.dot(q, ys) / q.sum()
return out
if SOURCE == "data":
pts = np.array(sorted(DATA), dtype=float)
xs, ys = pts[:, 0], pts[:, 1]
A, B = xs[0], xs[-1]
else:
xs = make_nodes(NODES, N, A, B)
ys = f(xs)
n = len(xs) - 1
t = np.linspace(A, B, 1200)
# 1. the special tables: l_j(x_k) must be the identity matrix
L = np.array([[lagrange_basis(xs, j, np.array([xk]))[0] for k, xk in enumerate(xs)] for j in range(n + 1)])
print("max |l_j(x_k) - delta_jk| =", np.abs(L - np.eye(n + 1)).max())
print("max |p(x_k) - y_k| =", np.abs(interpolate(xs, ys, xs) - ys).max())
print("max |Lagrange - barycentric| on the grid =", np.abs(interpolate(xs, ys, t) - barycentric(xs, ys, t)).max())
# 2. the Lebesgue function sum_j |l_j(x)| and its maximum: the condition number of interpolation
leb = sum(np.abs(lagrange_basis(xs, j, t)) for j in range(n + 1))
print("Lebesgue constant Lambda_n =", leb.max(), " (|p(x)| <= Lambda_n * max|y_j|)")
print("Vandermonde condition number =", np.linalg.cond(np.vander(xs, increasing=True)))
# 3. the pictures
p = interpolate(xs, ys, t)
fig, ax = plt.subplots(1, 2, figsize=(13, 4.8))
for j in range(n + 1):
ax[0].plot(t, ys[j] * lagrange_basis(xs, j, t), lw=1, alpha=.8, label=f"$y_{j}\\,\\ell_{j}$" if n <= 6 else None)
ax[0].plot(t, p, "k", lw=2.2, label="$p=\\sum y_j\\ell_j$")
if SOURCE == "function":
ax[0].plot(t, f(t), "--", color="gray", lw=1.2, label="$f$")
ax[0].plot(xs, ys, "o", color="crimson", zorder=5)
lo, hi = ys.min(), ys.max()
ax[0].set_ylim(lo - 1.5 * (hi - lo), hi + 1.5 * (hi - lo))
ax[0].set_title("your table = sum of $y_j$ times the special tables"); ax[0].legend(fontsize=8, ncol=2); ax[0].grid(alpha=.3)
for j in range(n + 1):
ax[1].plot(t, lagrange_basis(xs, j, t), lw=1.3)
ax[1].plot(xs, (np.arange(n + 1) == j).astype(float), "o", ms=3, color="k")
ax[1].set_ylim(-1.2, 1.6)
ax[1].set_title("$\\ell_j$ interpolates the table $(x_k,\\delta_{jk})$: a 1 at $x_j$, 0 elsewhere"); ax[1].grid(alpha=.3)
plt.tight_layout()
if SAVE:
plt.savefig("lagrange_pieces.png", dpi=140)
if SOURCE == "function":
fig2, ax2 = plt.subplots(figsize=(7, 4))
ax2.semilogy(t, np.abs(f(t) - p) + 1e-18, label="$|f-p_n|$")
ax2.semilogy(t, leb * 1e-3, "--", label="Lebesgue function / 1000")
ax2.set_title(f"error, n = {n}, {NODES} nodes"); ax2.legend(); ax2.grid(alpha=.3)
plt.tight_layout()
if SAVE:
plt.savefig("lagrange_error.png", dpi=140)
plt.show()
References: J.-P. Berrut and L. N. Trefethen, Barycentric Lagrange interpolation, SIAM Review 46 (2004) 501–517; N. J. Higham, The numerical stability of barycentric Lagrange interpolation, IMA J. Numer. Anal. 24 (2004) 547–556; L. N. Trefethen, Approximation Theory and Approximation Practice, SIAM; P. J. Davis, Interpolation and Approximation, Dover; any numerical analysis text (Burden–Faires, Süli–Mayers). Historical: C. Runge (1901), G. Faber (1914), P. Erdős (1961). J. Meijering, A chronology of interpolation, Proc. IEEE 90 (2002) 319–342.
Newton's form: building the polynomial one node at a time
Newton's way of interpolating: instead of finding all the coefficients at once, build the polynomial node by node. Each new node adds exactly one term, a number \(c_k\) (a divided difference) times the product \((x-x_0)\cdots(x-x_{k-1})\), and the terms already there are never touched. This app lets you add the nodes one at a time and see what each term does, reorder the nodes (the final polynomial does not change, the arithmetic does), watch the next term estimate the error, compare with Gregory's formula for equal spacing, and see how nodes that merge turn divided differences into derivatives. Use a test function, a formula of your own, or points that you click, which are used in the order in which you click them. A course with historical remarks follows below, and a ready-made Python script lets you repeat everything on your own computer.
The Newton polynomial
Function, nodes and the polynomial so far
Ready
f (function)p (polynomial)last term \(c_kN_k\)nodes usednodes not used yet
MAX ERROR \(\max|f-p_k|\)—
LAST COEFFICIENT \(c_k\)—
SIZE OF LAST TERM \(\max|c_kN_k|\)—
Max error: largest of \(|f-p_k|\) over 800 points of \([a,b]\), for the polynomial of the nodes used so far (only for a function). Last coefficient: the divided difference of the node just added. Size of the last term: the largest value of \(|c_kN_k|\) on \([a,b]\), which is what that node contributed.
In one glance
The four formulas to remember
Newton form: a sum of terms, each a divided difference times a product of factors.
The table of divided differences for the nodes in the chosen order (shown for \(n\le8\)). The top row holds the coefficients \(c_0,c_1,\dots\): those in use for the current \(k\) are highlighted, the others are grey. Below, the Newton form of \(p_k\) and its nested (Horner) form.
Each entry is (the right neighbour minus the left neighbour) divided by the distance between the extreme nodes of its diagonal, so the whole table costs about \(n^2/2\) subtractions and divisions.
One node at a time
The new term corrects the old residual
When the node \(x_k\) is added, the old polynomial \(p_{k-1}\) already fits the first \(k\) nodes and misses the new value by the residual \(r_k=y_k-p_{k-1}(x_k)\). The new term \(c_kN_k\) vanishes at all the old nodes and is scaled to cancel exactly this residual: it is the interpolating polynomial of the table of residuals. Dashed: the true residual function \(f-p_{k-1}\) (for a function). Dots: the residual at each node; grey ones are already zero, the red one is the new node, hollow ones have not been reached yet.
Error estimate
The next term is the error
The error of \(p_k\) is \(f[x_0,\dots,x_k,x]\,N_{k+1}(x)\), while the next Newton term is \(f[x_0,\dots,x_{k+1}]\,N_{k+1}(x)\): the same product of factors with two nearby divided differences. So the first term not yet used estimates the error of the terms already used, which the other forms of the polynomial do not offer. Black: \(|f-p_k|\). Red: the size of the next term.
Equal spacing
Differences and the Newton–Gregory formula
When the nodes are equally spaced, the division by \(x_{i+k}-x_i\) becomes a division by \(kh\) and the divided differences reduce to the ordinary forward differences \(\Delta^ky_i\). The table below appears when the nodes are equispaced (choose equispaced nodes, or click equally spaced points; the default points of “your data” are) and shows that \(c_k=\Delta^ky_0/(k!\,h^k)\). This is the formula found by Gregory in 1670 and used for centuries in tables of interpolation.
Order of the nodes
The same polynomial, different arithmetic
The polynomial \(p_n\) through all the nodes does not depend on their order, but the numbers \(c_k\) and the rounding errors do. Left: the size \(|c_k|\) of each coefficient for four orders (the current one in bold). Right: the difference, on \([a,b]\), between the Newton form evaluated in that order and the barycentric formula; it is rounding error and nothing else. Raise \(n\) (try 35 with Chebyshev nodes) and compare the orders.
Size of the coefficients
Rounding error of the evaluation
Clustering
When nodes merge, divided differences become derivatives
Take five nodes \(x_c,\ x_c+s,\dots,x_c+4s\) and let the spread \(s\) shrink. The coefficients \(c_0,\dots,c_4\) settle at the Taylor coefficients \(f^{(k)}(x_c)/k!\) (the Newton form turns into Taylor's polynomial) until \(s\) is so small that subtracting almost equal numbers and dividing by \(s^k\) destroys all the digits: the higher the \(k\), the sooner. For \(\exp\), \(\sin(2\pi x)\) and the polynomial the exact Taylor coefficients are shown in the last row.
Complete course
Newton interpolation: divided differences, errors and history
Six groups of short lessons; the history is in group 5. Each one points to something you can see in the app above.
1 · The idea: one node at a time
Why not just add a point?
Lagrange's form needs all the nodes at once: with one more node every \(\ell_j\) changes. The monomial form needs a new linear system. Newton arranged the work so that nothing already done is wasted. Suppose \(p_{k-1}\) interpolates the first \(k\) nodes \(x_0,\dots,x_{k-1}\), and look for \(p_k=p_{k-1}+q\) with \(q\) of degree at most \(k\). Both \(p_k\) and \(p_{k-1}\) fit the first \(k\) nodes, so \(q\) vanishes there, and a polynomial of degree at most \(k\) with these \(k\) roots is
The denominator \(N_k(x_k)=\prod_{j\lt k}(x_k-x_j)\) is not zero because the nodes are distinct. In the app, the red curve is \(p_k\), the dashed blue curve is the last term \(c_kN_k\), and Add the nodes one by one carries out the construction. Each step costs \(O(k)\) operations, so the whole polynomial costs \(O(n^2)\).
Divided differences and the table
The number \(c_k\) has a name: the divided difference \(f[x_0,\dots,x_k]\). Since \(p_k=p_{k-1}+c_kN_k\), where \(p_{k-1}\) has degree at most \(k-1\) and \(N_k\) is monic of degree \(k\), the number \(c_k\) is the leading coefficient (the coefficient of \(x^k\)) of the polynomial of degree at most \(k\) through the first \(k+1\) nodes. Take that as the definition. It satisfies the recursion
Proof. Let \(P\) interpolate at \(x_i,\dots,x_{i+k-1}\) and \(Q\) at \(x_{i+1},\dots,x_{i+k}\), and put \(R(x)=\dfrac{(x-x_i)Q(x)-(x-x_{i+k})P(x)}{x_{i+k}-x_i}\). It has degree at most \(k\). At \(x_i\) it equals \(P(x_i)\), at \(x_{i+k}\) it equals \(Q(x_{i+k})\), and at an inner node \(x_m\), where \(P=Q=y_m\), it equals \(y_m\cdot\dfrac{(x_m-x_i)-(x_m-x_{i+k})}{x_{i+k}-x_i}=y_m\). So \(R\) is the interpolating polynomial of all \(k+1\) nodes, and comparing the coefficients of \(x^k\) gives the recursion. (This is also the idea of Neville's algorithm.) \(\square\)
The recursion fills a triangular table, one column per order, and the coefficients \(c_0,\dots,c_n\) are its top row; that is the table in the first card under the plot, where the coefficients in use are highlighted.
A worked example, and adding a node
Nodes \(0,1,2,3\) with values \(1,2,0,5\). The columns of the table are \(1,2,0,5\); then \(1,-2,5\); then \(-\tfrac32,\tfrac72\); then \(\tfrac53\). The top row is \(c=(1,1,-\tfrac32,\tfrac53)\), so
Now add the node \(x_4=4\) with value \(y_4=3\). The old polynomial gives \(p_3(4)=1+4-18+40=27\), so the residual is \(3-27=-24\), while \(N_4(4)=4\cdot3\cdot2\cdot1=24\). Hence \(c_4=-24/24=-1\) and
\[p_4(x)=p_3(x)-x(x-1)(x-2)(x-3).\]
Nothing before was recomputed. (Check with the full table of five values: the new column is \(-2\), then \(-\tfrac72\), then \(-\tfrac73\), then \(-1\).) In “your data” mode click a new point to see exactly this: only the last term changes.
What \(c_k\) really is: the leading coefficient
Writing the interpolating polynomial of the nodes \(x_0,\dots,x_k\) in Lagrange's form, \(\sum_jy_j\ell_j\), and reading off the coefficient of \(x^k\) (the leading coefficient of \(\ell_j\) is \(1/\prod_{m\ne j}(x_j-x_m)\)), gives the explicit formula
Three consequences. (i) It is symmetric in the nodes: the divided difference depends on the set \(\{x_0,\dots,x_k\}\), not on the order (group 2 uses this). (ii) If the data come from a polynomial of degree \(d\), then \(c_k=0\) for \(k>d\) and \(c_d\) is its leading coefficient; for example \(f[x_0,x_1,x_2]=1\) for \(f=x^2\) and any three nodes. (iii) It links the two apps of this section: \(c_kN_k\) is the polynomial that vanishes at \(x_0,\dots,x_{k-1}\) and takes the value \(r_k\) at \(x_k\), that is, \(r_k\) times the Lagrange polynomial \(\ell_k\) of the nodes \(x_0,\dots,x_k\), which interpolates the special table \((0,\dots,0,1)\). The card The new term corrects the old residual checks this numerically.
2 · Properties: order, evaluation and error
Order of the nodes: same polynomial, different arithmetic
By symmetry, \(c_k\) depends only on which nodes are among the first \(k+1\), so reordering the nodes changes the intermediate polynomials \(p_k\) and the individual terms, but never the final polynomial \(p_n\) (it is the unique interpolant). In exact arithmetic the order is irrelevant. In floating point it is not: the terms \(c_kN_k(x)\) can be large and cancel each other, and the rounding errors grow with their size. The card The same polynomial, different arithmetic shows the size of the coefficients and the actual rounding error for four orders.
A good order spreads the nodes so that \(|N_k|\) does not run away. The Leja order takes \(x_0\) of largest modulus and then, at each step, the node that maximizes \(\prod_{j\lt k}|x-x_j|\) over the nodes still unused. It is cheap to compute and keeps the terms balanced; see the references (Reichel; Higham, chapter 5). The effect can be dramatic. With 36 Chebyshev nodes on \([-1,1]\) the left-to-right order (and its reverse) loses about eight digits in the Newton form, while the Leja order keeps nearly all of them (measured in this app for the Runge function; see the experiment). With many equispaced nodes all the orders suffer alike, because there the interpolation problem itself is ill conditioned (the Lebesgue constant of the Lagrange app), and no ordering can repair that.
Nested evaluation, its derivative and the cost
The Newton form evaluates like Horner's rule: \(s_n=c_n\), then \(s_j=c_j+(t-x_j)\,s_{j+1}\) for \(j=n-1,\dots,0\), and \(p(t)=s_0\). That is \(n\) multiplications and \(2n\) additions, with no powers and no products to build. Differentiating the recursion, \(s_j'=s_{j+1}+(t-x_j)\,s_{j+1}'\) with \(s_n'=0\), gives \(p'(t)=s_0'\) at the same cost. The nested form is the second line of the card Divided differences and the Newton form.
Proof. Use \(x\) as one more node, the last. By the incremental property of the Newton form, the interpolating polynomial of \(x_0,\dots,x_n,x\) is \(p_n(t)+f[x_0,\dots,x_n,x]\,N_{n+1}(t)\) with \(N_{n+1}=\omega\). It interpolates \(f\) at the node \(x\), so putting \(t=x\) gives \(f(x)=p_n(x)+f[x_0,\dots,x_n,x]\,\omega(x)\). \(\square\)
No derivative appears, and the formula is an identity valid for any function. Together with the mean value theorem of group 2 (\(f[x_0,\dots,x_n,x]=f^{(n+1)}(\xi)/(n+1)!\)) it gives the usual error formula of the Lagrange app. The factor \(\omega\), which depends only on the nodes, is the one the card on error of that app plots.
The next term as an error estimate
By the previous lesson the error of \(p_k\) is \(f[x_0,\dots,x_k,x]\,N_{k+1}(x)\), while the next Newton term is \(f[x_0,\dots,x_k,x_{k+1}]\,N_{k+1}(x)\). The product of factors is the same; the two divided differences differ only in which extra point they use, \(x\) or \(x_{k+1}\), and for a smooth function both are close to \(f^{(k+1)}/(k+1)!\). So \(|f-p_k|\approx|c_{k+1}N_{k+1}|\) near the data: the first term you did not use measures the error of the ones you did. That gives an adaptive rule: add nodes until the last term is below a tolerance. The card The next term is the error plots both curves; for smooth functions they stay close.
Caution. The estimate sees only the nodes. If \(f\) has a narrow feature between nodes, the divided differences may stay small while the true error is large, and the estimate is fooled. Try the “smooth bump” with few nodes, or type a formula with a narrow spike such as exp(-400*(x-0.3)^2).
Divided differences and derivatives
Theorem (mean value for divided differences). If \(f\in C^k\), then \(f[x_0,\dots,x_k]=f^{(k)}(\xi)/k!\) for some \(\xi\) between the smallest and the largest node.
Proof. Let \(q\) be the interpolating polynomial of degree at most \(k\); its \(k\)-th derivative is the constant \(k!\,c_k\) with \(c_k=f[x_0,\dots,x_k]\). The function \(f-q\) has \(k+1\) zeros, so by Rolle's theorem applied \(k\) times \((f-q)^{(k)}\) has a zero \(\xi\): \(f^{(k)}(\xi)=k!\,c_k\). \(\square\)
There is also an exact integral, the Hermite–Genocchi formula, \(f[x_0,\dots,x_k]=\int f^{(k)}(t_0x_0+\dots+t_kx_k)\,dt\) over the simplex \(t_i\ge0,\ \sum t_i=1\), which makes sense for repeated nodes. Practical reading: if \(|f^{(k)}|\le M_k\) then \(|c_k|\le M_k/k!\), so for smooth functions the coefficients \(|c_k|\) (left chart of the order card) fall fast, and for functions with a corner or a jump they do not. The size of the coefficients is a diagnostic for the smoothness of the data.
3 · Equal spacing: finite differences
Forward differences and the Newton–Gregory formula
For nodes \(x_i=x_0+ih\) define the forward differences \(\Delta y_i=y_{i+1}-y_i\) and \(\Delta^ky_i=\Delta^{k-1}y_{i+1}-\Delta^{k-1}y_i\). The recursion of divided differences has the denominator \(x_{i+k}-x_i=kh\), and induction on \(k\) gives
The second formula is the Newton–Gregory (forward difference) formula: here \(N_k(x_0+sh)=h^k\,s(s-1)\cdots(s-k+1)\), which absorbs the \(h^k\). Example: the values \(1,2,0,5\) at \(0,1,2,3\) have \(\Delta y_0=1\), \(\Delta^2y_0=-3\), \(\Delta^3y_0=10\), so \(p(s)=1+s-3\binom s2+10\binom s3\) and \(p(1.5)=1+1.5-1.125-0.625=0.75\), the value of group 1. Only subtractions are needed to build the table: a great help in the days of hand computation.
The binomial coefficients play the role of powers for differences, as the identity \(\Delta\binom{s}{k}=\binom{s}{k-1}\) plays the role of \(\tfrac{d}{ds}\tfrac{s^k}{k!}=\tfrac{s^{k-1}}{(k-1)!}\): the Newton–Gregory formula is the discrete Taylor formula. The card Differences and the Newton–Gregory formula checks \(c_k=\Delta^ky_0/(k!h^k)\) and the equality of the two ways of evaluating.
Differences reveal the degree, and amplify noise
If \(y_i\) are the values of a polynomial of degree \(d\) at equispaced nodes, then \(\Delta^{d}y_i\) is constant (equal to \(d!\,h^d\) times the leading coefficient) and \(\Delta^{d+1}y_i=0\). Example, the cubes \(1,8,27,64,125,216\): the differences are \(7,19,37,61,91\); then \(12,18,24,30\); then \(6,6,6\); then \(0,0\). The degree is 3 and the leading coefficient is \(6/(3!\cdot1)=1\). Computing tables of polynomials by adding a constant column upward is exactly what Babbage's Difference Engine was designed to do, in the 1820s, without a single multiplication.
The other face of the coin: each difference can double the size of random errors in the data (\(\Delta\) of two errors of size \(\sigma\) can reach \(2\sigma\), and so on up to \(2^k\sigma\) in \(\Delta^k\)). If a high column of differences, instead of vanishing, jumps up and down in sign, the data contain noise; tabulators have used this to find mistakes in printed tables. The same amplification is what makes high-order divided differences of noisy data useless (group 4).
4 · Repeated nodes: Taylor and Hermite
Repeated nodes: Taylor and Hermite
When two nodes coincide the recursion divides \(0\) by \(0\). The mean value theorem tells what the limit must be: \(f[x_0,x_0]=f'(x_0)\), and in general \(f[\underbrace{x_0,\dots,x_0}_{k+1}]=f^{(k)}(x_0)/k!\). With all the nodes equal, the Newton form is the Taylor polynomial,
With some nodes repeated and others not one gets Hermite interpolation: a node repeated \(m\) times prescribes the value and the first \(m-1\) derivatives there. The same table is used, putting the derivative where a \(0/0\) would appear. Example: \(f(0)=1\), \(f'(0)=0\), \(f(1)=2\), nodes \(0,0,1\): the columns are \(1,1,2\); then \(0,1\); then \(1\). So \(p=1+0\cdot x+1\cdot x^2=1+x^2\), which indeed has \(p(0)=1\), \(p'(0)=0\), \(p(1)=2\). Taylor's 1715 series is the limit of Newton's formula as the nodes merge, which is how Taylor found it.
Why clustered nodes ruin the arithmetic
Computed from function values, a divided difference of order \(k\) at spread \(s\) is a \(k\)-fold difference divided by \(s^k\). Each value carries a rounding error of about \(\varepsilon|f|\) (\(\varepsilon\approx1.1\times10^{-16}\)), and the division multiplies it: the error of \(c_k\) is of the order of \(\varepsilon|f|/s^k\) (up to a factor \(2^k\)). Meanwhile the approximation error of \(c_k\) as a Taylor coefficient is of the order of \(s\). The sum of the two is smallest at \(s\approx\varepsilon^{1/(k+1)}\): about \(10^{-8}\) for \(k=1\) but only \(10^{-3}\) for \(k=4\) (with the bounded factors ignored). This is the table of the card When nodes merge, divided differences become derivatives: each column improves, reaches its best and then drifts into noise, and the higher the order the earlier it happens.
It is the same trade-off as in numerical differentiation by finite differences. The remedy, when the derivatives are wanted: do not cluster; differentiate analytically or automatically, or use nodes on a circle in the complex plane (Cauchy's formula), which keeps the divisions harmless. And if you need Hermite data, give the derivatives to the Hermite form instead of approximating them by clustering.
5 · History
Before Newton: interpolation in tables, Briggs and Gregory
Interpolation is as old as astronomy: Babylonian and Greek astronomers filled in tables of positions by linear and then quadratic schemes. What made the polynomial formulas necessary in the seventeenth century was the production of large tables, above all of logarithms. Henry Briggs, in his Arithmetica Logarithmica (1624), already used finite differences to interpolate and to check his tables, and worked out special cases of the equal-interval formula.
The general equal-interval formula, the Newton–Gregory formula of group 3, is due to James Gregory, who found it in 1670 and described it in a letter to John Collins that year. Gregory did not publish it. He had in his hands, in a few lines, what the tabulators needed; the story of interpolation in the seventeenth century is partly the story of letters that were not printed.
Newton: letters, manuscripts, the Principia and the Methodus
Newton came to interpolation in the 1670s. In 1675 he described the method of filling tables by differences in a letter to John Smith. In 1676 he wrote the manuscript Regula differentiarum (the rule of differences), which stayed unpublished until the twentieth century. The first printed appearance is in the Principia (1687), Book III, Lemma V: to find a curve of parabolic kind that passes through any given points. Newton gives the solution first for equal intervals (his version of the Newton–Gregory formula) and then for unequal intervals, where the coefficients are what we now call divided differences, computed by successive division of differences by the distances between abscissas. The lemma serves a practical purpose: the path of a comet, observed at a few instants, is to be interpolated.
The treatise that develops the subject, the Methodus differentialis, was written in the 1670s and printed only in 1711; in it Newton speaks of differences “thus divided” (differentia sic divisa). So the Newton form of this app, with nodes at arbitrary positions, is in print from 1687, and Newton had the equal-spacing case and the general case within a few years of each other.
After Newton: Taylor, Waring, Euler, Lagrange, Gauss, Cauchy and the name
Brook Taylor's Methodus incrementorum (1715) takes Newton's formula to the limit of merging nodes and obtains the series that bears his name (group 4). Edward Waring published the formula we call Lagrange's in 1779; Euler derived it soon afterwards (around 1783); Lagrange gave it his own presentation in lectures at the École Normale around 1795 (see the Lagrange app of this section). In a lecture of 1812, published only later, Gauss showed that the formulas of Newton, Euler and Waring–Lagrange are the same polynomial in different notation, which is the uniqueness theorem.
The nineteenth century supplied the error theory and the vocabulary. Cauchy gave the remainder of the interpolation formula in terms of the derivative \(f^{(n+1)}\) (1821, 1840). Ampère (1826) called the quantities \(f[x_0,\dots,x_k]\) “fonctions interpolaires”, and Augustus De Morgan coined the name divided differences in his calculus text of 1842. Genocchi (1869) and Hermite gave the integral formula and Hermite (1878) the contour-integral version covering repeated nodes; the mean value theorem \(f[x_0,\dots,x_n]=f^{(n)}(\xi)/n!\) of group 2 is usually credited to Schwarz (1881–82) in this form. In 1901 Runge showed, with the example that carries his name (the Lagrange app), that interpolation at equispaced nodes can diverge.
The dates and attributions follow Meijering's chronology (Proc. IEEE 90, 2002) and Goldstine's History of Numerical Analysis; for the finer points of priority see those sources.
Newton and Lagrange: one polynomial, two viewpoints
The two forms are the same polynomial but they answer different questions. Lagrange's asks what is the polynomial for any table, once the nodes are fixed? and answers with a basis tied to the nodes, in which the coordinates of the polynomial are the data themselves: the interpolation matrix is the identity. Newton's asks how does the polynomial change when a node is added? and answers with a basis in which the interpolation matrix is triangular, so the polynomial grows by one term. Lagrange's is the form for theory (stability, the Lebesgue constant, error analysis for a fixed node set); Newton's is the form for computation that is built up (adaptive interpolation, tables, derivatives, Hermite data, confluent nodes). The barycentric formula, in the Lagrange app, gives the efficient version of the first and divided differences the efficient version of the second.
6 · Guided experiments and exercises
Experiment 1: one node at a time
Choose “eˣ on [−1, 1]”, Chebyshev nodes and \(n=8\), then press Add the nodes one by one. The red curve moves to \(f\), and the dashed blue term, the contribution of each new node, shrinks very fast: look at the “size of last term” box, and at the coefficients in the table.
Switch to “|x| (a corner)”. The terms and the coefficients no longer shrink at the same rate: the smoothness of \(f\) is written in the size of the divided differences (lesson “Divided differences and derivatives”).
Pause at \(k=3\) and read the card The new term corrects the old residual: the red dot at the node just added is the residual, the dashed curve is the true residual of the old polynomial.
In the card The next term is the error, compare the two curves for several \(k\). Then type the spike exp(-400*(x-0.3)^2) as your function and see when the estimate is fooled.
Experiment 2: the order of the nodes
Choose “Runge: 1/(1 + 25x²)” with Chebyshev nodes and set \(n=35\). In the card The same polynomial, different arithmetic compare the four orders. The left-to-right order and its reverse show a rounding difference of order \(10^{-7}\), the random order about \(10^{-13}\), and the Leja order about \(10^{-15}\): the same polynomial, evaluated with very different accuracy.
Now switch to equispaced nodes with the same \(n\). All four orders show a difference of about the same size (around \(10^{-5}\)): the problem itself is ill conditioned, and reordering cannot cure it. Compare with the Lebesgue constant in the Lagrange app.
Change the order in the settings and watch \(p_k\) for \(k<n\) change while the final polynomial does not: press Add the nodes one by one with “As given” and then with “Leja order”.
Experiment 3: your own points, in the order you click them
Choose “Your data”. The six initial points are equally spaced (\(h=2\)), so the card Differences and the Newton–Gregory formula shows the table of differences. Click a seventh point at a different spacing: the table disappears, because the nodes are no longer equally spaced.
Drag a point and watch which terms change. Moving the last point changes only the last term when you use fewer nodes; moving the first point changes every divided difference.
Click a new point in the middle: it becomes the last node (order “As given”), so only the last term changes. Choose “Leja order” and compare.
Exercises, with answers
Find the Newton form of the polynomial through \((-1,1),(0,0),(1,1)\). Answer. The table is \(1,0,1\); then \(-1,1\); then \(1\). So \(p=1-(x+1)+(x+1)x=x^2\).
Add the node \((2,3)\) to the previous polynomial without recomputing. Answer. \(p(2)=4\), so the residual is \(3-4=-1\) and \(N_3(2)=3\cdot2\cdot1=6\); hence \(c_3=-\tfrac16\) and \(p=x^2-\tfrac16(x+1)x(x-1)\). Check: \(p(2)=4-\tfrac16\cdot6=3\).
The table \(1,8,27,64,125,216\) at \(x=1,\dots,6\): find the degree and the Newton form. Answer. The differences are \(7,19,37,61,91\); \(12,18,24,30\); \(6,6,6\); then \(0\). Degree 3, \(c_k=\Delta^ky_1/k!\) gives \(1,7,6,1\): \(p=1+7(x-1)+6(x-1)(x-2)+(x-1)(x-2)(x-3)=x^3\).
Show that \(f[x_0,x_1,x_2]=1\) for \(f(x)=x^2\) and any three distinct nodes. Answer. \(f[x_0,x_1]=x_0+x_1\), \(f[x_1,x_2]=x_1+x_2\), so \(f[x_0,x_1,x_2]=\dfrac{(x_1+x_2)-(x_0+x_1)}{x_2-x_0}=1\). (It is the leading coefficient.)
Find the polynomial of least degree with \(p(0)=0\), \(p'(0)=1\), \(p(1)=2\), \(p'(1)=3\). Answer. Nodes \(0,0,1,1\): columns \(0,0,2,2\); then \(1,2,3\) (the first and last are the derivatives); then \(1,1\); then \(0\): \(p=0+1\cdot x+1\cdot x^2+0=x^2+x\), with \(p(1)=2\), \(p'(1)=3\) ✓.
Try it yourself in Python
A self-contained script for your own formula or table: it computes the divided-difference table, the Newton coefficients and the Newton form, checks them (interpolation property, order independence, comparison with the barycentric formula, Newton–Gregory for equal spacing), shows what clustering the nodes does, and saves a figure with the terms. It needs numpy and matplotlib (pip install numpy matplotlib). Change the settings at the top (SOURCE, the formula f, the interval, N, NODES, ORDER, DATA) and run it again.
# Newton form of the interpolating polynomial, with divided differences.
#
# p(x) = c_0 + c_1 (x - x_0) + c_2 (x - x_0)(x - x_1) + ... + c_n (x - x_0)...(x - x_{n-1}), c_k = f[x_0, ..., x_k]
#
# The k-th coefficient only depends on the first k + 1 nodes, so adding a node adds one term and changes nothing before it.
#
# Needs: pip install numpy matplotlib Run: python newton_interp.py
# Edit the settings just below and run again.
import numpy as np
import matplotlib.pyplot as plt
from math import comb
# ------------------------------------------------------------------ settings: play with these
SOURCE = "function" # "function": y_k = f(x_k) or "data": your own points below, IN THE ORDER GIVEN
def f(x): # any numpy expression of x
return np.exp(x) * np.sin(3 * x)
A, B = -1.0, 1.0
N = 6 # degree n: n + 1 nodes
NODES = "chebyshev" # "equispaced", "chebyshev" or "lobatto"
ORDER = "given" # order in which the nodes are used: "given", "reversed", "leja", "random"
DATA = [(0, 1), (2, 3), (4, 2.5), (6, 4.5), (8, 3), (10, 6)]
SAVE = True
# ------------------------------------------------------------------
def make_nodes(kind, n, a, b):
m, h, i = (a + b) / 2, (b - a) / 2, np.arange(n + 1)
if kind == "chebyshev":
return m - h * np.cos((2 * i + 1) * np.pi / (2 * n + 2))
if kind == "lobatto" and n > 0:
return m - h * np.cos(i * np.pi / n)
return np.linspace(a, b, n + 1)
def leja(xs):
"""Leja order: start at the node of largest modulus, then add the node that maximizes prod |x - chosen|."""
rest, out = list(xs), []
out.append(rest.pop(int(np.argmax(np.abs(rest)))))
while rest:
score = [np.sum(np.log(np.abs(r - np.array(out)) + 1e-300)) for r in rest]
out.append(rest.pop(int(np.argmax(score))))
return np.array(out)
def divided_differences(xs, ys):
"""Return the whole triangle T[k][i] = f[x_i, ..., x_{i+k}]; the Newton coefficients are T[k][0]."""
T = [np.array(ys, dtype=float)]
for k in range(1, len(xs)):
prev = T[-1]
T.append((prev[1:] - prev[:-1]) / (xs[k:] - xs[:-k]))
return T
def newton_eval(t, xs, c, k=None):
"""Nested (Horner-like) evaluation of the first k + 1 terms: c_0 + (t-x_0)(c_1 + (t-x_1)(c_2 + ...))."""
k = len(c) - 1 if k is None else k
s = np.full_like(t, c[k], dtype=float)
for j in range(k - 1, -1, -1):
s = s * (t - xs[j]) + c[j]
return s
if SOURCE == "data":
pts = np.array(DATA, dtype=float) # NOT sorted: the order is the order of the nodes
xs, ys = pts[:, 0], pts[:, 1]
A, B = xs.min(), xs.max()
else:
xs = make_nodes(NODES, N, A, B)
if ORDER == "reversed":
xs = xs[::-1]
elif ORDER == "leja":
xs = leja(xs)
elif ORDER == "random":
xs = np.random.default_rng(1).permutation(xs)
ys = f(xs)
n = len(xs) - 1
T = divided_differences(xs, ys)
c = np.array([T[k][0] for k in range(n + 1)])
# 1. the divided-difference table
print("nodes (in this order):", np.array2string(xs, precision=4))
print("\ndivided differences (column k holds f[x_i, ..., x_{i+k}]; the first row is the list of coefficients c_k)")
for i in range(n + 1):
print(" " + " ".join(f"{T[k][i]:11.5f}" if i <= n - k else " " * 11 for k in range(n + 1)))
print("\nNewton coefficients c_k =", np.array2string(c, precision=5))
# 2. the same polynomial for every order of the nodes, but not the same coefficients
t = np.linspace(A, B, 1000)
p = newton_eval(t, xs, c)
order2 = np.argsort(xs)
c2 = np.array([divided_differences(xs[order2], ys[order2])[k][0] for k in range(n + 1)])
p2 = newton_eval(t, xs[order2], c2)
print("\nsame polynomial with the nodes sorted: max |p - p_sorted| =", np.abs(p - p2).max())
# 3. the error: f(x) - p(x) = f[x_0, ..., x_n, x] * omega(x), and for smooth f this is f^(n+1)(xi)/(n+1)! * omega(x)
if SOURCE == "function":
print("max |f - p| on the grid =", np.abs(f(t) - p).max())
# 4. equispaced nodes: divided differences are forward differences divided by k! h^k (Gregory-Newton)
if NODES == "equispaced" and SOURCE == "function" and ORDER == "given":
h = xs[1] - xs[0]
D = [ys.copy()]
for k in range(1, n + 1):
D.append(np.diff(D[-1]))
s = (t - xs[0]) / h
pg = sum(D[k][0] * np.array([np.prod([(si - j) for j in range(k)]) / np.prod(range(1, k + 1)) for si in s]) for k in range(n + 1))
print("Gregory-Newton with Delta^k y_0 reproduces p: max difference =", np.abs(p - pg).max())
# 5. the pictures: p_0, p_1, ..., p_n (each new node adds one term)
fig, ax = plt.subplots(1, 2, figsize=(13, 4.8))
cmap = plt.get_cmap("viridis")
for k in range(n + 1):
ax[0].plot(t, newton_eval(t, xs, c, k), color=cmap(k / max(n, 1)), lw=1.1 + (k == n), label=f"$p_{k}$" if n <= 7 else None)
if SOURCE == "function":
ax[0].plot(t, f(t), "--", color="gray", lw=1.3, label="$f$")
ax[0].plot(xs, ys, "o", color="crimson", zorder=5)
for k, xk in enumerate(xs):
ax[0].annotate(str(k), (xk, ys[k]), textcoords="offset points", xytext=(4, 6), fontsize=8)
ax[0].set_title("partial sums $p_k$: node $x_k$ adds the term $c_k(x-x_0)\\cdots(x-x_{k-1})$"); ax[0].legend(fontsize=8, ncol=2); ax[0].grid(alpha=.3)
for k in range(n + 1):
nb = np.prod([t - xs[j] for j in range(k)], axis=0) if k else np.ones_like(t)
ax[1].plot(t, nb, color=cmap(k / max(n, 1)), lw=1.2)
ax[1].set_title("the Newton basis $N_k(x)=\\prod_{j<k}(x-x_j)$: $N_k$ vanishes at $x_0,\\dots,x_{k-1}$")
lo, hi = np.percentile(np.concatenate([np.prod([t - xs[j] for j in range(k)], axis=0) if k else np.ones_like(t) for k in range(n + 1)]), [1, 99])
ax[1].set_ylim(lo * 1.2, hi * 1.2); ax[1].grid(alpha=.3)
plt.tight_layout()
if SAVE:
plt.savefig("newton_pieces.png", dpi=140)
# 6. clustering the nodes: the divided differences become derivatives, f[x_0..x_k] -> f^(k)(x_0)/k! (Taylor, 1715)
if SOURCE == "function":
print("\nclustering the nodes around x_0 = 0: the first coefficients settle, then rounding errors (cancellation) take over")
u = np.arange(7) / 6.0
for spread in (1.0, 1e-1, 1e-2, 1e-3, 1e-4, 1e-6, 1e-8):
xc = spread * u
cc = [divided_differences(xc, f(xc))[k][0] for k in range(5)]
print(f" spread {spread:7.0e}: " + " ".join(f"{v:12.6f}" for v in cc))
plt.show()
References: J. Meijering, A chronology of interpolation: from ancient astronomy to modern signal and image processing, Proc. IEEE 90 (2002) 319–342; H. H. Goldstine, A History of Numerical Analysis from the 16th through the 19th Century, Springer 1977; C. de Boor, Divided differences, Surveys in Approximation Theory 1 (2005) 46–69; N. J. Higham, Accuracy and Stability of Numerical Algorithms, 2nd ed., SIAM 2002 (chapter 5); L. Reichel, Newton interpolation at Leja points, BIT 30 (1990) 332–346; I. Newton, Methodus differentialis (1711) and Principia, Book III, Lemma V.
Finite differences and the Newton–Gregory polynomial
When the nodes are equally spaced, \(x_i=x_0+ih\), the divided differences of the Newton form collapse into plain finite differences, obtained by repeated subtraction: \(\Delta y_i=y_{i+1}-y_i\), \(\Delta^2y_i=\Delta y_{i+1}-\Delta y_i\), and so on. The interpolating polynomial then takes the Newton–Gregory form, a sum of the differences \(\Delta^ky_0\) times binomial coefficients \(\binom sk\) in the variable \(s=(x-x_0)/h\), with a backward twin that starts from the last node. This app lets you build the difference table for a function, a formula of your own or a list of numbers; read forward, backward and central differences off it; see the polynomial grow term by term; watch noise in the data being amplified by the differences; and then differentiate the polynomial to obtain the finite-difference formulas for \(f'\) and \(f''\), with their truncation and rounding errors. This is the groundwork for numerical derivatives. A course with historical remarks follows below, and a ready-made Python script lets you repeat everything on your own computer.
The Newton–Gregory polynomial
Function, equally spaced nodes and the polynomial of degree \(k\)
Ready
f (function)p (polynomial)nodes usednodes not used
MAX ERROR \(\max|f-p_k|\)—
LAST DIFFERENCE —
NEXT DIFFERENCE —
Max error: largest of \(|f-p_k|\) over 400 points between the first and the last node (only for a function). The last difference is the one multiplying \(\binom sk\) in the highest term used; the next difference is the one that would come next, and its size estimates the error, as the lesson on the error term explains.
Forward, backward and central differences in one table
Each column is the difference of the previous one, written between its two parents. The numbers are the same whichever name we give them: \(\Delta^ky_i=\nabla^ky_{i+k}=\delta^ky_{i+k/2}\). What changes is which of them are used. The forward formula takes the top diagonal, the backward formula takes the bottom diagonal, and the central formulas (Gauss, Stirling, Bessel) zigzag along a horizontal line through the table. Choose the operator to see the path highlighted.
Reading the table: a column that is (nearly) constant tells you the degree; a column that is (nearly) zero tells you the previous one was constant. With real data the last columns are dominated by noise, see the next card but one.
Evaluating the formula
Terms and partial sums at a point
Type a point \(x\). The app computes \(s=(x-x_0)/h\) (forward) or \(s=(x-x_n)/h\) (backward) and adds the terms one by one. For \(x\) between the first two nodes the forward terms shrink fast, which is why the forward form is the one for the start of the table; near the end the backward form behaves the same way. The last column is the error of the partial sum against \(f\), when there is a function.
Noise and differences
Differences amplify noise: \(2^k\)
Add a disturbance of size \(\varepsilon\) to the values and take differences again. Every difference is a subtraction, so a perturbation \(\varepsilon_i\) of the data enters \(\Delta^ky_i\) with the coefficients \((-1)^{k-j}\binom kj\), whose absolute values add up to \(2^k\). The plot shows the largest absolute entry of each column, on a log scale, for the clean data (blue) and the disturbed data (red), with the bound \(2^k\varepsilon\) (dashed). The red line follows the blue one until the noise takes over, and from then on it grows by a factor close to 2 per column.
Numerical derivatives
Differentiating the Newton–Gregory polynomial
Since \(p(x_0+sh)=\sum_k\binom sk\Delta^ky_0\) and \(\tfrac{d}{dx}=\tfrac1h\tfrac{d}{ds}\), differentiating the polynomial at \(s=0\) gives \(hf'(x_0)\approx\Delta y_0-\tfrac12\Delta^2y_0+\tfrac13\Delta^3y_0-\cdots\) and similarly for higher derivatives. Truncating after \(\Delta^K\) and writing the differences in terms of the values gives the formulas of the table below, all of them exact for polynomials of degree \(K\). Their order is the power of \(h\) in the error, which is the first term that was left out. The central formulas come from differentiating the polynomial through nodes placed symmetrically around the point.
The same formulas applied to the table above, at its first node (forward) and at its middle node (central), with the step \(h\) of the table. When there is a function, the reference value is computed by a central formula with thirteen points and a much smaller step, and the last column is the absolute error.
Now shrink the step. The plot shows the error of each formula at \(x_0\) against the step \(h\) (both axes logarithmic), computed in double precision. For large \(h\) the truncation error \(Ch^p\) dominates and the curve is a straight line of slope \(p\); for small \(h\) the rounding errors of the values, of size about \(10^{-16}\), are divided by \(h^m\) and the curve climbs back with slope \(-m\). Each formula has a best step, where the two meet. You can also add a relative noise to the function values, to mimic measured data.
Complete course
Finite differences: from the table to the derivative
Six groups of short lessons; the history is in group 5. Each one points to something you can see in the app above.
1 · Finite differences
What is a finite difference?
Given values \(y_i=f(x_0+ih)\) at equally spaced nodes, the forward difference is \(\Delta y_i=y_{i+1}-y_i\), and higher differences are defined by repetition, \(\Delta^ky_i=\Delta^{k-1}y_{i+1}-\Delta^{k-1}y_i\). Unwinding the recursion gives the closed formula, in which Pascal's triangle appears with alternating signs:
Example: the values \(1,2,0,5\) have \(\Delta y=1,-2,5\), then \(\Delta^2y=-3,7\), then \(\Delta^3y=10\); and indeed \(\Delta^3y_0=-1+3\cdot2-3\cdot0+5=10\). The table in the app writes each column between its two parents, so that every entry sits in the middle of the nodes it uses.
Operators: shifts, \(\Delta\), \(\nabla\), \(\delta\) and the derivative
Let \(E\) be the shift, \((Ef)(x)=f(x+h)\). Then \(\Delta=E-I\) (forward), \(\nabla=I-E^{-1}\) (backward) and \(\delta=E^{1/2}-E^{-1/2}\) (central). They commute and satisfy \(\Delta^ky_i=\nabla^ky_{i+k}=\delta^ky_{i+k/2}\), which is why one table serves all three. Taylor's formula says \(f(x+h)=\sum_k\tfrac{h^k}{k!}f^{(k)}(x)=e^{hD}f(x)\), so \(E=e^{hD}\), where \(D=d/dx\), and therefore
This is formal, but it is the engine of the whole last group: every finite-difference formula for a derivative is a truncation of this series or of its powers. It also explains the formula \(E^s=(1+\Delta)^s=\sum_k\binom sk\Delta^k\), which is the Newton–Gregory formula in operator form.
Differences of polynomials: detecting the degree
If \(p\) is a polynomial of degree \(d\), then \(\Delta p\) has degree \(d-1\), because the leading terms of \(p(x+h)\) and \(p(x)\) cancel. Repeating, \(\Delta^dp\) is constant, equal to \(d!\,a_d\,h^d\) where \(a_d\) is the leading coefficient, and \(\Delta^{d+1}p=0\). So the column of the table that is constant tells the degree of a polynomial from its values alone. Example: the cubes \(1,8,27,64,125,216\) have first differences \(7,19,37,61,91\), second differences \(12,18,24,30\) and third differences \(6,6,6\): degree \(3\), and \(3!\cdot1=6\) is the leading coefficient times \(3!\). Type these values in Your data to see it.
The converse is the principle of numerical differentiation: if \(f\) is smooth, \(\Delta^kf(x)\approx h^kf^{(k)}(x)\), and more precisely \(\Delta^kf(x)=h^kf^{(k)}(\xi)\) for some \(\xi\) in the interval of the nodes used.
Falling factorials and the binomial basis
For differences, the role of the power \(x^k\) is played by the falling factorial \(x^{\underline k}=x(x-1)\cdots(x-k+1)\) (take \(h=1\)), because \(\Delta x^{\underline k}=k\,x^{\underline{k-1}}\), exactly as \(\tfrac{d}{dx}x^k=kx^{k-1}\). Dividing by \(k!\), the binomial coefficients satisfy \(\Delta\binom xk=\binom x{k-1}\) (Pascal's rule), so \(\binom xk\) is to differences what \(x^k/k!\) is to derivatives. Consequently a polynomial written in the basis \(\binom xk\) has a very simple table: \(p(x)=\sum_k a_k\binom xk\) has \(\Delta^kp(0)=a_k\). That is the Newton–Gregory formula, read as \(a_k=\Delta^ky_0\).
There is also a discrete fundamental theorem of calculus: \(\sum_{i=0}^{n-1}\Delta y_i=y_n-y_0\). With \(y_i=\binom i{k+1}\) it gives \(\sum_{i=0}^{n-1}\binom ik=\binom n{k+1}\), the hockey-stick identity.
2 · The Newton–Gregory formulas
The forward formula and where it comes from
The Newton form with equal spacing has \(c_k=f[x_0,\dots,x_k]\), and induction on the divided-difference recursion, whose denominator is now \(x_{i+k}-x_i=kh\), gives \(f[x_i,\dots,x_{i+k}]=\Delta^ky_i/(k!\,h^k)\). Also \(\prod_{j\lt k}(x-x_j)=h^k\,s(s-1)\cdots(s-k+1)\) for \(x=x_0+sh\), and the powers of \(h\) cancel:
It is a discrete Taylor formula: \(\Delta^ky_0\) in place of \(h^kf^{(k)}(x_0)\) and \(\binom sk\) in place of \(s^k/k!\). At the nodes \(s=0,1,\dots,n\) the binomial \(\binom sk\) is \(0\) for \(k>s\), so the sum is finite and reproduces \(y_s\) (this is \(E^s=(1+\Delta)^s\)). Only subtractions are needed to build the table, which was a great help in the days of hand computation. Note that the polynomial of degree \(k\) (the first \(k+1\) terms) is the interpolating polynomial of the first \(k+1\) nodes: raising the degree adds one term and changes nothing before it.
The backward formula
Start from the last node instead: the same polynomial can be written in the nodes \(x_n,x_{n-1},\dots\), and the Newton form in that order has coefficients \(f[x_n,\dots,x_{n-k}]=\nabla^ky_n/(k!\,h^k)\), where \(\nabla^ky_n=\Delta^ky_{n-k}\) is on the bottom diagonal of the table. With \(x=x_n+sh\), the product \((x-x_n)(x-x_{n-1})\cdots\) equals \(h^k\,s(s+1)\cdots(s+k-1)\), so
Here \(s\) is negative inside the table. For \(k\lt n\) the forward and backward polynomials of the same degree are different, built from different nodes, and each is the better one near its own end. For \(k=n\) they coincide, since there is only one polynomial of degree \(n\) through \(n+1\) points.
The error term and the next difference
The interpolation error for the first \(k+1\) nodes is \(f(x)-p_k(x)=f[x_0,\dots,x_{k},x]\prod_{j\le k}(x-x_j)\), which for equal spacing becomes
Since \(\Delta^{k+1}y_0\approx h^{k+1}f^{(k+1)}\), the error is estimated by the next term of the series, \(\binom s{k+1}\Delta^{k+1}y_0\), which is what the app shows under next difference. In practice: add terms while they keep shrinking, and stop when the next one is as small as you need or, for data, as small as the noise. Inside the table \(|\binom s{k+1}|\) is small for \(s\) near the nodes used and grows quickly for \(s\) outside \([0,k]\): that is the numerical reason why extrapolation is dangerous (tick Extend the plot to see it).
A worked example, forward and backward
Take \(y=1,2,0,5\) at \(x=0,1,2,3\). The differences are \(\Delta y_0=1\), \(\Delta^2y_0=-3\), \(\Delta^3y_0=10\) (top diagonal) and \(\nabla y_3=5\), \(\nabla^2y_3=7\), \(\nabla^3y_3=10\) (bottom diagonal). Forward: \(p(s)=1+s-3\binom s2+10\binom s3\), and at \(x=1.5\), \(s=1.5\), we get \(1+1.5-1.125-0.625=0.75\). Backward, with \(x=3+s\) and \(s=-1.5\): \(p=5+5s+7\binom{s+1}2+10\binom{s+2}3=5-7.5+2.625+0.625=0.75\). The same value, as it must be: both are the cubic through the four points. Enter these four numbers as Your data, with \(x_0=0\) and \(h=1\), and read the table, the formula and the evaluation card.
Central formulas: Gauss, Stirling, Bessel, Everett
All of these are the same polynomial written in a different order of the nodes: instead of \(x_0,x_1,x_2,\dots\) one uses \(x_c,x_{c+1},x_{c-1},x_{c+2},\dots\), zigzagging around a central node \(x_c\). The coefficients are then the differences along a horizontal path of the table (choose central differences above). Averaging the two Gauss formulas gives Stirling's formula, which is best for \(|s|\le\tfrac12\) around a node; averaging at the midpoint between two nodes gives Bessel's; and Everett's formula uses only even differences of the two nodes that enclose the point. These were the workhorses of table interpolation before computers, because the terms shrink the fastest when the point is near the middle of the nodes used. For derivatives, their value is different: they lead to the symmetric formulas of group 4, whose odd error terms vanish.
3 · Noise
Why differences amplify noise
Suppose each \(y_i\) carries an error \(\varepsilon_i\) with \(|\varepsilon_i|\le\varepsilon\). By the closed formula of lesson 1, the error in \(\Delta^ky_i\) is \(\sum_j(-1)^{k-j}\binom kj\varepsilon_{i+j}\), whose absolute value is at most \(2^k\varepsilon\), and this is attained when the errors alternate in sign. A single wrong value spreads over \(k+1\) entries of column \(k\) with the pattern \(\pm\binom kj\varepsilon\), and the middle one is about \(2^k\varepsilon/\sqrt{\pi k/2}\). So each column of the table can lose up to one bit, a factor \(2\), and the noise of the data is multiplied by about \(1000\) by the tenth column. Meanwhile the true differences of a smooth function are \(h^kf^{(k)}\), which shrink quickly when \(h\) is small. Eventually the noise wins; the card Differences amplify noise shows exactly where.
Detecting the degree of noisy data
For a polynomial known only up to errors of size \(\varepsilon\), the columns of the table do not become zero but settle at the noise level, and from there grow by a factor between \(1\) and \(2\) per column. A practical rule: compute the table; the degree is the index of the last column whose entries are clearly above \(2^k\varepsilon\); everything after is noise. (This is an old method, and the basis of the variate difference method of statisticians.) In the app, the line Disturbed data flattens and then grows by a factor of about \(2\); the point where it leaves the clean line is where information ends. For exact data in floating point the same rule applies with \(\varepsilon\approx10^{-16}\max|y|\), and the app uses it in the sentence under the table.
Equal spacing, high degree and the Runge phenomenon
The Newton–Gregory polynomial interpolates at equally spaced nodes, and for large \(n\) this is the worst possible choice of nodes: the binomial factor \(\binom s{n+1}\) is huge near the ends, and interpolation of Runge's function \(1/(1+25x^2)\) diverges (choose it in the app, increase \(n\), and look near the ends; use the backward form at the right end and the forward form at the left end, the error is the same). The remedy is not a better formula but better nodes (Chebyshev, see the Lagrange and Newton apps) or piecewise polynomials of low degree. For this reason the formulas of this app are used with small \(k\), typically 2 to 5, on a few neighboring nodes: that is how the finite-difference formulas for derivatives, and the Newton–Cotes quadrature rules, are built.
4 · Numerical derivatives
From the polynomial to the derivative formulas
Differentiate \(p(x_0+sh)=\sum_k\binom sk\Delta^ky_0\) with respect to \(x\), that is \(\tfrac1h\tfrac d{ds}\), and set \(s=0\). Since \(\tfrac d{ds}\binom sk\big|_{s=0}=\tfrac{(-1)^{k-1}}k\), we get \(hf'(x_0)\approx\Delta y_0-\tfrac12\Delta^2y_0+\tfrac13\Delta^3y_0-\cdots\), the series for \(\ln(1+\Delta)\). Truncating after \(\Delta^K\) and expanding the differences in terms of the \(y_j\):
For the second derivative square the series, \((hD)^2=\Delta^2-\Delta^3+\tfrac{11}{12}\Delta^4-\tfrac56\Delta^5+\cdots\), and obtain \(\tfrac{y_0-2y_1+y_2}{h^2}\), then \(\tfrac{2y_0-5y_1+4y_2-y_3}{h^2}\), and so on. The table of formulas in the app is computed by exactly this procedure, with exact fractions, and the weights always add up to \(0\), because the formula must give \(0\) for a constant function. The same formulas apply at the last node with \(\nabla\), changing the sign of the odd derivatives.
Truncation error and order
What was left out is the next term of the series. Writing \(a_K\) for the coefficient of \(\Delta^K\) in \(h^mD^m\), the exact derivative minus the formula with \(K\) terms is about \(a_{K+1}h^{K+1-m}f^{(K+1)}\). For the two-point forward formula, \(\tfrac{f(x+h)-f(x)}h=f'(x)+\tfrac h2f''(x)+\cdots\), so the order is \(1\). For the three-point forward formula the error is \(+\tfrac{h^2}3f'''\): order \(2\). The central formulas are better than their forward cousins because the odd terms cancel by symmetry: \(\tfrac{f(x+h)-f(x-h)}{2h}=f'(x)+\tfrac{h^2}6f'''(x)+\cdots\) is of order \(2\) with only two evaluations, and \(\tfrac{f(x+h)-2f(x)+f(x-h)}{h^2}=f''(x)+\tfrac{h^2}{12}f^{(4)}(x)+\cdots\) has order \(2\) for the second derivative. The last column of the table of formulas lists these error terms; they were not typed in, they are computed from the weights.
Rounding error and the best step
Each value \(y_j=f(x_j)\) is stored with a relative error up to \(u\approx1.1\times10^{-16}\). A formula with weights \(w_j\) divides \(\sum w_jy_j\) by \(h^m\), so the rounding error is up to \(u\,|f|\sum|w_j|/h^m\), which grows without bound as \(h\to0\). The total error behaves like \(E(h)\approx Ch^p+\rho/h^m\) with \(\rho\approx u|f|\sum|w_j|\). It is smallest at \(h_*\sim u^{1/(p+m)}\), with \(E(h_*)\sim u^{p/(p+m)}\):
These are the V shapes in the plot, with the minima where the formulas say. The practical lesson is that a smaller step is not always better, and that a higher-order formula both reaches a smaller error and tolerates a larger step. Also, the second derivative is intrinsically harder than the first: the same arithmetic gives only half the digits.
Which formula to use
Inside the table, use a central formula: for the same number of points it has the same order as the forward formula but a smaller error constant (compare \(\tfrac{h^2}6\) with \(\tfrac{h^2}3\) for \(f'\)), and for \(f'\) its middle weight is zero, so two evaluations are enough for order \(2\), where the forward formula needs three. At the ends of the table only forward (at the start) and backward (at the end) formulas are possible, and they need one more point for the same order. Orders higher than 4 are rarely worth it: the weights grow, the rounding amplification \(\sum|w_j|\) increases, and the function must be smooth over a wider stretch. The two-point central formula \(\tfrac{f(x+h)-f(x-h)}{2h}\) is the best simple default, with \(h\approx10^{-5}\) to \(10^{-6}\) times the scale of \(x\) in double precision.
Noisy data
When the values come from measurements, the noise \(\varepsilon\) is not \(10^{-16}\) but perhaps \(10^{-3}\), and the rounding term \(\rho/h^m\) is replaced by \(\varepsilon\sum|w_j|/h^m\): with \(h=0.1\) and \(m=2\), the second derivative from three points has noise amplified by a factor \(4/h^2=400\). Choose the noise option in the plot to watch the minimum move to larger \(h\) and to a larger error. The cure is to not differentiate the raw data: smooth first (a least-squares fit with a low-degree polynomial or a spline, see the least-squares app) and differentiate the fit, or use a wide stencil with larger \(h\).
5 · History
Differences before Newton: Briggs and the tables
Finite differences came with the great computations of tables. Henry Briggs, who computed the first tables of common logarithms, used differences to interpolate, and described the method in his Arithmetica logarithmica (1624); his tables were filled in by repeated subtraction and addition exactly as in the first lesson. Historians of interpolation (Meijering 2002, Goldstine 1977) trace the use of sequences of differences to astronomers and table makers of the early seventeenth century, well before the formulas that carry the names of Gregory and Newton.
Gregory and Newton
The forward formula with binomial coefficients appears in a letter that James Gregory wrote to John Collins in November 1670, where it is used to interpolate series and tables. Newton gave the formula for equal and for unequal spacing in Lemma V of Book III of the Principia (1687), in the setting of the orbit of a comet, and treated the subject at length in Methodus differentialis, written in 1676 and published in 1711. That is why the forward formula is called the Newton–Gregory or Gregory–Newton formula. The backward form and the central ones (Gauss, Stirling, Bessel, Everett) were developed in the eighteenth and nineteenth centuries.
The calculus of finite differences
Brook Taylor's Methodus incrementorum directa et inversa (1715) made finite differences a subject in their own right, and it contains the series that we call Taylor's, obtained as the limit of Newton's formula when \(h\to0\). James Stirling's Methodus differentialis (1730) developed the central formulas and the study of series by differences. Euler's Institutiones calculi differentialis (1755) fixed the notation \(\Delta\) and built differential calculus on top of the finite one, and the operator ideas of this page (\(E=e^{hD}\), \(\Delta=e^{hD}-1\)) were systematized in the nineteenth century, culminating in George Boole's A Treatise on the Calculus of Finite Differences (1860).
Difference engines
Lesson 3 is the reason for the machines. If a polynomial of degree \(d\) has constant \(d\)-th differences, its whole table can be extended by additions alone: put the \(d\)-th difference at the bottom, add it to the one above, and so on up to \(y\), and repeat. Charles Babbage proposed in 1822 a machine to do this mechanically, to print tables free of human error, and the Swedish father and son Georg and Edvard Scheutz built working printing engines of this kind in the 1850s. Smooth functions (logarithms, sines) were tabulated by fitting low-degree polynomials in short stretches and letting the machine run on differences.
6 · Exercises
Find the degree
Build the table of \(2,5,10,17,26,37\) (use Your data). Which column is constant? What is the polynomial? Answer: \(\Delta=3,5,7,9,11\) and \(\Delta^2=2,2,2,2\), so the degree is \(2\) with leading coefficient \(2/2!=1\). The values are \(x^2+1\) at \(x=1,\dots,6\), and the formula gives \(p(3.5)=13.25\) with \(x_0=1\), \(h=1\).
Evaluate by hand
With \(y=1,2,0,5\) at \(x=0,1,2,3\), use the forward formula to compute \(p(2.5)\), then the backward formula, and compare with the app. Answer: forward with \(s=2.5\): \(1+2.5-3\cdot1.875+10\cdot0.3125=1\). Backward with \(s=-0.5\): \(5-2.5+7\cdot(-0.125)+10\cdot(-0.0625)\)\(=1\). Both give \(1\). Which of them has the smaller terms? (The backward one: \(x\) is near the end.)
Derive the central formula for \(f\)
Show that \(\tfrac{f(x+h)-2f(x)+f(x-h)}{h^2}=f''(x)+\tfrac{h^2}{12}f^{(4)}(x)+O(h^4)\) by expanding \(f(x\pm h)\) in Taylor series, and check with the app that \(\delta^2=\Delta^2E^{-1}\), so that \(\delta^2y_i=\Delta^2y_{i-1}\). Hint: the odd terms cancel, and \(2\tfrac{h^4}{4!}=\tfrac{h^4}{12}\).
The best step by experiment
Take \(f=e^x\), \(m=1\), and read the best step of the two-point forward formula from the plot. Explain the value using lesson d3, then add a relative noise of \(10^{-8}\) and explain what changes. Answer: about \(h_*\approx10^{-8}\) with error about \(10^{-8}\), since \(u^{1/2}\approx10^{-8}\); with the noise \(10^{-8}\) in place of \(u\) the best step moves to about \(10^{-4}\) and the error to about \(10^{-4}\).
Try it yourself in Python
A self-contained script for your own formula or table of equally spaced values: it builds the difference table, evaluates the forward and backward Newton–Gregory polynomials (and checks them against the interpolating polynomial), shows how noise grows by a factor close to 2 per column, computes the finite-difference weights for the derivatives with exact fractions, and saves the plot of the error against the step. It needs numpy and matplotlib (pip install numpy matplotlib). Change the settings at the top and run it again.
# Finite differences, the Newton-Gregory polynomial and finite-difference derivatives.
#
# forward: p(x_0 + s h) = sum_k binom(s, k) Delta^k y_0 (s = (x - x_0) / h)
# backward: p(x_n + s h) = sum_k binom(s + k - 1, k) nabla^k y_n (s = (x - x_n) / h)
# derivative: h D = ln(1 + Delta) = Delta - Delta^2/2 + Delta^3/3 - ...
#
# Needs: pip install numpy matplotlib Run: python finite_differences.py
# Edit the settings just below and run again.
from fractions import Fraction
from math import comb, factorial
import numpy as np
import matplotlib.pyplot as plt
# ------------------------------------------------------------------ settings: play with these
SOURCE = "function" # "function": y_i = f(x_i) or "data": your own values below
def f(x): # any numpy expression of x
return np.exp(x)
X0, H, N = -1.0, 0.25, 8 # nodes x_i = X0 + i H, i = 0..N (the data list sets N when SOURCE = "data")
DATA = [1, 2, 0, 5, 4.5, 8]
K = 3 # degree used in the plot of the polynomial
EVAL_AT = -0.6 # point where both formulas are evaluated
NOISE = 1e-3 # size of the disturbance in the noise experiment (alternating signs: the worst case)
DERIVATIVE_ORDER = 1 # 1 or 2: the derivative in the step experiment
SAVE = True
# ------------------------------------------------------------------
def difference_table(y):
"""T[k][i] = Delta^k y_i; T[k] has N + 1 - k entries."""
T = [np.array(y, dtype=float)]
for _ in range(len(y) - 1):
T.append(np.diff(T[-1]))
return T
def binom_s(s, k):
"""binom(s, k) for real s."""
b = 1.0
for j in range(k):
b *= (s - j) / (j + 1)
return b
def forward_eval(x, x0, h, T, k):
s = (x - x0) / h
return sum(binom_s(s, j) * T[j][0] for j in range(k + 1))
def backward_eval(x, xn, h, T, k):
n = len(T) - 1
s = (x - xn) / h
return sum(binom_s(s + j - 1, j) * T[j][n - j] for j in range(k + 1))
def lagrange_check(x, xs, ys):
"""The same polynomial from the Lagrange form, to compare (n + 1 nodes, degree n)."""
tot = 0.0
for j in range(len(xs)):
term = ys[j]
for m in range(len(xs)):
if m != j:
term *= (x - xs[m]) / (xs[j] - xs[m])
tot += term
return tot
def forward_weights(m, K):
"""Weights w_j with D^m f(x_0) ~ h^-m sum_j w_j y_j from differentiating the forward polynomial, in exact fractions."""
def binom_poly(k): # coefficients of binom(s, k) in powers of s
c = [Fraction(1)]
for j in range(k):
nc = [Fraction(0)] * (len(c) + 1)
for i, ci in enumerate(c):
nc[i + 1] += ci
nc[i] -= j * ci
c = [q / (j + 1) for q in nc]
return c
w = [Fraction(0)] * (K + 1)
for k in range(m, K + 1):
a_k = binom_poly(k)[m] * factorial(m) # m-th derivative at s = 0
for j in range(k + 1):
w[j] += a_k * (-1) ** (k - j) * comb(k, j)
return w
def central_weights(m, r):
"""Weights on the offsets -r..r from sum_j w_j o_j^q = m! delta_{qm}."""
o = np.arange(-r, r + 1)
A = np.vander(o, increasing=True).T.astype(float)
b = np.zeros(2 * r + 1)
b[m] = factorial(m)
return np.linalg.solve(A, b)
def main():
if SOURCE == "data":
y = np.array(DATA, dtype=float)
n = len(y) - 1
x = X0 + H * np.arange(n + 1)
else:
n = N
x = X0 + H * np.arange(n + 1)
y = f(x)
T = difference_table(y)
print("difference table (column k = Delta^k y_i):")
for k, col in enumerate(T):
print(f" k={k}:", np.array2string(col, precision=6, suppress_small=True, max_line_width=110))
scale = np.max(np.abs(y))
for k in range(1, n + 1):
if np.max(np.abs(T[k])) <= 64 * 2.3e-16 * scale * 2 ** k:
print(f"column {k} vanishes to rounding error: the data are those of a polynomial of degree {k - 1}")
break
# ---- the two formulas against the interpolating polynomial
pf, pb = forward_eval(EVAL_AT, X0, H, T, n), backward_eval(EVAL_AT, x[n], H, T, n)
pl = lagrange_check(EVAL_AT, x, y)
print(f"\nat x = {EVAL_AT}: forward {pf:.12g} backward {pb:.12g} Lagrange {pl:.12g}")
for k in range(n + 1):
print(f" k={k}: forward {forward_eval(EVAL_AT, X0, H, T, k):.10g} backward {backward_eval(EVAL_AT, x[n], H, T, k):.10g}")
# ---- noise: alternating signs give the worst case 2^k
yn = y + NOISE * (-1.0) ** np.arange(n + 1)
Tn = difference_table(yn)
print(f"\nlargest entry of each column, clean / disturbed (noise {NOISE}):")
for k in range(n + 1):
print(f" k={k}: {np.max(np.abs(T[k])):.3e} {np.max(np.abs(Tn[k])):.3e} bound 2^k * noise = {2 ** k * NOISE:.3e}")
# ---- derivative formulas
m = DERIVATIVE_ORDER
print(f"\nforward formulas for the derivative of order {m}:")
for kk in range(m, m + 5):
w = forward_weights(m, kk)
den = np.lcm.reduce([q.denominator for q in w])
print(f" {kk + 1} points: (1/({den} h^{m})) * {[int(q * den) for q in w]}")
print("central formulas:")
for r in (1, 2, 3):
w = central_weights(m, r)
print(f" {2 * r + 1} points:", np.round(w, 8))
# ---- plots
fig, ax = plt.subplots(1, 3, figsize=(15, 4.2))
xs = np.linspace(x[0], x[n], 500)
if SOURCE == "function":
ax[0].plot(xs, f(xs), "k", label="f")
ax[0].plot(xs, [forward_eval(t, X0, H, T, K) for t in xs], "r", label=f"forward, degree {K}")
ax[0].plot(xs, [backward_eval(t, x[n], H, T, K) for t in xs], "b--", label=f"backward, degree {K}")
ax[0].plot(x, y, "o", color="gray")
ax[0].legend(); ax[0].set_title("Newton-Gregory polynomials")
ks = np.arange(n + 1)
ax[1].semilogy(ks, [max(np.max(np.abs(c)), 1e-17) for c in T], "o-", label="clean")
ax[1].semilogy(ks, [max(np.max(np.abs(c)), 1e-17) for c in Tn], "o-", label="disturbed")
ax[1].semilogy(ks, 2.0 ** ks * NOISE, "k--", label="2^k noise")
ax[1].set_xlabel("k"); ax[1].set_title("largest |difference| in column k"); ax[1].legend()
if SOURCE == "function":
hs = 10.0 ** np.linspace(-12, -0.5, 200)
hr = 0.005 * max(abs(n * H), 1e-3)
wr = central_weights(m, 6)
ref = sum(wr[j + 6] * f(X0 + j * hr) for j in range(-6, 7)) / hr ** m
for kk in range(m, m + 4):
w = [float(q) for q in forward_weights(m, kk)]
err = [abs(sum(w[j] * f(X0 + j * h) for j in range(kk + 1)) / h ** m - ref) for h in hs]
ax[2].loglog(hs, err, label=f"forward, order {kk + 1 - m}")
for r in (1, 2):
if 2 * r + 1 >= m + 2:
w = central_weights(m, r)
err = [abs(sum(w[j + r] * f(X0 + j * h) for j in range(-r, r + 1)) / h ** m - ref) for h in hs]
ax[2].loglog(hs, err, "--", label=f"central, order {2 * r}")
ax[2].set_xlabel("h"); ax[2].set_ylabel("|error|"); ax[2].set_title(f"derivative of order {m} at x0"); ax[2].legend()
plt.tight_layout()
if SAVE:
plt.savefig("finite_differences.png", dpi=130)
plt.show()
if __name__ == "__main__":
main()
References: J. Meijering, A chronology of interpolation: from ancient astronomy to modern signal and image processing, Proc. IEEE 90 (2002) 319–342; H. H. Goldstine, A History of Numerical Analysis from the 16th through the 19th Century, Springer 1977; G. Boole, A Treatise on the Calculus of Finite Differences, 1860; F. B. Hildebrand, Introduction to Numerical Analysis, 2nd ed., McGraw-Hill 1974 (chapters 3 and 4); B. Fornberg, Generation of finite difference formulas on arbitrarily spaced grids, Math. Comp. 51 (1988) 699–706; N. J. Higham, Accuracy and Stability of Numerical Algorithms, 2nd ed., SIAM 2002; I. Newton, Principia, Book III, Lemma V, and Methodus differentialis (1711).
Ulam, Sacks and sunflower spirals, rows of any width and colorings by residue or factor count: see why primes line up on quadratic diagonals, and click a number to find the polynomial through it.
Replace the integrand by a polynomial through equally spaced points and integrate the polynomial: that is a Newton–Cotes rule. This app shows the trapezoid rule, Simpson's 1/3 rule and Simpson's 3/8 rule, each simple (one panel) or composite (many panels), drawn over the function. It reports the error, checks it against the error term, measures the order of convergence, and shows which polynomials each rule integrates exactly. A short course follows, written to fit two classes.
The rule on a function
Panels and their polynomials
Ready
f (integrand)polynomial of each panelnodes
RULE VALUE \(Q\)—
EXACT INTEGRAL \(I\)—
ERROR \(E=I-Q\)—
The error is E = I − Q, exact integral minus the rule, so a negative sign means the rule overestimates.
The numbers below are recomputed for the rule, version, n and function currently chosen.
Order of convergence
Error against n, the three rules
Absolute error of the composite rules for n = 6, 12, 24, … on the chosen function. On this log–log plot a straight line of slope −p means order p.
number of subintervals n
“order” is log₂ of the ratio between consecutive errors: it should approach 2 for the trapezoid and 4 for both Simpson rules for a generic smooth f (a lucky f can do better: 1/(1 + x²) on [0, 1] gives 6 for Simpson). Entries near rounding level (10⁻¹⁴) are left out.
Degree of precision
Which powers does each rule integrate exactly?
Error of the simple rules on [0, 1] for f = xᵏ. “exact” means below 10⁻¹⁴.
The trapezoid rule is exact up to degree 1, Simpson 1/3 and 3/8 up to degree 3. Simpson 1/3 gets one degree for free thanks to symmetry.
Closed Newton–Cotes weights
n = 1 to 10 subintervals
Weights cⱼ with ∫ ≈ h Σ cⱼ f(xⱼ), computed here by solving the moment equations. Only the first half is listed (the weights are symmetric). Rows in color have negative weights.
Σ|c|/n equals 1 when all weights are positive and grows when they are not: it measures how much rounding errors in f can be amplified. From n = 8 on (except n = 9) negative weights appear.
Course for two classes
Newton–Cotes rules, from the idea to the error terms
Five groups of short lessons. Groups 1 and 2 are the first class (idea, weights, trapezoid); groups 3 and 4 are the second (Simpson 1/3 and 3/8, practice); group 5 has the plan and exercises with answers. Every number quoted was measured with the code that runs in this page.
1 · The idea: integrate the interpolating polynomial
Interpolatory quadrature and the weights
To integrate \(f\) on \([a,b]\), choose nodes \(x_0,\dots,x_n\), let \(p_n\) be the polynomial of degree \(\le n\) through the points \((x_j,f(x_j))\), and take \(\int_a^b p_n\) as the answer. In Lagrange form \(p_n=\sum f(x_j)\ell_j\), so
The weights depend only on the nodes, never on \(f\). A closed Newton–Cotes rule takes equally spaced nodes \(x_j=a+jh\), \(h=(b-a)/n\), endpoints included. Substituting \(x=a+th\) gives \(w_j=h\,c_j\) with \(c_j=\int_0^n\prod_{k\ne j}\frac{t-k}{j-k}\,dt\), a number you compute once. The panel “Closed Newton–Cotes weights” lists them: \(n=1\) gives \(\tfrac12,\tfrac12\) (trapezoid), \(n=2\) gives \(\tfrac13,\tfrac43,\tfrac13\) (Simpson 1/3), \(n=3\) gives \(\tfrac38,\tfrac98,\tfrac98,\tfrac38\) (Simpson 3/8).
Connection with the previous app: this is exactly “integrate the Lagrange interpolant”. The error of the rule is the integral of the interpolation error, and that is how every error term below is obtained.
Degree of precision, and the free degree from symmetry
A rule has degree of precision \(d\) if it is exact for every polynomial of degree \(\le d\) and not for some polynomial of degree \(d+1\). Testing on monomials \(1,x,x^2,\dots\) is enough, by linearity. The panel “Degree of precision” does this on \([0,1]\).
An interpolatory rule with \(n+1\) nodes has \(d\ge n\), because \(p_n=f\) whenever \(\deg f\le n\). For even \(n\) there is one more degree: put \(m\) at the midpoint of \([a,b]\); the nodes are symmetric about \(m\) and so are the weights, so both \(\int (x-m)^{n+1}\) and the rule applied to it vanish (odd function, symmetric sum). Hence
In the app, choose \(x^3\) on \([0,1]\), Simpson 1/3, simple: the error is zero (below \(10^{-16}\)), even though the parabola through three points of a cubic is not the cubic. For \(x^4\) the simple errors are \(-1/120\approx-8.3\cdot10^{-3}\) (Simpson 1/3) and \(-1/270\approx-3.7\cdot10^{-3}\) (Simpson 3/8); they will fix the constants of the error terms.
2 · The trapezoid rule, simple and composite
The simple trapezoid rule
With \(n=1\) the interpolant is the chord, and its integral is the area of a trapezoid:
Choose “Trapezoid”, “Simple”, function \(\sin x\) on \([0,\pi]\): the chord is the \(x\)-axis, the rule gives \(0\), the integral is \(2\), the error is \(2\). A single panel is a crude tool; the point of the next lessons is to quantify how crude, and to cure it by cutting \([a,b]\) into many panels.
Error term of the simple trapezoid rule (proof)
Let \(f\in C^2[a,b]\). The interpolation error through \(a,b\) is \(f(x)-p_1(x)=\tfrac12 f''(\xi_x)(x-a)(x-b)\). Integrate:
The factor \((x-a)(x-b)\) is \(\le0\) on all of \([a,b]\): it does not change sign. So the mean value theorem for integrals pulls \(f''\) out, at some \(\xi\in(a,b)\):
Reading it: convex \(f\) (\(f''>0\)) gives \(E<0\), the chord lies above the graph and the rule overestimates. Example in the app: \(e^x\) on \([0,1]\), simple trapezoid. The rule gives \(1.8591\), \(E=-0.14086\); the bound is \(e/12=0.2265\); solving \(-\tfrac1{12}e^{\xi}=E\) gives \(\xi=0.525\), inside \((0,1)\), as the theorem promises. The “Error term” card recomputes all this for the case you choose.
The composite trapezoid rule and its error
Cut \([a,b]\) into \(n\) panels of width \(h=(b-a)/n\) and apply the simple rule on each. The interior nodes are counted twice and the ends once:
Each panel contributes \(-\tfrac{h^3}{12}f''(\xi_i)\). Summing, \(-\tfrac{h^3}{12}\sum_{i=1}^n f''(\xi_i)\). The average \(\frac1n\sum f''(\xi_i)\) lies between the min and the max of the continuous \(f''\), so by the intermediate value theorem it equals \(f''(\xi)\) for some \(\xi\), and with \(nh=b-a\):
\[E=-\frac{(b-a)\,h^2}{12}\,f''(\xi).\]
The error is of order 2: halving \(h\) divides it by 4. Check in the app with \(\sin x\) on \([0,\pi]\): for \(n=6,12,24,48\) the errors are \(4.59\cdot10^{-2}\), \(1.144\cdot10^{-2}\), \(2.857\cdot10^{-3}\), \(7.14\cdot10^{-4}\), and the convergence table shows order \(2.00\). On the log–log plot it is a straight line of slope \(-2\).
Why the bound is pessimistic: the Euler–Maclaurin expansion
The mean-value form hides the true behavior. For smooth \(f\) the error has an expansion in even powers of \(h\) (Euler–Maclaurin):
Only the endpoints appear. Two consequences. First, the leading term is \(-\tfrac{h^2}{12}(f'(b)-f'(a))\), which is the integral of \(f''\) in disguise, so the true error is usually a bit smaller than the bound with \(\max|f''|\). For \(\sin x\) on \([0,\pi]\) the leading term is \(h^2/6\), which is the observed error to three digits.
Second, if \(f\) is periodic (or just has matching odd derivatives at \(a\) and \(b\)) every term vanishes and the trapezoid rule converges faster than any power of \(h\). For \(e^{\cos x}\) on \([0,2\pi]\) the error is \(3.4\cdot10^{-2}\) for \(n=4\), \(2.8\cdot10^{-4}\) for \(n=6\), \(1.3\cdot10^{-6}\) for \(n=8\), \(6.5\cdot10^{-12}\) for \(n=12\): no straight line of slope \(-2\) here. This is why the trapezoid rule is the method of choice for periodic integrands, and it prepares the ground for Gaussian quadrature, the next topic.
Richardson: the trapezoid rule produces Simpson's rule
Since \(I-T_n=c_2h^2+c_4h^4+\cdots\) with the same constants for \(h\) and \(h/2\), the combination that kills the \(h^2\) term is
\[S=\frac{4\,T_{2n}-T_n}{3}.\]
Writing it out, the weights on the \(2n+1\) points are \(\tfrac13(1,4,2,4,\dots,4,1)\) times the finer step: this is the composite Simpson 1/3 rule. Numerically, for \(\sin x\) on \([0,\pi]\): \(T_6\) and \(T_{12}\) give \((4T_{12}-T_6)/3=2.0000526243\), identical to Simpson 1/3 with \(n=12\). Repeating the idea on the \(h^4\) term gives Romberg integration. The lesson: the trapezoid rule and Simpson's rule are one family, and the extra order of Simpson comes from cancelling the leading term of the Euler–Maclaurin series.
3 · Simpson's rules: 1/3 and 3/8
Simpson's 1/3 rule: derivation of the weights
Take three nodes \(m-h,\,m,\,m+h\) (with \(m=\tfrac{a+b}2\), \(h=\tfrac{b-a}2\)) and the parabola through them. Instead of integrating Lagrange polynomials, use the degree of precision: the rule \(w_0f_0+w_1f_1+w_2f_2\) must be exact on \(1\) and \((x-m)^2\), and by symmetry \(w_0=w_2\). Then
On \(\sin x\) over \([0,\pi]\) the simple rule gives \(2.0944\) (error \(-0.0944\)) against \(0\) for the simple trapezoid: three points and a huge gain. Geometrically, we integrate a parabola through the midpoint instead of a chord.
Error term of Simpson 1/3 (proof)
The Lagrange error gives \(f'''\), but the rule has degree 3, so we expect \(f^{(4)}\). The trick: let \(q\) be the cubic with \(q=f\) at \(m-h,m,m+h\) and \(q'(m)=f'(m)\) (Hermite). The rule only sees the three values, so \(Q(f)=Q(q)=\int q\), because Simpson is exact on cubics. Hence \(E=\int(f-q)\), and the Hermite error is
Check against the \(x^4\) test: \(f^{(4)}=24\), \(h=\tfrac12\): \(-\tfrac{1}{32\cdot90}\cdot24=-\tfrac1{120}\), the value in the exactness table. For \(e^x\) on \([0,1]\) the bound is \(\tfrac{e}{90\cdot32}=9.4\cdot10^{-4}\) and the actual error is \(-5.8\cdot10^{-4}\).
Composite Simpson 1/3: order 4
Take \(n\) even and apply the simple rule on each of the \(n/2\) pairs of subintervals (each of width \(2h\)):
Order 4: halving \(h\) divides the error by 16. In the app, \(e^{-x^2}\) on \([0,2]\) with \(n=6,12\): \(4.98\cdot10^{-5}\) then \(3.14\cdot10^{-6}\), ratio \(15.9\). And the cost comparison is striking: for \(\sin x\) on \([0,\pi]\) with \(n=12\), trapezoid and Simpson use the same 13 points, but the errors are \(1.1\cdot10^{-2}\) and \(5.3\cdot10^{-5}\), a factor of 200.
Simpson 3/8: weights and error term
Four nodes, spacing \(h=\tfrac{b-a}3\), cubic interpolant. Symmetry gives \(w_0=w_3\), \(w_1=w_2\). With \(m\) the midpoint the nodes are \(m\pm\tfrac{3h}2,\ m\pm\tfrac h2\). Exactness on \(1\) and \((x-m)^2\):
Its degree of precision is 3 (not 4: the symmetry bonus applies only to even \(n\)), so \(E=K\,h^5f^{(4)}(\xi)\) with a constant of sign \(-\) (the Peano kernel of the rule has constant sign, a fact we quote). The constant is fixed by the test \(f=x^4\): on \([0,1]\) the table gives \(E=-\tfrac1{270}\), with \(h=\tfrac13\), \(f^{(4)}=24\), so \(K\,\tfrac1{243}\cdot24=-\tfrac1{270}\), which gives
\[E=-\frac{3h^5}{80}\,f^{(4)}(\xi).\]
The composite rule needs \(n\) a multiple of 3, has coefficients \(1,3,3,2,3,3,2,\dots,3,3,1\), and error \(-\tfrac{(b-a)h^4}{80}f^{(4)}(\xi)\) (there are \(n/3\) groups, each contributing \(-\tfrac{3h^5}{80}f^{(4)}\)).
1/3 or 3/8? Comparing at equal cost
Both rules are of order 4; the constants of the composite errors are \(\tfrac1{180}\) and \(\tfrac1{80}\). At the same \(n\) (hence the same \(h\) and the same number of evaluations) the 3/8 error is \(\tfrac{180}{80}=2.25\) times larger. Observed with \(\sin x\) on \([0,\pi]\), \(n=12\): \(5.26\cdot10^{-5}\) for 1/3 against \(1.19\cdot10^{-4}\) for 3/8, ratio \(2.27\).
So when \(n\) is even, use 1/3. The 3/8 rule earns its keep when \(n\) is odd: take \(n-3\) (even) subintervals with Simpson 1/3 and the last three with Simpson 3/8; the combined rule has the same order. Example: \(n=9\) is one 3/8 plus three 1/3 panels. The two rules are also the first members of a pattern: Boole's rule (\(n=4\), degree 5) is the next with even \(n\).
4 · In practice: how many points, rough functions, bigger rules
Choosing n from the error bound
To guarantee \(|E|\le\varepsilon\) solve the bound for \(n\). Take \(\int_0^\pi\sin x\,dx\) with \(\varepsilon=10^{-8}\) and \(\max|f''|=\max|f^{(4)}|=1\):
Direct experiments show how pessimistic the bound is: the trapezoid rule reaches \(10^{-8}\) with about \(12\,830\) subintervals (the Euler–Maclaurin term \(h^2/6\) predicts \(\pi/\sqrt{6\cdot10^{-8}}\approx12\,826\)) and Simpson with \(102\). The ratio \(16\,075/114\) already says why Simpson is the default for smooth integrands. In practice one also does the reverse: double \(n\) until two successive values agree to the tolerance.
Rough functions: when f'' or f⁗ does not exist
The error terms assume \(f\in C^2\) or \(C^4\). Test the app on two functions that violate this.
\(\sqrt x\) on \([0,1]\): \(f''\) blows up at 0, so the bound is infinite. Measured orders: \(1.46\) to \(1.49\) for the trapezoid, \(1.50\) for both Simpson rules. The singularity \(x^{1/2}\) at the end limits every rule to order \(3/2\), and Simpson's extra accuracy is wasted. Remedy: substitute \(x=t^2\) to remove the singularity, or split the interval.
\(|x-1/\pi|\) on \([0,1]\): a corner inside the interval. The error is irregular because it depends on where the corner falls relative to the nodes (a corner exactly on a node would give a deceptively exact answer, which is why the corner is placed at the irrational \(1/\pi\)). The convergence table shows orders that jump around and can even turn negative. The lesson: a high-order rule needs smoothness on the whole interval; put a node at every kink, i.e. integrate piece by piece.
Order is a property of the pair (rule, function)
The orders 2 and 4 are what you get for a generic smooth \(f\); special functions can do better. Take \(1/(1+x^2)\) on \([0,1]\), whose integral is \(\pi/4\). The trapezoid rule has order \(2.00\), as expected (\(f'(1)=-\tfrac12\ne0\)). But Simpson 1/3 and 3/8 both show order \(6.0\): errors \(2.2\cdot10^{-7}\), \(3.3\cdot10^{-9}\), \(5.2\cdot10^{-11}\) for \(n=6,12,24\).
The reason is in the Euler–Maclaurin series for Simpson, whose \(h^4\) coefficient is proportional to \(f'''(b)-f'''(a)\). Here \(f'''(x)=-24x(1-x^2)/(1+x^2)^4\) vanishes at both \(0\) and \(1\), so the \(h^4\) term is absent and \(h^6\) leads. The error bound with \(f^{(4)}\) is still true, just far from sharp. Practical consequence: estimate the order from the data (the convergence table), do not trust the textbook number blindly. As a bonus, \(4\,S_6\) with this integrand is \(3.14159178\), six correct digits of \(\pi\) from seven function values.
Bigger Newton–Cotes rules and negative weights
Why stop at \(n=3\) panels? Because higher closed rules inherit the weakness of equally spaced interpolation (the Runge phenomenon of the previous app). The weight table shows it. For \(n=6\) (Boole's neighbor, degree 7) all weights are positive. From \(n=8\) the weights change sign:
and for \(n=10\) the largest weight is \(7.14\) and the quantity \(\sum|c_j|/n\) has grown from \(1\) to \(3.06\) (it is \(1.45\) already at \(n=8\)). Since \(\sum c_j=n\) always, large positive and negative weights cancel, and any rounding error in \(f\) is amplified by that factor: the rule becomes unstable. The stable alternative is what we did: keep a low-degree rule (trapezoid, Simpson) and refine \(h\). The only way to get high degree without losing stability is to give up equally spaced nodes, which is the subject of the next class, Gaussian quadrature.
5 · Plan of the two classes and exercises
Plan of the two classes
Class 1: from interpolation to the trapezoid rule.
Lessons 1.1–1.2: interpolatory quadrature, weights \(w_j=\int\ell_j\), degree of precision, symmetry bonus (open the weights and exactness panels).
Lessons 2.1–2.3: simple trapezoid, error proof with the mean value theorem for integrals, composite rule and order 2. In the app: sin, trapezoid, simple then composite, read the order in the convergence table.
If time remains, 2.4 (Euler–Maclaurin and periodic functions) as a remark.
Class 2: Simpson and practice.
Lessons 3.1–3.3: Simpson 1/3, the Hermite proof of \(-h^5f^{(4)}/90\), composite order 4. Lesson 2.5 (Richardson) connects it to the first class.
Lessons 3.4–3.5: Simpson 3/8, comparison at equal cost, the odd-\(n\) combination.
Lessons 4.1–4.4 as discussion: choosing \(n\), rough functions (\(\sqrt x\), the corner), the lucky \(1/(1+x^2)\), negative weights as the bridge to Gaussian quadrature.
Exercises, with answers
Derive the weights \(\tfrac38,\tfrac98,\tfrac98,\tfrac38\) by solving the moment equations for \(1,x,x^2,x^3\) on \([0,3]\). Answer: the system is the one in the weights panel; the solution is \(c=(\tfrac38,\tfrac98,\tfrac98,\tfrac38)\), in units of \(h\), summing to \(3\).
For \(\int_0^1e^x\,dx\) with the composite trapezoid and \(n=10\), give the bound and compare with the app. Answer: bound \(e/1200=2.27\cdot10^{-3}\); actual \(-1.43\cdot10^{-3}\). With Simpson 1/3, \(n=10\): bound \(e/(180\cdot10^4)=1.51\cdot10^{-6}\); actual \(-9.5\cdot10^{-7}\).
Show that Simpson 1/3 integrates \(x^3\) exactly on any \([a,b]\). Answer: write \(x^3=(x-m)^3+3m(x-m)^2+3m^2(x-m)+m^3\); the odd powers of \(x-m\) cancel by symmetry and the rest has degree \(\le2\).
Integrate \(e^x\) on \([0,1]\) with \(n=9\) using Simpson 1/3 on the first six subintervals and Simpson 3/8 on the last three, then the other way round. Answer: errors \(-2.27\cdot10^{-6}\) and \(-1.87\cdot10^{-6}\); the pure composite 3/8 with \(n=9\) gives \(-3.26\cdot10^{-6}\). Placing the (larger-error) 3/8 block where \(f^{(4)}\) is smaller, near \(x=0\), is better.
Explain why the trapezoid rule with \(n=8\) on \(e^{\cos x}\), \([0,2\pi]\) is accurate to about \(10^{-6}\) while on \(e^x\), \([0,1]\) it needs \(n\approx 380\) (trapezoid) for the same. Answer: lesson 2.4, periodicity.
References: R. L. Burden and J. D. Faires, Numerical Analysis, chapter 4; E. Süli and D. Mayers, An Introduction to Numerical Analysis, chapter 7; K. Atkinson, An Introduction to Numerical Analysis, chapter 5; P. J. Davis and P. Rabinowitz, Methods of Numerical Integration, Academic Press; J. Stoer and R. Bulirsch, Introduction to Numerical Analysis (Euler–Maclaurin, Romberg).
Numerical calculus · Newton–Cotes and finite differences
Typography along a curve
To set text on a curve you need two numbers for every letter: how far along the curve it sits (an integral, the arc length) and which way it points (a derivative, the tangent). Choose a curve, a Newton–Cotes rule and a finite-difference formula, and watch the letters drift away from their exact positions (grey) as the numerical errors grow.
Workbench
Letters on a curve
numerical placementexact placement
Arc length (relative error)—
Largest position error—
Largest angle error—
Panels used—
Convergence
Arc length: the order of the rule
Relative error of the length of the whole curve against \(n\), computed with the exact speed so that only the quadrature error shows. The dashed guides have slopes \(-2\), \(-4\) and \(-6\). The red line marks your current \(n\).
Log–log scale; errors below \(10^{-16}\) are at the floor of double precision.
Differentiation
Tangent and curvature: truncation against roundoff
Error of the tangent angle (or of the curvature) against the step \(h\). Without noise you see the truncation errors \(h\), \(h^2\), \(h^4\); with noise the curves turn upward at small \(h\): a smaller step is not always better.
Root-mean-square error over 24 sample points; the red dashed line marks your current \(h\). On the ellipse the central formulas are exact up to roundoff (see Exercise 1).
At a glance
The formulas
Arc length, tangent angle and curvature of \(\gamma(t)=(x(t),y(t))\):
Placing glyph \(i\) of width \(w_i\) (the glyph is rotated by the tangent at its midpoint):
\[s_i=s_0+\sum_{j\lt i}w_j+\tfrac12w_i,\qquad S(t_i)=s_i\ \ (\text{bisection}),\qquad \text{glyph } i \text{ at } \gamma(t_i),\ \text{rotated by }\theta(t_i)\]
Errors of the composite rules, with step \(h=(b-a)/n\) and \(g\) the speed:
\[\text{sagitta of a straight glyph of width }w:\quad \sigma\approx\frac{w^2\kappa}{8}\]
Caveats
Where the textbook orders fail
Periodic integrands. When the odd derivatives of the speed vanish at both ends (a closed smooth curve, or the half-period of the sine wave), the trapezoid rule is spectrally accurate by the Euler–Maclaurin formula and beats Simpson and Boole. Try the ellipse with the trapezoid rule and compare the total length (excellent) with the letter positions (not: partial lengths \(s(t)\) are not integrals over a full period). On an open arc such as the spiral the textbook orders 2, 4, 6 return.
Cusps and zero speed. At the cusp of the semicubical parabola the speed \(\lVert\gamma'\rVert=|t|\sqrt{4+9t^2}\) has a kink, so the integrand is not smooth and all three rules drop to order 2 unless a node falls exactly on the kink. The tangent direction also flips there, so letters near a cusp turn abruptly. The cardioid has the same kind of cusp at its two ends.
The second derivative (optional). The curvature \(\kappa=(x'y''-y'x'')/\lVert\gamma'\rVert^3\) says by how much a straight glyph of width \(w\) sticks out, about \(w^2\kappa/8\). The sagitta correction uses it and the curvature comb draws it. But second differences lose half of the digits (error \(\varepsilon/h^2\), best \(h\sim\varepsilon^{1/4}\)): turn the noise on, switch the lower plot to curvature and watch the smallest error climb.
Real typesetting engines also kern pairs and avoid overlaps on tight curves. Here every glyph is a rigid box centred on the curve, the simplest model, chosen so that the numerical errors are what you see.
Exercises
Questions
Try them before opening the answers.
1. Why is the central-difference tangent angle on the ellipse exact to machine precision?
The ellipse is the affine image \((1.6\cos t,\ \sin t)\) of a circle. The chord from \(t-h\) to \(t+h\) is \((-3.2\sin t\sin h,\ 2\cos t\sin h)\), proportional to \((-1.6\sin t,\ \cos t)\): exactly the tangent direction. What the plot shows is roundoff only.
2. Why must \(n\) be even for Simpson and a multiple of 4 for Boole? What does the app do?
Simpson pairs panels (weights \(1,4,2,\dots,4,1\)) and Boole groups four (weights \(7,32,12,32,14,\dots,7\)). The app rounds \(n\) up to the next admissible value and shows it as "Panels used".
3. Balance truncation and roundoff to find the best step for the central first difference when the data have noise \(\varepsilon\). Do the same for the second difference.
First difference: error \(\approx h^2M/6+\varepsilon/h\) with \(M=\lVert\gamma'''\rVert\); minimising gives \(h=(3\varepsilon/M)^{1/3}\) and error of order \(\varepsilon^{2/3}\). Second difference: \(h^2M_4/12+4\varepsilon/h^2\), so \(h=(48\varepsilon/M_4)^{1/4}\) and error of order \(\varepsilon^{1/2}\).
4. For a glyph of width \(w\) on a circle of radius \(R\), compare the direction of the chord joining the ends of the glyph with the tangent at its start and at its midpoint.
The chord has exactly the direction of the tangent at the midpoint, while the tangent at the start differs from it by \(w/(2R)\) radians. That is why glyph \(i\) is rotated by \(\theta(t_i)\) at the arc-length midpoint \(s_i\).
Newton–Cotes fixes the nodes and chooses the weights. Gaussian quadrature chooses both, and the best nodes turn out to be the roots of orthogonal polynomials: with n nodes the rule integrates exactly every polynomial of degree up to 2n − 1. This app lets you pick the family (Legendre, Chebyshev, Laguerre, Hermite), the number of nodes and a test function; it draws the polynomials and their roots, lists nodes and weights, checks the error term, compares with Simpson's rule, and verifies the degree of precision. A short course follows, written to fit two classes.
The rule on a function
Nodes, weights and the interpolating polynomial
Ready
f (integrand)polynomial through the nodesweight w(x)nodes
RULE VALUE \(Q\)—
EXACT INTEGRAL \(I\)—
ERROR \(E=I-Q\)—
The rule approximates the integral of f(x)·w(x) over the interval of the family; the plot shows f itself, not f·w. E = I − Q.
In one glance
The four classical families
Legendre: weight 1 on [−1, 1]. Any smooth integrand.
\[\text{degree of precision }2n-1,\qquad E=\frac{f^{(2n)}(\xi)}{(2n)!}\int w(x)\,\tilde p_n(x)^2\,dx\quad(\tilde p_n\text{ monic})\]
Orthogonal polynomials
The polynomials and their roots
The orthonormal polynomials pₖ/√hₖ of the chosen family, for the last degrees up to n. The roots of pₙ (dots) are the nodes of the rule; the roots of pₙ₋₁ (small dots) fall strictly between them.
The table is the Gram matrix ⟨pⱼ, pₖ⟩ = ∫ pⱼ pₖ w computed numerically: zeros off the diagonal (orthogonality) and the numbers hₖ = ⟨pₖ, pₖ⟩ on the diagonal.
Error term
What the error formula says about this case
Recomputed for the family, n and function currently chosen. The constant is hₙ/(kₙ²(2n)!), where kₙ is the leading coefficient of pₙ.
Convergence
Error against the number of nodes
Absolute error of the Gauss rule for n = 1, …, 24 (log scale). For Legendre the dashed line is composite Simpson's rule using the same number of function evaluations (an odd number).
number of nodes n (function evaluations)
For analytic f the error falls faster than any straight line on this plot (it bends down): geometric or better convergence. For functions with a kink or a singularity (|x|, √x, log x) it falls only like a power of n. The floor between 10⁻¹⁶ and 10⁻¹⁴ is rounding.
Nodes and weights
The rule for the chosen n
Nodes xⱼ (roots of pₙ) and weights wⱼ, computed here with the Christoffel formula. All weights are positive.
Positive weights make the rule stable: no cancellation, unlike the high-order Newton–Cotes rules of the previous app.
Degree of precision
Exact up to degree 2n − 1, and not beyond
For k = 0, …, 2n + 1: the exact value of ∫ xᵏ w, the rule's value, and the error relative to Σ wⱼ|xⱼ|ᵏ (so that zero moments can be compared). Shown for n ≤ 10.
The first failure is at k = 2n, one degree above 2n − 1 (marked), as the theory says. Above n = 10 the monomial moments of Laguerre and Hermite are too ill-conditioned for double precision to show this cleanly.
Course for two classes
Orthogonal polynomials and Gaussian quadrature
Five groups of short lessons. The first class is groups 1 and 2 (why the nodes can be chosen, orthogonal polynomials and their roots) plus lessons 3.1 and 4.1; the second class is the rest (error term, Chebyshev, Laguerre, Hermite, practice). Every number quoted was measured with the code that runs in this page.
1 · Choosing the nodes too
Counting parameters: 2n unknowns, degree 2n − 1
A rule \(Q(f)=\sum_{j=1}^n w_jf(x_j)\) for \(\int f\,w\) has \(2n\) free numbers: \(n\) weights and \(n\) nodes. Newton–Cotes fixes the nodes and uses only the weights, so it reaches degree \(n-1\) (or \(n\)). If the nodes are free too, we can ask for exactness on \(1,x,\dots,x^{2n-1}\), which is \(2n\) equations:
n = 1 (Legendre, \([-1,1]\)): \(w_1=2\) and \(w_1x_1=0\), so \(x_1=0\): the midpoint rule, one evaluation, degree 1. n = 2: by symmetry \(w_1=w_2=1\), and \(2x^2=\mu_2=\tfrac23\) gives \(x=\pm\tfrac1{\sqrt3}=\pm0.5774\). Two evaluations and degree 3, as many as Simpson's rule uses with three. The system is nonlinear in the nodes, which is why the structure of the next lessons is needed.
No rule can do better than \(2n-1\). Take \(q(x)=\prod_j(x-x_j)^2\), of degree \(2n\): \(q\ge0\), so \(\int q\,w>0\), while \(Q(q)=0\).
The key theorem: nodes must be roots of an orthogonal polynomial
Theorem. Let \(x_1,\dots,x_n\) be distinct, \(\omega(x)=\prod(x-x_j)\), and let the weights be those of interpolatory quadrature (\(w_j=\int\ell_jw\)). The rule has degree of precision \(\ge2n-1\) if and only if \(\int\omega\,q\,w=0\) for every polynomial \(q\) of degree \(\le n-1\).
Proof. (⇐) Let \(\deg f\le2n-1\). Divide: \(f=q\,\omega+r\) with \(\deg q,\deg r\le n-1\). Then \(\int fw=\int q\omega w+\int rw=\int rw\) by orthogonality. On the other side, \(f(x_j)=r(x_j)\) because \(\omega(x_j)=0\), so \(Q(f)=Q(r)=\int rw\), the last step because the rule is interpolatory and \(\deg r\le n-1\). (⇒) Take \(f=q\,\omega\) with \(\deg q\le n-1\), so \(\deg f\le2n-1\): exactness gives \(\int q\omega w=Q(q\omega)=0\) since \(\omega\) vanishes at the nodes. ∎
So the problem of choosing the nodes becomes: find the monic polynomial \(\omega\) of degree \(n\) orthogonal to all lower degrees, and take its roots. That polynomial is \(p_n\), the \(n\)-th orthogonal polynomial for the weight \(w\).
2 · Orthogonal polynomials and their roots
Inner product, Gram–Schmidt, and the four families
For a weight \(w>0\) on an interval (with finite moments) put \(\langle f,g\rangle=\int fg\,w\). Applying Gram–Schmidt to \(1,x,x^2,\dots\) gives a unique sequence of monic polynomials \(p_k\), \(\deg p_k=k\), with \(\langle p_j,p_k\rangle=0\) for \(j\ne k\). Equivalently, \(p_k\perp\) all polynomials of degree \(\lt k\) (they span the same space as \(p_0,\dots,p_{k-1}\)).
The Gram table in the app checks this numerically: for Legendre the diagonal reads \(2,\ 0.6667,\ 0.4,\ 0.2857,\ 0.2222\), i.e. \(2/(2k+1)\), and every off-diagonal entry is zero to rounding.
The three-term recurrence (proof)
Orthogonal polynomials satisfy, for monic \(p_k\),
Proof. \(xp_k-p_{k+1}\) has degree \(\le k\), so \(xp_k=p_{k+1}+\sum_{j\le k}c_jp_j\) with \(c_j=\langle xp_k,p_j\rangle/h_j\). For \(j\lt k-1\): \(\langle xp_k,p_j\rangle=\langle p_k,xp_j\rangle=0\), because \(\deg(xp_j)=j+1\lt k\). Only \(j=k\) and \(j=k-1\) survive. ∎
The coefficients are explicit: \(\alpha_k=\langle xp_k,p_k\rangle/h_k\) and \(\beta_k=h_k/h_{k-1}\). In the usual normalizations the recurrences are, for Legendre \((k+1)P_{k+1}=(2k+1)xP_k-kP_{k-1}\); Chebyshev \(T_{k+1}=2xT_k-T_{k-1}\); Laguerre \((k+1)L_{k+1}=(2k+1-x)L_k-kL_{k-1}\); Hermite \(H_{k+1}=2xH_k-2kH_{k-1}\). The code in this page evaluates all four with one loop; the recurrence is also the best way to compute \(p_n(x)\) (it is cheap and stable).
The roots are real, simple, and inside the interval (proof)
Claim. \(p_n\) has \(n\) distinct real roots, all in the open interval \((a,b)\).
Proof. Let \(t_1,\dots,t_m\) be the points of \((a,b)\) where \(p_n\) changes sign (roots of odd multiplicity); \(m\le n\). Put \(q=\prod_{i\le m}(x-t_i)\). Then \(p_nq\) does not change sign on \((a,b)\), so \(\int p_nq\,w\ne0\). If \(m\lt n\), orthogonality of \(p_n\) to polynomials of degree \(\lt n\) would give \(0\): contradiction. Hence \(m=n\): all \(n\) roots are real, simple and in \((a,b)\). ∎
This is what makes the rule usable: the nodes are real, inside the domain of integration, never repeated. Look at the plot: for Legendre with \(n=4\) the roots are at \(\pm0.3400\) and \(\pm0.8611\), and the nodes crowd toward the ends of \([-1,1]\) as \(n\) grows (try \(n=30\)); for Laguerre and Hermite the roots spread over \([0,\approx4n]\) and \([-2\sqrt n,2\sqrt n]\).
Interlacing of roots, and how the code finds them
Between two consecutive roots of \(p_n\) there is exactly one root of \(p_{n-1}\), and \(p_{n-1}\) has none outside \([x_1,x_n]\): the roots interlace. The plot shows it: the small dots (roots of \(p_{n-1}\)) sit in the gaps between the big dots (roots of \(p_n\)). It follows from the Christoffel–Darboux identity, or from the recurrence by induction: \(p_{n+1}(t_i)=-\beta_np_{n-1}(t_i)\) at a root \(t_i\) of \(p_n\), so \(p_{n+1}\) and \(p_{n-1}\) have opposite signs at the roots of \(p_n\), which forces a sign change of \(p_{n+1}\) in each gap.
Practical use: to find all roots of \(p_n\) one needs a bracket for each. The gaps between the roots of \(p_{n-1}\), plus the two end intervals, are \(n\) disjoint brackets each containing exactly one root. The code computes the rows of roots for \(p_1,p_2,\dots,p_n\) successively and in each gap runs Newton's method kept inside the bracket (bisection when a Newton step escapes). No initial guess is ever needed and no root can be missed or found twice, which is the usual failure of general polynomial root-finders at degree 20 or 30.
With the nodes fixed, the weights are those of interpolatory quadrature, \(w_j=\int\ell_j\,w\), where \(\ell_j\) is the Lagrange basis polynomial. Two facts:
Positivity. \(\ell_j^2\) has degree \(2n-2\le2n-1\), so the rule integrates it exactly: \(0\lt \int\ell_j^2w=\sum_kw_k\ell_j(x_k)^2=w_j\), since \(\ell_j(x_k)=\delta_{jk}\). Every weight is positive, and \(\sum w_j=\int w=\mu_0\).
Christoffel's formula. \(w_j=\dfrac{k_n\,h_{n-1}}{k_{n-1}\,p_n'(x_j)\,p_{n-1}(x_j)}\), with \(k_m\) the leading coefficient of \(p_m\). This is what the code uses, with \(p_n'\) obtained from the recurrence differentiated. Check in the app (Nodes and weights card): Legendre \(n=3\) gives weights \(\tfrac59,\tfrac89,\tfrac59\) at \(0,\pm0.7746\); Chebyshev gives \(\pi/n\) at every node.
Positive weights are the deep difference with high-order Newton–Cotes (whose weights, from \(n=8\), change sign): no cancellation, so the rule is numerically stable for every \(n\).
The error term (proof)
Let \(f\in C^{2n}\) and let \(q\) be the Hermite interpolant: degree \(\le2n-1\), \(q(x_j)=f(x_j)\), \(q'(x_j)=f'(x_j)\). The rule only sees the values, so \(Q(f)=Q(q)=\int qw\) (exact, degree \(2n-1\)). Thus \(E=\int(f-q)\,w\), and the Hermite error formula is
Closed forms: Legendre \(\frac{(b-a)^{2n+1}(n!)^4}{(2n+1)((2n)!)^3}\); Chebyshev \(\frac{2\pi}{2^{2n}(2n)!}\); Laguerre \(\frac{(n!)^2}{(2n)!}\); Hermite \(\frac{n!\sqrt\pi}{2^n(2n)!}\). For \(n=2\), Legendre: \(E=f^{(4)}(\xi)/135\) on \([-1,1]\). Check with \(f=x^4\) (\(f^{(4)}=24\)): the rule gives \(2\cdot\tfrac19=\tfrac29\), the integral is \(\tfrac25\), the difference \(\tfrac8{45}=\tfrac{24}{135}\). ✓. With \(e^x\): the constant for \(n=2\) is \(1/135\), the bound \(e/135=2.0\cdot10^{-2}\), the actual error \(7.7\cdot10^{-3}\); for \(n=3\) the constant is \(6.35\cdot10^{-5}\) and the error \(6.5\cdot10^{-5}\); the app prints both, and the implied value \(f^{(6)}(\xi)=1.03\in[1,e]\).
Convergence, and Gauss against Simpson
Because the weights are positive and \(\sum w_j=\mu_0\), for continuous \(f\) on a finite interval and any polynomial \(r\) of degree \(\le2n-1\): \(|I-Q|\le|\int(f-r)w|+|Q(r-f)|\le2\mu_0\max|f-r|\). Choosing the best \(r\) (Weierstrass), Gauss quadrature converges for every continuous \(f\), and as fast as the best polynomial approximation of degree \(2n-1\): geometrically for analytic functions. Newton–Cotes cannot promise this (negative weights).
Numbers (Legendre, \(e^x\) on \([-1,1]\), same number of function evaluations): 3 points: Gauss \(6.5\cdot10^{-5}\), Simpson \(1.2\cdot10^{-2}\); 5 points: Gauss \(8.2\cdot10^{-10}\), Simpson \(7.9\cdot10^{-4}\); with \(n=6\) Gauss is already at \(1.6\cdot10^{-12}\), while Simpson with 11 points is at \(2.1\cdot10^{-5}\). Another instance from the Newton–Cotes app: \(\int_0^\pi\sin x\,dx\) with three evaluations: Simpson \(2.0944\) (error \(-0.094\)), Gauss–Legendre \(2.00139\) (error \(-1.4\cdot10^{-3}\)). Open the convergence card: the Gauss curve bends down faster than any straight line, while Simpson's decays like \(n^{-4}\).
Computing nodes and weights in practice
Three ways to get the rule for a given \(n\). (1) Tables, fine for \(n\le5\) by hand. (2) The method of this page: roots of \(p_n\) by safeguarded Newton inside the interlacing brackets, weights by Christoffel's formula. (3) Golub–Welsch (1969): the recurrence says \(xp_k=p_{k+1}+\alpha_kp_k+\beta_kp_{k-1}\), i.e. the nodes are the eigenvalues of the symmetric tridiagonal Jacobi matrix with diagonal \(\alpha_k\) and off-diagonal \(\sqrt{\beta_k}\), and \(w_j=\mu_0\,v_{j,1}^2\) where \(v_{j,1}\) is the first component of the normalized eigenvector. This is the standard in libraries.
Double precision limits. The nodes and weights are accurate to near rounding for all \(n\le30\) here (Legendre \(n=20\): \(\sum w_j=2\) to \(4\cdot10^{-14}\)). The weights of Laguerre and Hermite decay super-exponentially: for \(n=10\) the smallest Laguerre weight is \(9.9\cdot10^{-13}\) at the node \(29.9\), and the smallest Hermite weight is \(7.6\cdot10^{-6}\). That is why the “degree of precision” table is shown only for \(n\le10\): the monomials \(x^k\), \(k\) near \(2n\), are a poor basis and the test itself, not the rule, loses digits.
4 · The four classical cases
Gauss–Legendre
Weight \(1\) on \([-1,1]\); roots of the Legendre polynomial \(P_n\). Explicit small cases:
For \([a,b]\): \(x=\frac{b-a}2t+\frac{a+b}2\), \(\int_a^bf=\frac{b-a}2\sum w_jf(x(t_j))\), and the error constant becomes \(\frac{(b-a)^{2n+1}(n!)^4}{(2n+1)((2n)!)^3}\) (for \(n=2\): \((b-a)^5/4320\)). This is the all-purpose rule for smooth integrands on a finite interval. In the app, Legendre, \(e^x\), \(n=1,2,3,4,5\): errors \(3.5\cdot10^{-1}\), \(7.7\cdot10^{-3}\), \(6.5\cdot10^{-5}\), \(2.9\cdot10^{-7}\), \(8.2\cdot10^{-10}\): each node multiplies the accuracy by a factor between \(45\) and \(360\). The test function \(x^6\) is exact for \(n\ge4\) (degree \(7\ge6\)) and has error \(4.6\cdot10^{-2}\) for \(n=3\), which equals \(720\cdot6.35\cdot10^{-5}\), exactly the error term since \(f^{(6)}=720\).
Gauss–Chebyshev
Weight \((1-x^2)^{-1/2}\) on \([-1,1]\). Here the roots of \(T_n\) are explicit, because \(T_n(\cos\theta)=\cos n\theta\):
Why equal weights: with \(x=\cos\theta\), \(\int_{-1}^1\frac{f(x)}{\sqrt{1-x^2}}dx=\int_0^\pi f(\cos\theta)\,d\theta\), and the rule is the midpoint rule in \(\theta\) for the even, periodic function \(f(\cos\theta)\), the case where the trapezoid/midpoint rules are spectrally accurate (Euler–Maclaurin lesson of the Newton–Cotes app). Use it for integrals with the factor \(1/\sqrt{1-x^2}\), which makes endpoint singularities harmless. Example in the app: \(\int_{-1}^1\frac{e^x}{\sqrt{1-x^2}}dx=\pi I_0(1)=3.97746326\); \(n=4\) gives \(3.97746264\), error \(6.3\cdot10^{-7}\), below the bound \(6.09\cdot10^{-7}\cdot e=1.7\cdot10^{-6}\); for \(n=2\): nodes \(\pm0.7071\), value \(\pi\cosh0.7071=3.9603\), error \(1.7\cdot10^{-2}\).
Gauss–Laguerre
Weight \(e^{-x}\) on \([0,\infty)\): \(\int_0^\infty e^{-x}f(x)\,dx\approx\sum w_jf(x_j)\), \(E=\frac{(n!)^2}{(2n)!}f^{(2n)}(\xi)\). For \(n=2\): nodes \(2\mp\sqrt2=0.5858,\ 3.4142\), weights \(\frac{2\pm\sqrt2}4=0.8536,\ 0.1464\). The rule has degree 3: \(\int_0^\infty x^3e^{-x}=6\) is reproduced (\(5.99999\ldots\)) while for \(x^4\) it gives \(20\) instead of \(24\). The test function \(x^5\) is exact for \(n\ge3\).
Use it when the integrand decays like \(e^{-x}\) times something smooth. To integrate \(\int_0^\infty g\) take \(f=ge^{x}\), which is only sensible if \(g\) already decays at least like \(e^{-x}\). In the app: \(\int_0^\infty e^{-x}\sin x\,dx=\tfrac12\): the error oscillates in sign and falls to \(-1.9\cdot10^{-8}\) for \(n=12\) and \(1.5\cdot10^{-11}\) for \(n=16\). With a function that has a singularity at the endpoint, \(\sqrt x\) (exact \(\sqrt\pi/2\)) or \(\ln x\) (exact \(-\gamma\)), the convergence is only algebraic: for \(\sqrt x\) the error is \(1.3\cdot10^{-2},\ 4.6\cdot10^{-3},\ 1.6\cdot10^{-3}\) for \(n=4,8,16\), a factor \(2^{3/2}\) per doubling.
Gauss–Hermite
Weight \(e^{-x^2}\) on \((-\infty,\infty)\), \(E=\frac{n!\sqrt\pi}{2^n(2n)!}f^{(2n)}(\xi)\). Small cases:
The nodes are symmetric, so odd functions are integrated exactly for any \(n\). With \(n=2\): \(\int x^2e^{-x^2}=\tfrac{\sqrt\pi}2\) is exact (degree 3), \(x^4\) gives \(0.4431\) against \(\tfrac{3\sqrt\pi}4=1.3293\). Test \(x^6\) (exact \(\tfrac{15\sqrt\pi}8\)): error \(1.33\) for \(n=3\), zero for \(n\ge4\). Example with a transcendental function: \(\int e^{-x^2}\cos x\,dx=\sqrt\pi\,e^{-1/4}=1.38038845\); the errors for \(n=4,6,8\) are \(5.9\cdot10^{-5}\), \(3.7\cdot10^{-8}\), \(1.2\cdot10^{-11}\), and for \(n=12\) rounding level. Use Gauss–Hermite for expectations under a normal distribution (with \(x\mapsto\sqrt2\,x\) rescaling), integrals in quantum mechanics, and any integrand with a Gaussian factor. For \(1/(1+x^2)\) (exact \(1.3433\)) the convergence is slow and algebraic: poles at \(\pm i\) are too close to the real axis.
Smoothness decides. For \(|x|\) on \([-1,1]\) (a corner at the middle) the errors for \(n=2,4,8,16,32\) are \(1.5\cdot10^{-1}\), \(4.3\cdot10^{-2}\), \(1.2\cdot10^{-2}\), \(3.0\cdot10^{-3}\), \(7.8\cdot10^{-4}\): order \(n^{-2}\) only, the same as the composite trapezoid rule. Split the interval at the kink and use Gauss on each piece. For \(1/(1+25x^2)\) the errors are \(3.4\cdot10^{-1}\), \(1.8\cdot10^{-1}\), \(4.1\cdot10^{-2}\), \(1.8\cdot10^{-3}\), \(3.1\cdot10^{-6}\): Gauss converges here (interpolation at equispaced nodes does not), but late, since the poles at \(\pm i/5\) are near the interval.
Oscillation needs nodes. For \(\cos10x\) on \([-1,1]\): \(n=2,4\) are garbage (\(-1.85\), \(1.63\)), then \(5.3\cdot10^{-3}\) at \(n=8\), \(3.8\cdot10^{-5}\) at \(n=10\), \(1.6\cdot10^{-13}\) at \(n=16\). Rule of thumb: about \(\pi\) nodes per wavelength before the geometric regime starts.
Composite Gauss. For a long interval or a rough function, cut into panels and use a fixed \(n\) (3 to 7) on each, like composite Simpson but with order \(2n\). Gauss nodes are not nested, so refining \(n\to2n\) reuses no evaluations; Gauss–Kronrod and Clenshaw–Curtis rules fix this and are what adaptive libraries use.
Choose the family by the weight, not by the interval. If the integrand is \(e^{-x^2}g(x)\) with \(g\) smooth, Gauss–Hermite with \(f=g\) is exponentially better than truncating the integral and using Legendre.
Plan of the two classes
Class 1: why the nodes are roots of orthogonal polynomials.
1.1–1.2: counting parameters, degree \(2n-1\), the orthogonality theorem (the proof is short and is the heart of the subject).
2.1–2.4: inner product, Gram–Schmidt, three-term recurrence, real simple roots, interlacing. In the app: change \(n\) and watch the roots and the small dots; read the Gram table.
3.1 (weights, positivity) and 4.1 (Gauss–Legendre, \(n=2,3\) by hand, then the app).
Class 2: error, the other three families, practice.
3.2 (Hermite-interpolation proof of the error term; reuse the Simpson proof from the previous class), 3.3 (convergence and Gauss against Simpson in the convergence card).
4.2–4.4: Chebyshev (explicit nodes), Laguerre, Hermite: for each, the weight, one explicit \(n=2\) computation, and one experiment in the app.
3.4 and 5.1 as discussion: Golub–Welsch, composite Gauss, rough functions.
Exercises, with answers
Derive the Gauss–Legendre rule with \(n=2\) from the four moment equations. Answer: \(w_1=w_2=1\), \(x=\pm1/\sqrt3\) (lesson 1.1).
Show that it is not exact for \(x^4\) and compute the error two ways. Answer: \(\int x^4=\tfrac25\), \(Q=\tfrac29\), \(E=\tfrac8{45}\); the formula gives \(24/135=\tfrac8{45}\).
Estimate \(\int_0^\pi\sin x\,dx\) with Gauss–Legendre \(n=2\) and \(n=3\) (change variables to \([-1,1]\)). Answer: \(1.93582\) (error \(6.4\cdot10^{-2}\)) and \(2.00139\) (error \(-1.4\cdot10^{-3}\)).
With the Chebyshev rule, \(n=2\), estimate \(\int_{-1}^1\frac{e^x}{\sqrt{1-x^2}}dx\). Answer: \(\pi\cosh(1/\sqrt2)=3.9603\); the exact value is \(3.97746\), error \(1.7\cdot10^{-2}\).
Check that the Laguerre rule with \(n=2\) is exact for \(x^3\) but not for \(x^4\). Answer: \(0.8536\cdot0.5858^3+0.1464\cdot3.4142^3=6\); for \(x^4\) it gives \(20\neq24\).
Show that Gauss–Hermite with \(n=2\) integrates \(x^2e^{-x^2}\) exactly. Answer: \(2\cdot\tfrac{\sqrt\pi}2\cdot\tfrac12=\tfrac{\sqrt\pi}2\).
Prove that no \(n\)-node quadrature rule is exact for all polynomials of degree \(2n\). Answer: \(q=\prod(x-x_j)^2\) (lesson 1.1).
References: R. L. Burden and J. D. Faires, Numerical Analysis, sections 4.7 and 8.2; E. Süli and D. Mayers, An Introduction to Numerical Analysis, chapter 9; K. Atkinson, An Introduction to Numerical Analysis, section 5.3; P. J. Davis and P. Rabinowitz, Methods of Numerical Integration, chapters 2–3; G. Szegő, Orthogonal Polynomials, AMS; G. H. Golub and J. H. Welsch, Calculation of Gauss quadrature rules, Math. Comp. 23 (1969) 221–230; L. N. Trefethen, Is Gauss quadrature better than Clenshaw–Curtis?, SIAM Review 50 (2008) 67–87.
A cross-section of a suspension-bridge deck on one-sided hangers, integrated with Euler, Heun or Runge–Kutta. Push it with a vertical gust and watch whether the deck only bounces or starts to twist, then see how the step size and the method decide whether you can trust the answer.
Simulation
A deck section under a vertical gust
ready
time (s)0
heave \(y\) (m)—
twist \(\theta\) (°)—
hangers L / R—
largest twist \(\max|\theta|\) (°)—
The right half is an illustration: the model has one cross-section only, and the span shape (twist with a node at mid-span, heave without) is assumed. Heave \(y\) is positive downward; twist \(\theta\) is positive when the right edge goes down. The dashed lines mark the twist threshold \(\theta = 0.1\) rad.
Phase portrait
Twist against twist rate
The path of \((\theta,\dot\theta)\). A small ellipse is a small oscillation of the torsion mode; a wide loop means the deck is swinging through large angles. The orange dot is the present state.
Horizontal: \(\theta\) (rad). Vertical: \(d\theta\)/dt (rad/s). The last 1500 samples (\(75\,\mathrm{s}\) of simulated time) are drawn.
Energy
Mechanical energy per metre of deck
Kinetic energy plus the energy stored in the hangers plus gravity, zero at rest. With no wind and no damping it must stay constant, so any drift is the method's error, not physics: try the two free-fall experiments.
Units: kJ per metre of deck. The dashed line is the initial energy (shown only when the wind is off). With wind on, energy flows in and out and is not conserved. The red dashed line is the breaking energy: the deck snaps when the curve touches it.
Critical wind
How strong must the gust be to twist the deck?
not run yet
For each gust frequency the scan finds the smallest strength that twists the deck beyond 0.1 rad within 120 simulated seconds: a scan upward on a geometric grid, then bisection. It uses the method, step, damping, stiffness and slack setting you have chosen, so run it again after changing them.
gust frequency \(\mu / \omega_y\) (vertical axis: \(\varepsilon_{\mathrm{crit}}\) in \(\mathrm{m/s^2}\), logarithmic, from 0.01 to 10)
A grey dot at the top means no twisting up to \(\varepsilon = 8\,\mathrm{m/s^2}\). With the hangers always taut every dot is grey: the linear model never twists. The curve is jagged on purpose: the response is not monotone in \(\varepsilon\), so the bisection finds one threshold, not the only one.
Step size
Error against step size
A free-fall trajectory (no wind, no damping, \(2\,\mathrm{s}\)) is computed with each method at seven step sizes and compared with RK4 at \(h=0.0001\,\mathrm{s}\). The error is the larger of the heave error and the edge-lift error, in metres. On a log-log plot a straight line of slope \(p\) means error ∝ \(h^p\).
step size \(h\) (s), logarithmic. Solid: hangers always taut (smooth). Dashed: hangers go slack and taut again (kinks).
method
order taut
order slack
error taut
error slack
Orders are least-squares slopes over the four smallest steps; the two error columns are at \(h=0.036\,\mathrm{s}\), in metres. The hangers' on/off switching makes the right-hand side only continuous, not smooth, and the effective order of RK4 collapses from 4 to about 2.4 here, and becomes erratic.
Stability
Does the method keep a pure oscillation bounded?
Apply a method to an undamped oscillation of frequency \(\omega\). Each step multiplies the amplitude by |R(z)|, with \(z = \omega h\). Anything above 1 grows without bound. Your two vibration modes, torsion and heave, are the dots for the method and step you chose.
\(z = \omega h\). Dashed lines: |R| = 1 and the RK4 limit \(z = 2\sqrt{2} \approx 2.83\).
Honest history
What is, and is not, modelled here
The Tacoma Narrows Bridge opened on 1 July 1940 and fell on 7 November 1940 in a wind of about 40 mph (64 km/h), after months of vertical bouncing that had earned it the nickname “Galloping Gertie”. On the last morning the motion changed into a large twisting of the deck, at about \(0.2\,\mathrm{Hz}\).
Many textbooks say that wind resonance with vortex shedding destroyed it. Specialists disagree: the usual modern explanation is aeroelastic flutter, a self-excited motion in which the wind itself feeds the twisting, and there is still no universally accepted account of every detail. This app does not model flutter.
What it does model is a different mechanism proposed by Lazer and McKenna: hangers pull but cannot push, so when they go slack the deck's equations stop being linear and vertical forcing can feed torsion. Whether the Tacoma hangers really went slack is disputed. The parameters here give a believable shape, not a replica: the model torsion frequency is \(0.70\,\mathrm{Hz}\), not the real \(0.2\,\mathrm{Hz}\).
At a glance
The model and the tools
Stretch of each hanger (\(y\) down, \(\theta\) positive with the right edge down). Tension is one-sided.
Equations of motion per metre of deck: a bar of half-width \(\ell = 6\,\mathrm{m}\), mass \(m = 2500\,\mathrm{kg/m}\) and moment of inertia \(m\ell^2/3\). Damping \(\delta\) and a vertical gust of strength \(\varepsilon\) and frequency \(\mu\).
Four groups: the bridge and the honest story, the mathematics of the model, numerical methods, and experiments and exercises. Open a question, think, and then read the answer.
1. The bridge and the story
What happened at Tacoma?
The Tacoma Narrows Bridge in Washington State had a main span of 2,800 ft (\(853\,\mathrm{m}\)) and a deck only about 39 ft (\(12\,\mathrm{m}\)) wide, slender for its length. It opened on 1 July 1940 and from the first days it bounced in moderate wind. On 7 November 1940 the bouncing turned into a large torsional motion, the two edges of the deck going up and down in opposition, and within about an hour or so the main span fell. Nobody died; a dog in a car was the only casualty. Ambient wind was about 40 mph (64 km/h). Sources differ on some figures (the day is sometimes given as 10 November in older accounts), so treat the numbers as approximate.
Was it resonance?
Not in the textbook sense. The popular story is that the wind pushed at the bridge's natural frequency, like a child on a swing. Billah and Scanlan (1991) showed that physics textbooks repeated this and that it is not what the evidence supports. The vortices shed by the deck had a frequency far from the torsional frequency that appeared. The explanation most engineers accept is aeroelastic flutter: when the deck twists, the airflow around it changes in a way that pushes it further, a self-excited feedback that behaves like negative damping above a critical wind speed. A resonance needs an external rhythm; flutter creates its own.
Other mechanisms have been proposed, and published surveys state that no explanation of every detail is universally accepted. This app is about one of them, not about the verdict.
Then why simulate slack hangers?
Because it is a clean example of a nonlinear mechanism that couples two motions that the linear theory keeps apart. Lazer and McKenna (1990) pointed out that a cable stretches but does not compress, so a hanger is a spring that turns off when slack. McKenna and Tuama (2001) showed in a simple model that vertical forcing can then create a torsional response. Critics, for example Green and Unruh, argue that the Tacoma hangers did not in fact go slack and that the parameters needed are unrealistic. So treat the card as a laboratory for the mechanism and for the numerics, not as a reconstruction of 7 November 1940.
2. The mathematics of the model
Where do the equations come from?
The deck section is a rigid bar of half-width \(\ell\), mass \(m\) per metre. Its centre moves down by \(y\) and it rotates by \(\theta\), so the left and right edges move down by \(y\mp\ell\sin\theta\). Each hanger is stretched by \(f=y_0+y\mp\ell\sin\theta\), where \(y_0=mg/(2K)\) is the static stretch, and pulls with \(K f^{+}\) with \(f^+=\max(f,0)\). Newton for the centre gives \(m\ddot y=-K(f_L^++f_R^+-2y_0)\) plus damping and forcing. For the rotation, the torque of a vertical force \(F\) at distance \(\ell\cos\theta\) from the centre is \(F\ell\cos\theta\), and the moment of inertia of a bar about its centre is \(\int_{-\ell}^{\ell}(m/2\ell)\,x^2\,dx=m\ell^2/3\). That gives the second equation in the summary card.
What happens if the hangers never go slack?
Then \(f^+=f\), and for small \(\theta\) the equations become \(\ddot y+\delta\dot y+\tfrac{2K}{m}y=\varepsilon\sin\mu t\) and \(\ddot\theta+\delta\dot\theta+\tfrac{6K}{m}\theta=0\): two independent oscillators. Vertical forcing drives only \(y\); the twist \(\theta\) has no forcing and any initial twist simply dies out. With \(K=8000\,\mathrm{N/m^2}\) and \(m=2500\,\mathrm{kg/m}\), \(\omega_y=\sqrt{2K/m}=2.53\) rad/s (period \(2.48\,\mathrm{s}\)) and \(\omega_\theta=\sqrt3\,\omega_y=4.38\) rad/s (period \(1.43\,\mathrm{s}\)). Run “Same gust, hangers kept taut”: the twist stays at about 0.06°, the seed we gave it. The ratio \(\sqrt3\) is not a coincidence of the numbers: it comes only from the geometry, inertia \(m\ell^2/3\) against two springs at distance \(\ell\).
Why is the static sag \(1.5\,\mathrm{m}\)?
At rest the two hangers carry the weight: \(2Ky_0=mg\), so \(y_0=mg/(2K)=2500\cdot9.8/16000=1.53\) m. This number matters for slackness: a hanger goes slack when its stretch \(y_0+y\mp\ell\sin\theta\) becomes negative, that is, when the deck center rises more than \(y_0\) above its rest position (for \(\theta=0\)), or earlier when the deck is tilted. A stiffer hanger (larger \(K\)) gives a smaller \(y_0\), so slackness is reached for a smaller motion, but \(\omega_y\) is also higher. Earlier trials with \(K=1000\) gave an unphysical \(12\,\mathrm{m}\) sag and twists of tens of radians, so the default is \(K=8000\).
How does a vertical push make the deck twist?
In the taut model the torque is \(-2K\ell^2\sin\theta\cos\theta\) and does not depend on \(y\). With slackness it becomes \(K\ell\cos\theta\,(f_L^+-f_R^+)\). If the deck is lifted high enough, both hangers are slack and the torque vanishes, so the twist is freed from its restoring spring. When one side goes slack before the other, which happens as soon as \(\theta\neq0\), the torque is not symmetric and depends on the heave. In this way the heave motion changes the stiffness of the torsion mode over each cycle, and a stiffness that varies with the right rhythm can pump energy into an oscillation (parametric excitation, as when a child pumps a swing by standing and squatting). That is a plausible reading, not a proof: the experiments show what happens, the mathematics of when is hard.
Run “Gust at the heave frequency, slack hangers” and watch the edge-lift curve: a tiny twist of 0.06° is amplified to the full 5.7° threshold and beyond.
What does the critical-wind curve say?
With RK4 and \(h=0.02\) s the scan gives, for example, \(\varepsilon_{crit}\approx6.6\,\mathrm{m/s^2}\) at \(\mu/\omega_y=0.6\), 3.2 at 0.8, 0.78 at 1.0, 1.4 at 1.04, 7.4 at 1.08, and no twisting up to \(8\,\mathrm{m/s^2}\) from 1.16 on. Three lessons. First, there is a valley near the heave frequency, as resonance would suggest. Second, it is lopsided: weaker forcing is enough on the low-frequency side than on the high side, which fits the picture of a spring that softens when hangers go slack (a softening spring resonates at lower frequency when driven hard); this is a plausible reading, not something the app proves. Third, the curve is jagged: with a nonlinear coupling the response is not monotone in \(\varepsilon\), so “the smallest \(\varepsilon\) that twists” depends on how you search. We scan upward on a geometric grid, bracket the first crossing and bisect inside it; a threshold hidden between two scan points could be missed.
3. Numerical methods
Why not just use Euler?
Euler's method turns \(y''=-\omega^2y\) into a map whose amplitude grows each step by \(\sqrt{1+z^2}\), \(z=\omega h\): an undamped oscillation spirals outward whatever \(h\) you choose. For the torsion mode at \(h=0.02\) s, \(z=0.088\) and the amplitude grows by 0.4% per step, about 21% per simulated second, and the energy by about 47% per second at the start. Try “Free fall, no wind: Euler”: the energy curve climbs and the status line warns that energy grew with no wind. The same trajectory with RK4 and the same \(h\) stays within \(3\cdot 10^{-4}\) of the initial energy over \(30\,\mathrm{s}\), and Heun within \(5\cdot 10^{-3}\) (maximum relative drift over \(30\,\mathrm{s}\): Euler 4.9, Heun \(4.7\cdot 10^{-3}\), RK4 \(2.9\cdot 10^{-4}\) at \(h=0.02\); at \(h=0.005\): 1.1, \(1.3\cdot 10^{-4}\), \(1.8\cdot 10^{-5}\).
The consequence for the critical wind: with Euler the scan returns thresholds near zero everywhere, because the method itself drives the twist.
What are the orders of the methods?
Euler has local error \(O(h^2)\) and global error \(O(h)\); Heun (the explicit trapezoid rule) global \(O(h^2)\); the classical Runge–Kutta method global \(O(h^4)\). In the step-size card, on a smooth trajectory (hangers always taut), the measured slopes are 0.95, 2.02 and 4.02. When the hangers switch on and off the right-hand side has corners, so the Taylor expansion behind the order proofs fails near each switch: Euler and Heun keep roughly their order (0.99 and 2.04), but RK4 falls to about 2.4 and its error curve is irregular, depending on where the corners fall between steps. Moral: a high-order method does not buy you high accuracy on a non-smooth problem; to recover it you would have to locate each switching time and restart the integrator there (event handling).
Why does RK4 blow up with a huge step?
For \(y''=-\omega^2y\) one RK4 step multiplies the state by \(R(z)=1-\tfrac{z^2}{2}+\tfrac{z^4}{24}+i\bigl(z-\tfrac{z^3}{6}\bigr)\), \(z=\omega h\), and \(|R|\le1\) only for \(z\le2\sqrt2\approx2.83\). With \(h=0.7\) s the torsion mode has \(z=4.38\cdot0.7=3.07\), just past the limit, and \(|R|=1.75\): the oscillation grows by 75% per step. Run “RK4 with a huge step”: the energy rises by a factor of several hundred although damping is on and no wind blows. In the nonlinear model the twist does not grow without bound (the \(\sin\theta\cos\theta\) torque saturates), so you see an erratic motion at large amplitude instead of an overflow: the energy curve is the reliable alarm. Below the limit RK4 is stable and slightly damps high frequencies (at \(z=2\) the amplitude loses about 25% per step), so too large a step can also make a real oscillation die from numerical damping alone.
How do I know the answer is right?
You never know for sure; you collect evidence. (1) Check a case with a known answer: the taut model has periods \(2\pi/\omega_y=2.484\) s and \(2\pi/\omega_\theta=1.434\) s, and the simulation reproduces them to four digits. (2) Check an invariant: with no wind and no damping the energy must be flat. (3) Halve the step and see whether the answer changes by about the expected \(2^{-p}\); for RK4 on smooth parts, a factor 16. (4) Compare two methods: Heun and RK4 give the same critical thresholds to three digits (0.776 against 0.78 at \(\mu/\omega_y=1\)) while Euler does not, which tells you which to distrust. Agreement is not proof, but disagreement is a certain warning.
Why a fixed step and not an adaptive one?
For teaching: with a fixed step, the formulas for the amplification factor and the order apply directly, and you can see how the error depends on \(h\). A production code would use an adaptive embedded pair, such as Dormand–Prince, to choose \(h\) from an error estimate, and an event finder to land exactly on each slack/taut switch. Adaptive steps shrink automatically near the switches, which is where the fixed-step RK4 loses its order.
4. Experiments and exercises
Experiment: gust against frequency
Start with “Gust at the heave frequency”. Keep the strength at 1.2 and move the frequency slider from 0.90 to 1.10 in steps of 0.02. In our runs the deck twists only for \(0.94\lesssim\mu/\omega_y\lesssim1.04\), reaching 12° to 22° within two minutes, and it takes about \(25\,\mathrm{s}\) to cross the 5.7° threshold at 1.00. Outside that band the twist stays at its seed. Then lower the strength to 0.5: no frequency twists the deck at all, and the lowest threshold of the whole scan is \(0.78\,\mathrm{m/s^2}\). Compare with the critical-wind curve (run the scan first): the band where it dips below 1.2 is the band you found.
Exercise: change the stiffness
Change \(K\) from 8000 to 2000 and then to 30000 with the gust frequency fixed at 1.0 times \(\omega_y\). Predict first: how do \(\omega_y\), \(\omega_\theta\) and \(y_0\) scale with \(K\)? (Answers: \(\omega\propto\sqrt K\), halving to 1.26 and \(2.53\,\mathrm{rad/s}\) when \(K\) is divided by 4, and \(y_0\propto1/K\), growing to \(6.1\,\mathrm{m}\).) Then check the readout beside the slider. Which setting twists more easily? Notice that the preset menu goes to “Custom” and the simulation restarts, because changing \(K\) changes what “rest” means.
Exercise: derive the amplification factors
Write \(y''=-\omega^2y\) as a first-order system \(u'=Au\), \(u=(y,y')\), and apply one step of each method. Since \(A\) has eigenvalues \(\pm i\omega\), it is enough to apply the method to the scalar test equation \(u'=\lambda u\), \(\lambda=i\omega\). Euler: \(R=1+\lambda h=1+iz\), so \(|R|=\sqrt{1+z^2}\). Heun: \(R=1+\lambda h+\tfrac12(\lambda h)^2=1-\tfrac{z^2}2+iz\), and \(|R|^2=(1-\tfrac{z^2}{2})^2+z^2=1+\tfrac{z^4}{4}\). RK4: \(R\) is the Taylor polynomial of \(e^{\lambda h}\) up to order 4: \(1+\lambda h+\tfrac{(\lambda h)^2}{2}+\tfrac{(\lambda h)^3}{6}+\tfrac{(\lambda h)^4}{24}\). Show that \(|R|=1\) is reached at \(z=2\sqrt2\) (there \(R=-\tfrac13-\tfrac{2\sqrt2}{3}i\)) and that near \(z=0\), \(|R|^2=1-z^6/72+\ldots\) so RK4 damps slightly. Check against the plot.
Exercise: what would a better model include?
List what the model leaves out and say which would matter most. A partial list: the wind force depends on the angle of attack and therefore on \(\theta\) and \(\dot\theta\) (this is flutter); the cables and towers have their own motion; the bridge is a long structure with many modes, not one section; the hangers have different lengths and tensions; the damping is not a single viscous constant. Then decide which new term would turn the damping \(\delta\) negative above a critical wind speed, which is the signature of flutter. (Hint: a force proportional to \(+\dot\theta\) in the torque equation.)
References: Lazer and McKenna, SIAM Review 32 (1990) 537–578. McKenna, American Mathematical Monthly 106 (1999) 1–18. McKenna and Tuama, American Mathematical Monthly 108 (2001) 738–745. Billah and Scanlan, American Journal of Physics 59 (1991) 118–124. Gazzola, arXiv:1306.0080 (a survey of the competing explanations). Dates and measurements of the 1940 collapse differ between sources and are quoted as approximate.
Try it in Python
The same model and the same three methods in a short script: the energy test, the orders of convergence and the critical wind at one frequency. It needs only numpy.
"""Tacoma Narrows toy model: a deck cross-section on one-sided hangers (Lazer-McKenna / McKenna).
State s = (y, y', theta, theta'); y is the downward heave of the centre, theta > 0 when the right edge is down.
Run: python3 tacoma_bridge.py (needs only numpy; takes a few seconds)"""
import numpy as np
m, K, ell, delta, G = 2500.0, 8000.0, 6.0, 0.15, 9.8
y0 = m * G / (2 * K) # static stretch of each hanger
wy, wt = np.sqrt(2 * K / m), np.sqrt(6 * K / m) # heave and torsion frequencies when the hangers stay taut
def rhs(s, t, eps, mu, d=delta, slack=True):
y, v, th, w = s
fL, fR = y0 + y - ell * np.sin(th), y0 + y + ell * np.sin(th)
if slack:
fL, fR = max(fL, 0.0), max(fR, 0.0) # a hanger pulls but never pushes
return np.array([v,
-(K / m) * (fL + fR - 2 * y0) - d * v + eps * np.sin(mu * t),
w,
3 * K * np.cos(th) * (fL - fR) / (m * ell) - d * w])
def step(method, s, t, h, *a, **k):
f = lambda u, tt: rhs(u, tt, *a, **k)
if method == "euler":
return s + h * f(s, t)
if method == "heun":
k1 = f(s, t); return s + 0.5 * h * (k1 + f(s + h * k1, t + h))
k1 = f(s, t); k2 = f(s + 0.5 * h * k1, t + 0.5 * h); k3 = f(s + 0.5 * h * k2, t + 0.5 * h); k4 = f(s + h * k3, t + h)
return s + h / 6 * (k1 + 2 * k2 + 2 * k3 + k4)
def energy(s, slack=True):
y, v, th, w = s
fL, fR = y0 + y - ell * np.sin(th), y0 + y + ell * np.sin(th)
if slack:
fL, fR = max(fL, 0.0), max(fR, 0.0)
return 0.5 * m * v**2 + 0.5 * (m * ell**2 / 3) * w**2 + 0.5 * K * (fL**2 + fR**2 - 2 * y0**2) - m * G * y
def run(method, s0, T, h, **k):
s, t = np.array(s0, float), 0.0
for i in range(int(round(T / h))):
s = step(method, s, t, h, **k); t = (i + 1) * h
return s
def max_theta(eps, mu, T=120.0, h=0.02, stop=0.1, method="rk4", slack=True):
s, t, big = np.array([0, 0, 0.001, 0.0]), 0.0, 0.0
for i in range(int(round(T / h))):
s = step(method, s, t, h, eps, mu, slack=slack); t = (i + 1) * h
big = max(big, abs(s[2]))
if not np.isfinite(big) or big > stop:
break
return big
print(f"y0 = {y0:.3f} m, omega_y = {wy:.4f} rad/s (period {2*np.pi/wy:.3f} s), omega_theta = {wt:.4f} rad/s (period {2*np.pi/wt:.3f} s)")
# 1. energy test: free fall, no wind, no damping
s0 = [-2.5 * y0, 0, 0.3, 0]
print("\nenergy drift over 30 s (max |E - E0| / E0), h = 0.02")
for meth in ("euler", "heun", "rk4"):
s, t, worst, E0 = np.array(s0, float), 0.0, 0.0, energy(s0)
for i in range(1500):
s = step(meth, s, t, 0.02, 0.0, 1.0, d=0.0); t = (i + 1) * 0.02
worst = max(worst, abs(energy(s) - E0) / E0)
print(f" {meth:6s} {worst:.2e}")
# 2. orders of convergence against RK4 with h = 1e-4 (T = 2 s)
print("\nconvergence orders (T = 2 s, error = max(|dy|, ell |dtheta|)), taut vs slack-crossing")
for slack in (False, True):
ref = run("rk4", s0, 2.0, 1e-4, eps=0.0, mu=1.0, d=0.0, slack=slack)
for meth in ("euler", "heun", "rk4"):
hs = np.array([2.0 / n for n in (56, 112, 224, 448, 896)])
er = [max(abs(a[0] - ref[0]), ell * abs(a[2] - ref[2]))
for a in (run(meth, s0, 2.0, h, eps=0.0, mu=1.0, d=0.0, slack=slack) for h in hs)]
p = np.polyfit(np.log(hs[-4:]), np.log(er[-4:]), 1)[0]
print(f" {'slack' if slack else 'taut ':5s} {meth:6s} order {p:5.2f}")
# 3. critical wind at one frequency: scan eps upward on a geometric grid, then bisect the first crossing
def critical(ratio, **kw):
grid = 0.02 * (8 / 0.02) ** (np.arange(14) / 13)
for i, e in enumerate(grid):
if max_theta(e, ratio * wy, **kw) > 0.1:
lo, hi = (grid[i - 1] if i else 0.0), e
for _ in range(9):
mid = 0.5 * (lo + hi)
lo, hi = (lo, mid) if max_theta(mid, ratio * wy, **kw) > 0.1 else (mid, hi)
return hi
return None
print("\ncritical gust strength eps (m/s^2) that twists the deck beyond 0.1 rad within 120 s")
for r in (0.8, 1.0, 1.3):
print(f" mu/omega_y = {r}: RK4 {critical(r)}")
print(" mu/omega_y = 1.0, hangers always taut:", critical(1.0, slack=False))
A footbridge sways a little, each walker adjusts the pace to the sway, and above a critical crowd size they all fall into step and the sway grows. Simulate hundreds of walkers on one bridge mode, find the critical crowd, then fit dampers and see the wobble die. The same page shows how a careless solver can fake a wobble out of nothing.
Simulation
A crowd on a footbridge
ready
time (s)0
walkers on the bridge \(n\)—
sway amplitude \(A\) (cm)—
in step \(r\)—
critical crowd \(N_c\)—
predicted final sway \(A_\ast\) (cm)—
The bridge is one lateral mode seen from above; the sway is drawn exaggerated, with 12 cm reaching the largest drawn bend. A walker's dot moves sideways with its own step phase; its colour is the phase relative to the bridge, so a crowd in step shows one colour. “In step” \(r\) is 0 for walkers at random phases and 1 for walkers all in phase with the sway. The dotted line in the chart marks 1 cm. Mass, damping and frequency are illustrative round numbers, not measurements of the real bridge.
Synchrony
How much of the crowd is in step
The order parameter \(r\) is the length of the average of the walkers' phase vectors relative to the bridge. For random walkers it is of order \(1/\sqrt{N}\); once the crowd locks to the sway it climbs towards 1. The dashed line is the prediction \(r^*\) for the final state.
time (s)
Walkers cannot all lock: those whose natural pace is far from the bridge frequency keep drifting, which is why \(r\) stays below 1 and why it takes a minimum crowd for the sway to feed itself. The prediction is computed for the truncated Lorentzian actually used, hence \(r^*\) can reach 1/\(Z\) slightly above 1 for huge crowds before the formula stops applying.
Bifurcation
The critical crowd
not run yet
Run the whole simulation for 15 crowd sizes from 0.4 to 1.8 times the predicted critical crowd and plot the sway each reaches. Below the critical size nothing happens; above it the sway grows like the square root of \(N(N - N_c)\).
number of walkers \(N\)
Each point is a run of \(1500\,\mathrm{s}\) with RK4, \(h=0.08\,\mathrm{s}\), averaged over the last \(150\,\mathrm{s}\), with the damping, spread and sensitivity currently set. Points just above \(N_c\) fall under the curve because the sway needs a very long time to grow there: its growth rate is about \(\zeta \Omega_0(N/N_c - 1)\), which is 0.005 per second at \(N = 1.1 N_c\).
The fix
How much damping is enough
not run yet
Fix the crowd at the number set above and vary the damping ratio instead. The critical crowd is proportional to \(\zeta\), so the sway disappears as soon as \(\zeta\) exceeds \(N\) divided by the critical crowd per unit of \(\zeta\). That is why the real bridge was cured with dampers and not with a sturdier deck.
damping ratio \(\zeta\) (%)
Same runs as the other scan (\(1500\,\mathrm{s}\), RK4, \(h=0.08\,\mathrm{s}\)). With the default settings and \(N\) = 250 the damping needed is \(\zeta = 1.75\) %: a little under twice what the bridge has. A safety margin is needed, since people also gather in crowds bigger than \(N\) and different pedestrians sway at different frequencies.
Numerics
A solver that makes a wobble from nothing
Take the bridge alone, with no walkers, give it a 1 cm push and integrate \(300\,\mathrm{s}\). The true motion dies away. Plot the amplitude after \(300\,\mathrm{s}\) against the step \(h\) for three methods. Explicit Euler pumps energy into an oscillator at every step and, unless \(h\) is tiny, turns a damped bridge into one that wobbles by itself.
time step \(h\) (s)
\(h\) (s)
Euler
Heun
RK4
The numbers are amplitude after \(300\,\mathrm{s}\) divided by the initial 1 cm; the exact value is 2.8e-7. A ratio above 1 means the method has invented a wobble. Euler is stable only for \(h < 2\zeta/\Omega_0 = 0.004\,\mathrm{s}\), Heun for \(h < (8\zeta/\Omega_0^3)^{1/3} = 0.086\,\mathrm{s}\). RK4 never grows here, but above \(h=0.1\,\mathrm{s}\) it damps too fast (the value at \(h=0.12\) is half the true one), which would hide a real instability.
The story
What happened on the London bridge
The Millennium Bridge, a steel footbridge across the Thames in London, opened to the public in June 2000. On the opening days, with large crowds on it, it began to sway sideways, and walkers instinctively widened their stance and fell into step with the motion. It was closed after two days.
Engineers measured the effect with crowd tests and concluded that above a critical number of people the crowd acts as negative damping on the lateral mode. The cure was to add damping, not stiffness: fluid viscous dampers and tuned mass dampers were fitted and the bridge reopened in February 2002.
The model on this page is the one proposed by Strogatz and collaborators in 2005: a bridge mode driven by walkers whose pace responds to the sway, a Kuramoto-type problem. Its prediction that a critical crowd size exists, and that it grows in proportion to the damping, matches the engineers' remedy.
Honest limits
What the model leaves out
It has one bridge mode and walkers who depend on the bridge only through its amplitude and phase. Real crowds are not uniform along the span, people differ in weight and step, and the walkers' mass also changes the bridge frequency.
The numbers are illustrative. The modal mass 113 t, the frequency \(0.8\,\mathrm{Hz}\), the damping ratio 1 %, the force \(G=35\) \(N\) per walker and the spread \(\gamma = 0.8\) rad/s are round values in the range of such bridges, not measurements of the real structure; only the sensitivity \(C = 16\,\mathrm{m}^{-1}\mathrm{s}^{-1}\) is the value quoted in the 2005 paper. The critical crowd of about 143 is therefore a property of this toy bridge.
Whether walkers truly lock their steps is debated. Later work on walking, such as inverted-pendulum models of each walker, shows that a crowd can feed a sway through its response to the motion even without strict phase locking. This app shows one explanation, the synchronization one, and not the only possible mechanism.
At a glance
The model and its predictions
The bridge is a damped oscillator pushed by the sum of the walkers' lateral forces:
Four groups: the story, the mathematics of the model, numerical methods, and experiments. Open a question, think, then read the answer.
1. The bridge and the story
Why does a crowd make a bridge sway more?
Walking sideways pushes on the deck with a force that alternates left and right once per two steps, at roughly \(1\,\mathrm{Hz}\), near the lateral frequencies of many footbridges. If the pushes of different people are random they largely cancel, and the bridge only feels a force of order \(\sqrt N\). If the people are in step with each other and with the sway, the forces add up to order \(N\) and push the bridge in phase with its own velocity, which is exactly what negative damping means.
Why a critical number of people, and not a gradual effect?
It is a feedback loop. More sway makes more walkers adjust their pace to it, more walkers in step make more sway. Whether the loop amplifies or dies depends on whether the push from the crowd beats the damping of the bridge. Below the threshold any small sway is damped out and the walkers stay at their own paces; above it the loop runs away until it saturates, because walkers whose natural pace is far from the bridge's cannot lock. Try the “below critical” preset (a push dies out) and “above critical” (a tiny seed grows over a couple of minutes).
Why did the fix add dampers instead of stiffening the bridge?
The critical crowd is proportional to the damping coefficient: \(N_c=4\zeta K\gamma/(GC\sin\alpha)\propto B\). Doubling the damping doubles the threshold. Stiffness enters only through \(K\), so tripling the threshold by stiffening would need three times the stiffness, moving the frequency by a factor \(\sqrt3\), and a stiffer bridge is still excited by a crowd at its own frequency. Adding damping raises the threshold directly and cheaply. In the app the “Fit dampers” button raises \(\zeta\) to 3 %, so that \(N_c\) goes from 143 to 428 and a wobbling crowd of 250 has its sway brought below 1 cm in about \(35\,\mathrm{s}\).
2. The mathematics
How is the model built?
One lateral mode of the bridge is an oscillator \(M\ddot X+B\dot X+KX=G\sum\sin\Theta_i\), where \(\Theta_i\) is the phase of the \(i\)-th walker's sway cycle and \(G\) the lateral force amplitude of one walker. Each walker obeys \(\dot\Theta_i=\omega_i+CA\sin(\Psi-\Theta_i+\alpha)\): its own pace \(\omega_i\) plus a pull toward the bridge phase \(\Psi\), proportional to the bridge amplitude \(A\). With \(X=A\sin\Psi\), \(\dot X=\Omega_0A\cos\Psi\) the amplitude and phase are computed from \(X\) and \(\dot X\) at each step. With \(\alpha=\pi/2\) the walkers' force runs a quarter turn ahead of the displacement and so pushes in phase with the velocity. The \(N+2\) equations are integrated together. This is a Kuramoto model in which the coupling is not fixed but is carried by the bridge itself.
Where does the critical crowd come from?
Look for a steady state with sway amplitude \(A\). In the frame turning with the bridge, a walker with detuning \(\nu=\omega_i-\Omega_0\) obeys \(\dot\varphi=\nu-CA\sin\varphi\). Those with \(|\nu|\lt CA\) lock at \(\sin\varphi=\nu/CA\); the others drift and on average contribute nothing to the force. The locked walkers give an order parameter \(r=\int_{-CA}^{CA}P(\nu)\sqrt{1-\nu^2/(CA)^2}\,d\nu\), and the bridge, driven at resonance, has its damping balanced by the part of the crowd force in phase with its velocity: \(B\Omega_0A=GNr\sin\alpha\). For small \(A\), \(r\approx\tfrac\pi2P(0)\,CA\); inserting it gives a nonzero \(A\) only if \(N>N_c=\dfrac{2B\Omega_0}{\pi\,G\,C\,P(0)\sin\alpha}\). With a Lorentzian, \(P(0)=1/(\pi\gamma)\) and \(N_c=2\gamma B\Omega_0/(GC\sin\alpha)\). Equivalently \(N_c=4\zeta K\gamma/(GC\sin\alpha)\), proportional to the damping ratio \(\zeta\).
Why is the Lorentzian convenient, and what does the cut change?
For a Lorentzian the integral can be done in closed form: \(r=(\sqrt{a^2+\gamma^2}-\gamma)/a\) with \(a=CA\). The balance \(B\Omega_0A=GNr\sin\alpha\) then becomes \(\sqrt{a^2+\gamma^2}-\gamma=q\,a^2\), whose solution is \(a^2=(1-2\gamma q)/q^2\); hence \(A_\ast=\dfrac{G\sin\alpha}{B\Omega_0}\sqrt{N(N-N_c)}\) and \(r_\ast=\sqrt{1-N_c/N}\). A true Lorentzian has tails reaching negative walking frequencies, so the app cuts it at \(\pm T=\pm\min(5\gamma,0.8\Omega_0)\) and renormalizes; the kept share is \(Z=\tfrac2\pi\arctan(T/\gamma)=0.874\) for the default settings. Everything above then holds with \(N_c\) multiplied by \(Z\), \(A_\ast\) and \(r_\ast\) divided by \(Z\), as long as \(CA_\ast\le T\).
How well does the theory match the simulation?
With the default settings the predicted critical crowd is \(N_c=142.6\). Simulating \(1500\,\mathrm{s}\) and averaging the last \(150\,\mathrm{s}\) gives, for \(N=150,175,200,250,300,400\), sways of 2.15, 5.15, 7.40, 11.36, 15.09 and 22.31 cm against the predicted 2.33, 5.28, 7.51, 11.48, 15.23 and 22.49 cm, within 1 to 8 %; the discrepancy is largest closest to threshold, where growth is slow. The order parameter agrees within 0.005 for \(N\ge200\). Fitting \(A^2/N\) against \(N\) to the simulated sways for \(N=175\) to \(300\) and reading off the intercept gives \(N_c=143.7\), within 1 % of the formula. Walkers are sampled at quantiles, so there is no randomness: the runs are exactly reproducible.
3. Numerical methods
Why integrate everything together with RK4?
The state has \(N+2\) numbers: bridge displacement and velocity and \(N\) phases. The fast part is the oscillation of the bridge at \(\Omega_0\) and of the walkers' phases at their own paces, all within a few rad/s, and the slow part is the growth or decay of the amplitude over minutes. A fourth-order method lets one step cover a good fraction of a period, so that 25 minutes of simulated time cost under 20 000 steps. The cost per step is proportional to \(N\) (four evaluations, each one sine per walker), so one run with 250 walkers takes under a second.
How small must the step be?
Two limits. For the bridge alone, the amplification factor per step of each method is a function of \(z=\Omega_0h\): \(|R|=\sqrt{1+z^2}\) for Euler, \(\sqrt{1+z^4/4}\) for Heun, both larger than 1 for every \(z>0\), so they pump energy at the rate \(\Omega_0^2h/2\) (Euler) and \(\Omega_0^4h^3/8\) (Heun) per second. The true damping is \(\zeta\Omega_0\), so a method fakes a wobble when its pumping beats it: \(h>2\zeta/\Omega_0=0.004\) s for Euler and \(h>(8\zeta/\Omega_0^3)^{1/3}=0.086\) s for Heun. These agree with the table above (Euler grows already at \(0.004\,\mathrm{s}\), Heun at \(0.09\,\mathrm{s}\)). For the coupled system with RK4, the error in the final sway relative to \(h=0.01\) is \(-76\%\) at \(h=0.2\), \(-3.4\%\) at 0.1, \(-0.12\%\) at 0.05 and below 0.005 % at 0.02.
Why sample the walkers at quantiles?
Random frequencies would make each run different, and a threshold measured on a few hundred walkers would carry noise of several percent. Placing walker \(i\) at the quantile \((i+\tfrac12)/N\) of the distribution makes the sum over walkers a midpoint rule for the integral that defines \(r\), so the run is deterministic and the finite-\(N\) error is smooth. The initial phases are a golden-ratio sequence \(2\pi\{i\varphi\}\), which spreads evenly (so the initial order parameter is small), and both sequences are shuffled so that the first \(n\) walkers, the ones who have entered when the crowd arrives gradually, still represent the whole distribution.
4. Experiments and exercises
Exercise: find the critical crowd by experiment
Pick the preset “below critical”, press “Push the bridge” a few times, and watch the sway die. Raise \(N\) by 10 each time and repeat. At which \(N\) does a 1 cm push stop dying and start to grow? Because growth near threshold is slow, wait a few minutes of simulated time (speed \(32\times\) and compare with the predicted \(N_c\) in the metrics row. Then double \(\zeta\) and see how the threshold moves.
Exercise: what makes the threshold easier to cross?
Run “Walkers with nearly equal paces”. The critical crowd falls from 143 to 54, because \(N_c\) is proportional to the spread \(\gamma\): when everyone naturally walks at nearly the same pace, more of the crowd is within reach of the bridge frequency and a smaller crowd suffices. With the settings of that preset and \(N=100\), the predicted sway is 4.8 cm and the simulated one 4.7 cm. Now lower \(C\) to 8 and check that \(N_c\) doubles: it is inversely proportional to the sensitivity.
Exercise: the solver that invents a wobble
Run “No walkers, explicit Euler”. The bridge is empty and damped, yet the amplitude grows. Lower \(h\) to \(0.004\,\mathrm{s}\) and see the growth stop; lower it again and see the decay that should be there. Switch to Heun with \(h=0.1\): also growth. Then switch to RK4. What would a model of the Tacoma bridge, a real bridge whose behaviour depended on a small damping, have said if its author had used explicit Euler?
References: Strogatz, Abrams, McRobie, Eckhardt and Ott, “Crowd synchrony on the Millennium Bridge”, Nature 438 (2005) 43–44. Dallard et al., “London Millennium Bridge: pedestrian-induced lateral vibration”, Journal of Bridge Engineering 6 (2001) 412–417. Eckhardt, Ott, Strogatz, Abrams and McRobie, “Modeling walker synchronization on the Millennium Bridge”, Physical Review E 75 (2007) 021110. Dates are as widely reported; the model parameters of the app are illustrative.
Try it in Python
The same model in a short script: the critical crowd, the simulated and predicted sway for four crowd sizes, the effect of 3 % damping, and an explicit Euler solver that makes a wobble out of nothing. It needs only numpy.
"""Millennium Bridge toy model (Strogatz, Abrams, McRobie, Eckhardt and Ott, Nature 2005): a bridge mode driven by walkers who adjust their pace to the sway.
Bridge: M X'' + B X' + K X = G sum sin(Theta_i) Walker: Theta_i' = omega_i + C A sin(Psi - Theta_i + alpha), X = A sin Psi, X' = Omega0 A cos Psi.
Run: python3 mb_bridge.py (needs only numpy; takes about ten seconds)"""
import numpy as np
M, f0, zeta, G, C, alpha, gamma = 1.13e5, 0.8, 0.01, 35.0, 16.0, np.pi / 2, 0.8 # illustrative values
Om = 2 * np.pi * f0
B, K = 2 * zeta * M * Om, M * Om**2
T = min(5 * gamma, 0.8 * Om) # the Lorentzian of walking frequencies is cut at +-T
Z = 2 / np.pi * np.arctan(T / gamma) # share of the Lorentzian that is kept
Nc = Z * 2 * gamma * B * Om / (G * C * np.sin(alpha))
print(f"omega0 = {Om:.3f} rad/s, B = {B:.0f} kg/s, Z = {Z:.4f}, critical crowd Nc = {Nc:.1f}")
def walkers(N):
"""natural frequencies at the quantiles of the cut Lorentzian, golden-ratio phases (no random numbers)"""
u = (np.arange(N) + 0.5) / N
w = Om + gamma * np.tan(np.arctan(T / gamma) * (2 * u - 1))
th = 2 * np.pi * ((np.arange(N) * 0.6180339887498949) % 1)
p = (np.arange(N) * 7919) % N, (np.arange(N) * 4973) % N
return w[p[1]], th[p[0]]
def rhs(s, w, zeta=zeta):
X, V, th = s[0], s[1], s[2:]
A, psi = np.hypot(X, V / Om), np.arctan2(X, V / Om)
return np.concatenate(([V, (G * np.sin(th).sum() - 2 * zeta * M * Om * V - K * X) / M], w + C * A * np.sin(psi - th + alpha)))
def rk4(s, w, h, **k):
k1 = rhs(s, w, **k); k2 = rhs(s + h / 2 * k1, w, **k); k3 = rhs(s + h / 2 * k2, w, **k); k4 = rhs(s + h * k3, w, **k)
return s + h / 6 * (k1 + 2 * k2 + 2 * k3 + k4)
def wobble(N, Tend=1500.0, h=0.08, **k):
"""mean amplitude A (m) and order parameter r over the last 150 s"""
w, th = walkers(N); s = np.concatenate(([1e-3, 0.0], th)); n = round(Tend / h); a = []; r = []
for i in range(n):
s = rk4(s, w, h, **k)
if i * h > Tend - 150 and i % 5 == 0:
A, psi = np.hypot(s[0], s[1] / Om), np.arctan2(s[0], s[1] / Om); a.append(A); r.append(abs(np.exp(1j * (s[2:] - psi)).mean()))
return np.mean(a), np.mean(r)
print("\n crowd A (cm) simulated A (cm) theory r simulated r theory")
for N in (100, 150, 200, 250):
A, r = wobble(N)
At = G / (Z * B * Om) * np.sqrt(N * (N - Nc)) if N > Nc else 0.0
rt = np.sqrt(1 - Nc / N) / Z if N > Nc else 0.0
print(f" {N:5d} {100 * A:12.2f} {100 * At:13.2f} {r:11.3f} {rt:8.3f}")
print("\nthe fix: with zeta = 3 % the critical crowd is", round(3 * Nc, 0), "- a crowd of 250 no longer wobbles")
A, r = wobble(250, zeta=0.03)
print(f" simulated: A = {100 * A:.2f} cm, r = {r:.2f}")
# a bad solver can fake a wobble: the bridge alone (no walkers), explicit Euler
for h in (0.002, 0.004, 0.01):
s = np.array([0.01, 0.0])
for _ in range(round(300 / h)):
d = np.array([s[1], (-2 * zeta * M * Om * s[1] - K * s[0]) / M]); s = s + h * d
print(f"Euler, bridge alone, h = {h}: amplitude after 300 s = {np.hypot(s[0], s[1] / Om):.2e} m (exact decay: {0.01 * np.exp(-zeta * Om * 300):.1e} m)")
A multi-storey building as a chain of masses and springs on a shake table. Choose a ground motion, let the storeys yield, weaken the first floor or put the whole building on isolators, and watch the drifts, hysteresis loops, response spectrum and energy balance. The time stepping is Newmark's method, and the page shows where it fails.
Simulation
The building on the shake table
computing…
Time0
Peak ground acc. (g)—
Peak roof displacement (cm)—
Max storey drift (%)—
Peak base shear—
Energy balance error—
The building is drawn relative to the ground, with the displacements magnified by the factor in the status line. Drift is the displacement between two floors divided by the storey height of \(3.2\,\mathrm{m}\); colors go from green (small) to red (3.5 % or more). The base shear is given as a fraction of the weight W.
Response spectrum
What this record does to each period
The spectrum gives, for a single-mass oscillator of period \(T\) and the damping you chose, the peak pseudo-acceleration it reaches under this ground motion. The building has one such oscillator per mode: the dashed lines mark its first three periods.
period \(T\) (s)
Mode
\(T\) (s)
Sa (g)
Participation \(\Gamma\)
Effective mass (%)
Modes are those of the fixed-base elastic building. The pseudo-acceleration is Sa =\(\omega^2\) Sd / g, computed with Newmark's method at \(h=0.005\,\mathrm{s}\) for 90 periods.
Hysteresis
Force against drift in one storey
Each storey is an elastic–plastic spring with a little hardening. Below the yield force the loop is a straight line and no energy is lost; past it the loop opens, and its area is the energy dissipated by damage.
drift (%)
The dashed lines are the yield forces. With kinematic hardening the loop for amplitude A has area \(4 F_y (A - \delta_y)(1 - r)\), where \(r=2\,\%\) is the post-yield stiffness ratio.
Incremental dynamic analysis
Peak drift against shaking strength
not run
The same record shape is scaled to ten peak accelerations, and the building is simulated three ways: fixed base, with a weak first storey, and on isolators. The curves show the largest storey drift. A building that has a weak storey ends up with a drift far larger than the uniform one at the same shaking.
peak ground acceleration (g)
Uses the building settings above, with the weak-storey strength you set, the isolated period you set, and the current record type and seed. The red dashed line is 4 % drift, an illustrative level of very severe damage, not a code limit.
The solver
How large can the step be?
For the elastic building shaken at its own frequency, the peak roof displacement from each method is divided by a very fine reference. Newmark's average acceleration stays accurate until the step is comparable to the shortest period; linear acceleration and central difference blow up once \(\omega_{\max}\cdot h\) passes \(2\sqrt{3}\) and 2.
step \(h\) (s, logarithmic)
step \(h\) (s)
average acc.
linear acc.
central diff.
The dashed vertical lines are the two stability limits, \(2/\omega_{\max}\) and \(2\sqrt{3}/\omega_{\max}\), for the highest mode of the building as set above. Average acceleration never blows up, but with large steps it distorts the high-frequency response.
History
From strong-motion records to isolators
The first strong-motion accelerograph records were made in the 1933 Long Beach earthquake, and the El Centro record of 1940 became the standard test motion. Maurice Biot introduced the response spectrum in the early 1930s; George Housner made it a working tool in the 1940s and 1950s.
Nathan Newmark published his family of integration methods in 1959, and it remains the workhorse of structural dynamics. The filtered-noise ground motion used here is the model of Kanai (1957) and Tajimi (1960).
Soft ground storeys, open for shops or parking, have collapsed in many earthquakes, from San Fernando in 1971 to Northridge in 1994 and Türkiye in 2023. Modern base isolation, with lead-rubber bearings invented by William Robinson in New Zealand in the 1970s, is the opposite remedy: let the ground move and keep the building still.
Honest limits
What this model leaves out
It is a shear building: rigid floors, columns that act as horizontal springs, motion in one direction, no torsion, no P–\(\Delta\) effect, no soil–structure interaction. Real buildings have beams, joints, walls, and many more ways to fail.
The ground motions are synthetic, scaled to the peak you choose, not recorded earthquakes. Damping is Rayleigh damping from the initial stiffness, which overestimates damping once storeys yield. The isolator is a bilinear spring.
Nothing here is a design tool. The drift colors and the 4 % line are illustrations. The point is the mechanics and the numerics: modes, spectra, hysteresis, energy, and the limits of the integrator.
At a glance
The formulas
Equation of motion (displacements relative to the ground):
Questions and worked answers, from the equation of motion to the limits of the integrator. Open each one and try before reading.
Basics
What is a shear building?
An idealization in which each floor is a rigid slab carrying a lumped mass \(m_i\) and the columns between two floors act as a horizontal spring of stiffness \(k_i\) (the storey stiffness). The only unknowns are the horizontal floor displacements \(u_1,\dots,u_n\), and the storey drift is \(d_i=u_i-u_{i-1}\) with \(u_0=0\). The stiffness matrix is tridiagonal: \(K_{ii}=k_i+k_{i+1}\), \(K_{i,i+1}=-k_{i+1}\).
Why does the ground motion act as a force on the masses?
Write the absolute displacement of floor \(i\) as \(u_g(t)+u_i(t)\), where \(u_i\) is measured relative to the ground. Newton's law for the masses gives \(M(\ddot u_g\mathbf 1+\ddot{\mathbf u})+C\dot{\mathbf u}+f(\mathbf u)=0\), because springs and dampers feel only relative motion. Moving \(M\ddot u_g\mathbf 1\) to the right gives \(M\ddot{\mathbf u}+C\dot{\mathbf u}+f(\mathbf u)=-M\mathbf 1\,a_g(t)\): the building behaves as if each mass were pushed by \(-m_ia_g\).
What are modes, and why do they matter?
Free undamped vibration \(\mathbf u=\boldsymbol\phi\cos\omega t\) requires \(K\boldsymbol\phi=\omega^2M\boldsymbol\phi\), an eigenvalue problem solved here by Jacobi rotations. For equal masses and stiffnesses the frequencies are exactly \(\omega_j=2\sqrt{k/m}\,\sin\frac{(2j-1)\pi}{2(2n+1)}\), which the app compares with the numerical eigenvalues (they agree to 15 digits). For five storeys the periods are in the ratio \(1:0.34:0.22:0.17:0.15\), so a tall building is much more sensitive to long periods. In the elastic case each mode is an independent oscillator driven by \(-\Gamma_ja_g\).
The numerical method
How does Newmark's method work?
It advances displacement and velocity from the accelerations at both ends of the step: \(u_{k+1}=u_k+h\dot u_k+h^2[(\tfrac12-\beta)\ddot u_k+\beta\ddot u_{k+1}]\), \(\dot u_{k+1}=\dot u_k+h[(1-\gamma)\ddot u_k+\gamma\ddot u_{k+1}]\). Putting these into the equation of motion at \(t_{k+1}\) leaves one unknown, \(\ddot u_{k+1}\), found from a linear (or, with yielding, Newton) system with the tridiagonal matrix \(M+\gamma hC+\beta h^2K_t\). With \(\gamma=\tfrac12\) the method is second order; \(\beta=\tfrac14\) is the average acceleration rule, unconditionally stable for linear systems. The app measures the second-order convergence against a fine reference (errors fall by a factor near 4 when the step is halved).
Where do the limits 2 and \(2\sqrt{3}\) come from?
For an undamped mode with \(\omega h=z\), one step multiplies the state by a matrix whose eigenvalues stay on the unit circle exactly when \(z\le 1/\sqrt{\gamma/2-\beta}\) (for \(\gamma=\tfrac12\) and \(\beta\lt \tfrac14\)). Central difference (\(\beta=0\)) gives \(z\le2\), linear acceleration (\(\beta=\tfrac16\)) gives \(z\le\sqrt{12}=2\sqrt3\approx3.46\). The limit applies to the highest mode, even if it hardly matters physically: for the five-storey default \(\omega_{\max}\approx 85\) rad/s, so the explicit scheme needs \(h<0.024\) s. Preset 6 uses \(h=0.03\) s and the displacements blow up within a few steps.
How is yielding handled?
Each storey spring is a one-dimensional elastic–plastic element with linear kinematic hardening. Given the drift, a trial elastic force is computed; if it lies outside the yield band, the excess is returned to the band by a plastic increment (return mapping), and the tangent stiffness drops to \(r\,k\). The Newmark step then needs Newton iterations on the acceleration; two or three iterations per step are enough, as the page's tests show. Energy is the check: kinetic + damping + internal work must equal the input energy, and the error stays below \(10^{-3}\).
Seismic behavior
What does the response spectrum tell me?
For each period \(T\), take a single-mass oscillator of that period, shake it with the record, and keep the peak displacement \(S_d\); the pseudo-acceleration is \(S_a=\omega^2S_d\). Reading the building's periods on the curve gives the peak response of each mode, and combining the modes by the square root of the sum of squares (SRSS) estimates the roof displacement. For the elastic cases the page prints that estimate next to the time-history peak; they typically agree within 10 to 20 %. For a harmonic record at resonance the spectrum rises to \(a_0/(2\zeta)\), checked in the tests.
What are ductility and hysteretic damping?
Ductility demand is the peak drift divided by the yield drift, \(\mu=\delta_{\max}/\delta_y\); the page prints it for the storey you choose. A yielding storey dissipates energy in every cycle by the area of its loop, \(4F_y(A-\delta_y)(1-r)\) for amplitude \(A\), which is why a building can survive shaking that would be far larger than its elastic strength. The price is damage: the same loops that save the structure are cracked columns.
Why do soft storeys fail?
If one storey is weaker than the others, it yields first, and then its stiffness falls to \(r\,k\), so it can no longer pass much force to the storeys above. They stay nearly elastic and move almost as a rigid block, so all the deformation is concentrated in the weak storey. In the soft-storey scenario (6 storeys, 0.45 g, first storey at 45 % strength, default record) the maximum drift is about 4.1 % in storey 1, against about 2.1 % for the uniform building; the scan shows the gap widening with the shaking.
How does base isolation help?
The isolators are soft in the horizontal direction (here an effective period of \(2.6\,\mathrm{s}\)) and the structure above moves almost as a rigid body on top of them. A longer period means a lower spectral acceleration for typical earthquake records, so the superstructure sees smaller forces and drifts: in the last two scenarios the peak storey drift drops to a few tenths of a percent, while the isolator takes the displacement (about 25 cm). The trade-offs are the large displacement that must be accommodated, response to wind, and long-period or pulse-like ground motions, for which isolation helps much less (try the pulse record with a long isolated period).
Going further
Why not just use a tiny step with the explicit method?
You can, and for impact or wave problems that is the right choice, since explicit steps need no matrix solution. But the step must satisfy \(h<2/\omega_{\max}\), and \(\omega_{\max}\) grows as the model is refined (more storeys, stiffer elements). For earthquake problems the response is dominated by the first modes, with periods of seconds, so an unconditionally stable method with \(h\approx0.01\) s is far cheaper than an explicit one forced to follow the highest mode.
What does the energy balance verify?
Multiplying the equation of motion by \(\dot{\mathbf u}^T\) and integrating gives \(E_K+E_D+E_{\rm int}=E_I\) with \(E_I=-\int \dot{\mathbf u}^TM\mathbf 1\,a_g\,dt\). The page evaluates each integral with the trapezoid rule and prints \(|E_K+E_D+E_{\rm int}-E_I|/E_I\). It does not prove the result is right (a wrong model can conserve energy perfectly) but it catches bugs in the integrator, in the tangent stiffness and in the hysteresis update.
What would a real assessment add?
Real records scaled to a site-specific hazard spectrum, frame or fibre models with the actual beams, columns and joints, P–\(\Delta\) effects, torsion from asymmetric plans, soil–structure interaction and a statistical treatment: many records, many scalings, and fragility curves rather than one drift number. Incremental dynamic analysis, as in the scan here, is the first step in that direction; Vamvatsikos and Cornell (2002) is the standard reference.
References: A. Chopra, Dynamics of Structures (5th ed., Pearson, 2017); N. M. Newmark, A method of computation for structural dynamics, J. Eng. Mech. Div. ASCE 85 (1959) 67–94; R. W. Clough and J. Penzien, Dynamics of Structures (McGraw-Hill); K. Kanai, Bull. Earthq. Res. Inst. 35 (1957); H. Tajimi, Proc. 2nd World Conf. Earthq. Eng. (1960); D. Vamvatsikos and C. A. Cornell, Earthq. Eng. Struct. Dyn. 31 (2002) 491–514; J. M. Kelly, Earthquake-Resistant Design with Rubber (Springer, 1997).
Try it in Python
One script with only the standard library: the five-storey chain, its modes against the analytic formula, Newmark's average acceleration under harmonic shaking, the stability limits of the central difference and linear acceleration schemes, and the energy balance.
"""Earthquake shake table, in Python: a uniform n-storey shear building (equal masses m, equal storey stiffness k, fixed base, free top) under
harmonic ground acceleration. Modes against the exact formula, Newmark's average acceleration method, the stability limits of the
central difference (omega h <= 2) and linear acceleration (omega h <= 2 sqrt 3) schemes, and the energy balance. Standard library only."""
from math import sin, cos, pi, sqrt
def build(n, T1, m=2.0e5, zeta=0.02):
k = m * (2 * pi / T1 / (2 * sin(pi / (4 * n + 2)))) ** 2
K = [[0.0] * n for _ in range(n)]
for i in range(n):
K[i][i] = k + (k if i + 1 < n else 0)
if i + 1 < n: K[i][i + 1] = K[i + 1][i] = -k
return k, m, K
def jacobi(A):
n = len(A); A = [r[:] for r in A]
for _ in range(60):
off = sum(A[i][j] ** 2 for i in range(n) for j in range(i + 1, n))
if off < 1e-24: break
for p in range(n):
for q in range(p + 1, n):
if abs(A[p][q]) < 1e-300: continue
th = (A[q][q] - A[p][p]) / (2 * A[p][q]); t = (1 if th >= 0 else -1) / (abs(th) + sqrt(th * th + 1)); c = 1 / sqrt(t * t + 1); s = t * c
for r in range(n): A[r][p], A[r][q] = c * A[r][p] - s * A[r][q], s * A[r][p] + c * A[r][q]
for r in range(n): A[p][r], A[q][r] = c * A[p][r] - s * A[q][r], s * A[p][r] + c * A[q][r]
return sorted(A[i][i] for i in range(n))
def solve(A, b):
n = len(b); M = [A[i][:] + [b[i]] for i in range(n)]
for i in range(n):
piv = max(range(i, n), key=lambda r: abs(M[r][i])); M[i], M[piv] = M[piv], M[i]
for r in range(i + 1, n):
f = M[r][i] / M[i][i]
for c in range(i, n + 1): M[r][c] -= f * M[i][c]
x = [0.0] * n
for i in range(n - 1, -1, -1): x[i] = (M[i][n] - sum(M[i][j] * x[j] for j in range(i + 1, n))) / M[i][i]
return x
def newmark(n, T1, f, amp, zeta, h, T, beta=0.25, gamma=0.5):
"""returns peak roof displacement and the energy balance error; ground acceleration amp*sin(2 pi f t)"""
k, m, K = build(n, T1); w2 = jacobi([[K[i][j] / m for j in range(n)] for i in range(n)])
wa, wb = sqrt(w2[0]), sqrt(w2[min(n - 1, 2)] if n > 1 else w2[0] * 2.25)
a0, a1 = 2 * zeta * wa * wb / (wa + wb), 2 * zeta / (wa + wb)
C = [[a0 * m * (i == j) + a1 * K[i][j] for j in range(n)] for i in range(n)]
u = [0.0] * n; v = [0.0] * n; ag0 = amp * sin(0.0); a = [-ag0] * n
mv = lambda A, x: [sum(A[i][j] * x[j] for j in range(n)) for i in range(n)]
Keff = [[m * (i == j) + gamma * h * C[i][j] + beta * h * h * K[i][j] for j in range(n)] for i in range(n)]
peak = 0.0; EI = ED = EW = 0.0; Ip = Dp = Wp = 0.0
for s in range(1, int(T / h) + 1):
t = s * h; ag = amp * sin(2 * pi * f * t)
up = [u[i] + h * v[i] + h * h * (0.5 - beta) * a[i] for i in range(n)]; vp = [v[i] + h * (1 - gamma) * a[i] for i in range(n)]
rhs = [-m * ag - c - kk for c, kk in zip(mv(C, vp), mv(K, up))]
an = solve(Keff, [rhs[i] - 0 for i in range(n)]) # (M + gamma h C + beta h^2 K) a_new = -M ag - C vp - K up
u = [up[i] + beta * h * h * an[i] for i in range(n)]; v = [vp[i] + gamma * h * an[i] for i in range(n)]; a = an
peak = max(peak, abs(u[-1]))
Ik = -m * sum(v) * ag; Dk = sum(x * y for x, y in zip(v, mv(C, v))); Wk = sum(x * y for x, y in zip(v, mv(K, u)))
EI += 0.5 * h * (Ip + Ik); ED += 0.5 * h * (Dp + Dk); EW += 0.5 * h * (Wp + Wk); Ip, Dp, Wp = Ik, Dk, Wk
EK = 0.5 * m * sum(x * x for x in v)
return peak, abs(EK + ED + EW - EI) / max(EI, 1e-30)
def central_difference_stable(n, T1, f, amp, zeta, h, T):
"""explicit central difference; returns True if the roof displacement stays finite and small"""
k, m, K = build(n, T1); w2 = jacobi([[K[i][j] / m for j in range(n)] for i in range(n)])
wa, wb = sqrt(w2[0]), sqrt(w2[min(n - 1, 2)])
a0, a1 = 2 * zeta * wa * wb / (wa + wb), 2 * zeta / (wa + wb)
C = [[a0 * m * (i == j) + a1 * K[i][j] for j in range(n)] for i in range(n)]
Lh = [[m / h ** 2 * (i == j) + C[i][j] / (2 * h) for j in range(n)] for i in range(n)]
u = [0.0] * n; up = [0.5 * h * h * 0.0] * n
mv = lambda A, x: [sum(A[i][j] * x[j] for j in range(n)) for i in range(n)]
for s in range(int(T / h)):
ag = amp * sin(2 * pi * f * s * h); Ku = mv(K, u); Cu = mv(C, up)
rhs = [-m * ag - Ku[i] + 2 * m * u[i] / h ** 2 - m * up[i] / h ** 2 + Cu[i] / (2 * h) for i in range(n)]
un = solve(Lh, rhs); up, u = u, un
if abs(u[-1]) > 1e3 or u[-1] != u[-1]: return False
return True
if __name__ == "__main__":
n, T1 = 5, 0.5; k, m, K = build(n, T1)
w = [sqrt(x) for x in jacobi([[K[i][j] / m for j in range(n)] for i in range(n)])]
exact = [2 * sqrt(k / m) * sin((2 * j - 1) * pi / (2 * (2 * n + 1))) for j in range(1, n + 1)]
print("periods (s):", [round(2 * pi / x, 4) for x in w]); print("max relative error against the exact formula:", max(abs(a - b) / b for a, b in zip(w, exact)))
ref = newmark(n, T1, 1 / T1, 0.5, 0.02, 0.0025, 8)[0]
for h in (0.02, 0.01, 0.005):
p, e = newmark(n, T1, 1 / T1, 0.5, 0.02, h, 8); print(f"h = {h}: peak roof {p:.5f} m (error vs h = 0.0025: {abs(p - ref):.2e}), energy error {e:.1e}")
wmax = w[-1]; print("omega_max =", round(wmax, 1), "-> central difference needs h <", round(2 / wmax, 4))
for h in (0.019, 0.022, 0.026, 0.03): print(f"central difference, h = {h}:", "stable" if central_difference_stable(n, T1, 1 / T1, 0.5, 0.02, h, 4) else "blows up")
Cast one light ray at a time past a non-rotating black hole and watch it bend, loop around or fall in. The whole problem is one ordinary differential equation, \(u''+u=3u^2\) with \(u=1/r\), so you can solve it yourself with Euler, Heun or Runge–Kutta and see how the step size decides what the black hole “does”.
Simulation
One ray at a time
ready
Choose the height b of the incoming ray with the slider, a preset, or by clicking in the top view; then press Cast. With sound on, the pitch rises as the ray dives toward the hole, and a bell rings when it falls in.
angle swept \(\psi\) (degrees) · vertical axis: \(u=1/r\), in units of \(1/M\)
impact parameter \(b\) (M)—
distance \(r\) (M)—
angle swept \(\psi\) (°)—
first-integral error—
result—
Units: \(G=c=1\) and lengths in \(M=GM_{\text{hole}}/c^2\) (about \(1.48\,\mathrm{km}\) per solar mass). The left picture is an embedding diagram: only the geometry of space is drawn, and the ray is lifted onto the funnel for show; the ray itself is computed in the flat plane of the top view. The black-hole picture is an illustration, not to scale. The dotted gray ray is a reference solution (RK4 with \(h=0.002\)). The first-integral error is \(|b^2(v^2+u^2-2u^3)-1|\), zero for an exact ray.
Deflection
How much does it bend?
For rays that escape, the bending angle \(\alpha\) grows as \(b\) shrinks and diverges at \(b_c\). Far away it follows Einstein's \(4M/b\) (twice the Newtonian guess), and a second term corrects it closer in. Your cast rays appear as dots.
The green curve is computed by this very program with RK4 and \(h=0.01\). The vertical axis stops at 260°, but the curve keeps rising: \(\alpha\to\infty\) as \(b\to b_c\), because a ray that arrives almost exactly at \(b_c\) winds around the photon sphere as many times as you like.
Error meter
The first integral as a check
Along an exact ray the quantity \(v^2+u^2-2u^3\) equals \(1/b^2\) at every angle. The numerical solution does not know this, so its departure from it is a free measure of error, the same trick as the energy plot of the Tacoma app. Euler lets it drift upward; Heun and RK4 hold it far better.
angle swept \(\psi\) (degrees) · vertical axis: error of the first integral, logarithmic
This plot uses the \(b\) now set on the slider and the current step; rays that fall in stop early. Euler's error is systematic: the quantity drifts upward, which is the same as sending in a ray with a smaller \(b\). That is why Euler “captures” rays that should escape (try the experiment “Euler with a big step lies”).
Capture boundary
Find \(b_c\) by bisection
idle
Bisect on the single yes/no question “does the ray fall in?”, between \(b=4\) (falls) and \(b=6.5\) (does not), with 32 halvings, using each method at the current step. The exact answer, \(3\sqrt3\,M\), comes from the first integral by hand (see the course below).
method
estimate of \(b_c\)
error
halvings
Euler's error is of first order in \(h\): halve the step and the error roughly halves. Heun's is of second order and RK4's of fourth, so RK4 already has eight digits at \(h=0.02\). The rays near \(b_c\) are the hardest: a small mistake at the start grows like \(e^{\psi}\) while the ray winds around the photon sphere.
Order of convergence
How fast does the answer improve?
The deflection angle at \(b=8\) is computed with each method at seven step sizes and compared with a very fine RK4 solution (\(h=0.0005\)). On a log–log plot the error is a straight line whose slope is the order of the method: 1, 2 and 4.
angle step \(h\) (radians, logarithmic) · vertical axis: error of \(\alpha\) in radians, logarithmic
method
observed order
error at \(h=0.1\)
error at \(h=0.025\)
Orders are least-squares slopes over the four smallest steps. They come out clean (1, 2, 4) because the point where the ray reaches the horizon, or comes back out, is located with the same method and not by a cruder interpolation; with linear interpolation the order of RK4 would drop to about 2.
How it works
From general relativity to one ODE
Light in the equatorial plane of a Schwarzschild black hole follows a null geodesic. Two conserved quantities, energy and angular momentum, whose ratio is the impact parameter \(b\), reduce the motion to \((dr/d\lambda)^2=1-b^2(1-2M/r)/r^2\) and \(d\varphi/d\lambda=b/r^2\), with \(\lambda\) an affine parameter. Dividing the two and putting \(u=1/r\) gives the orbit equation \(u''+u=3Mu^2\).
Without the term \(3Mu^2\) the equation would be \(u''+u=0\), whose solutions are the straight lines \(u=\sin\psi/b\): that term is all of the gravity. The problem is a second-order ODE with initial values \(u_0=1/r_0\) and \(u'_0\) fixed by \(b\), integrated in the angle \(\psi\), and the only events are \(u=1/2\) (the ray reaches the horizon: captured) and \(u\) back at \(u_0\) (the ray escapes). Nothing else is needed.
Why the angle and not time? Coordinate time freezes at the horizon, while in the angle the equation is smooth everywhere. The movie, however, runs in the affine parameter, for which \(d\psi/d\lambda=bu^2\): far away \(\lambda\) is almost the ordinary time, and near the hole the ray hurries around it.
Honest notes
What the picture does and does not show
The funnel is Flamm's paraboloid, \(z=2\sqrt{2M(r-2M)}\), the surface whose own geometry reproduces the curvature of space (not of spacetime) in the equatorial plane. It is a drawing aid. Light does not run along that surface, and its path on it is not a geodesic of the surface: the ray is computed from the equation above and then lifted onto the funnel for show. The rubber sheet with a ball is a fair first picture, but the funnel is not “what gravity does to light”.
What the model includes: a non-rotating, uncharged hole (Schwarzschild), light treated as rays, motion in one plane, and a ray that does not disturb the hole. The 1919 eclipse expeditions tested the prediction \(4M/b\approx1.75''\) at the rim of the Sun, twice what Newtonian gravity gives for a particle moving at the speed of light (Soldner, 1801). For a hole of one solar mass \(M\approx1.48\,\mathrm{km}\), so \(b_c\approx7.7\,\mathrm{km}\).
What it leaves out: a real hole spins (Kerr), the gas around it glows and its light is bent into the rings of the picture, and a real image needs a great many rays traced backward from the camera. The glowing disc at the bottom is only an illustration. The photon sphere \(r=3M\) is where light can circle the hole on an unstable orbit; the shadow that telescopes see, as in the 2019 image of M87*, has radius \(b_c=3\sqrt3\,M\), not \(3M\), because it is set by the impact parameter at infinity and not by a position.
At a glance
The equations and the tools
Null geodesic in the equatorial plane (\(\lambda\) affine, \(b=L/E\)).
The funnel of the picture (embedding diagram, geometry only).
\[z(r)=2\sqrt{2M(r-2M)}\qquad(\text{Flamm's paraboloid: the funnel of the picture})\]
Course
From light to an ODE solver
Four groups: the physics, the ODE and the methods, the numerical traps, and experiments with exercises. Open a question, think, and then read the answer.
1. The physics
Why does light bend at all?
In general relativity gravity is the curvature of spacetime, and light, which has no mass to push around, simply follows the straightest possible paths in that curved spacetime, the null geodesics. Near a mass these paths are no longer straight lines in the space we draw. A Newtonian argument that treats light as particles of speed \(c\) also predicts a bend, but half as large: \(2M/b\) against Einstein's \(4M/b\). The other half comes from the curvature of space itself, not only from the time part of the metric.
What are the horizon, the photon sphere and the shadow?
The horizon \(r=2M\) is the point of no return: nothing inside can get out, light included. At \(r=3M\), the photon sphere, light can travel in a circle around the hole, but the orbit is unstable like a ball balanced on a hilltop: the slightest push sends the light in or out. A ray arriving from far away is captured if its impact parameter is below \(b_c=3\sqrt3\,M\approx5.196\,M\), which is not \(3M\) because of the bending on the way in. The dark disc that a distant observer sees, the shadow, has radius \(b_c\).
What is the impact parameter?
Imagine the ray coming from very far away along a straight line. The impact parameter \(b\) is the distance between that line and the parallel line through the hole: the height of the ray in the top view. It is the ratio \(L/E\) of two conserved quantities, angular momentum and energy, so it is the same at every point of the ray and it alone decides the whole orbit. That is why the app needs only one number to launch a ray: \(b=8\) always gives the same curve.
2. The ODE and the methods
How do we get from geodesics to \(u''+u=3Mu^2\)?
Write the conserved quantities \(E=(1-2M/r)\dot t\) and \(L=r^2\dot\varphi\) (dots are derivatives in the affine parameter) and the null condition \(0=-(1-2M/r)\dot t^2+\dot r^2/(1-2M/r)+r^2\dot\varphi^2\). Eliminating \(t\) gives \(\dot r^2=E^2-(L^2/r^2)(1-2M/r)\). With \(u=1/r\) and \(du/d\varphi=-\dot r/L\) this becomes \((du/d\varphi)^2=1/b^2-u^2+2Mu^3\), the first integral. Differentiating once in \(\varphi\) and dividing by \(2u'\) gives \(u''+u=3Mu^2\).
Only the last two lines are needed in this course; the derivation from the metric is a good exercise for a relativity class.
How is it written for Euler, Heun and RK4?
Put \(y_1=u\) and \(y_2=u'\). Then \(y_1'=y_2\) and \(y_2'=3y_1^2-y_1\) (with \(M=1\)): a first-order system \(\mathbf y'=\mathbf f(\mathbf y)\) in which the angle \(\psi\) does not appear on the right (an autonomous system). Euler: \(\mathbf y_{n+1}=\mathbf y_n+h\,\mathbf f(\mathbf y_n)\). Heun: average the slope at the start and at the Euler prediction. RK4: four slopes with weights \(1,2,2,1\) over 6. The file bh-core.js has the three in about twenty lines, and so has the Python script at the end of the page.
How is “falls in” decided?
The horizon is at \(u=1/2\). After each step the program checks whether \(u\ge1/2\); if so the ray is captured, the bell rings, and the last step is shortened so that the path ends exactly on the horizon. The ray escapes when \(u'<0\) and \(u\) is back at its starting value \(u_0=1/r_0\). If neither happens after \(14\pi\) radians the ray is declared undecided, still circling. Locating the last step is a root-finding problem: a few Newton iterations on the fraction of the step, done with the same method, so the method keeps its order.
What is the deflection angle, and how is it measured?
With no gravity the ray would sweep the polar angle from \(\psi_0=\arcsin(b/r_0)\) to \(\pi-\psi_0\). The bent ray sweeps a larger angle \(\psi_{\text{exit}}-\psi_0\), and the difference is the deflection \(\alpha=(\psi_{\text{exit}}-\psi_0)-\pi+2\psi_0\). The program starts at \(r_0=1000M\), far enough that the result is close to the value for a ray coming from infinity. To first order \(\alpha=4M/b\); the expansion continues as \(4M/b+15\pi M^2/(4b^2)+128M^3/(3b^3)+\dots\), and it fails near \(b_c\), where \(\alpha\) diverges.
3. Numerical traps
Why is the answer so sensitive near \(b_c\)?
Linearize the orbit equation about the circular photon orbit \(u=1/3\): with \(u=1/3+\delta\) we get \(\delta''=\delta+3\delta^2\), so small errors grow like \(e^{\psi}\) (a saddle, not a centre). A ray whose \(b-b_c\) is \(10^{-6}\) spends about \(\ln10^{6}\approx14\) radians, more than two turns, near the photon sphere before it decides. A numerical error of that size is amplified in the same way, so locating \(b_c\) to many digits needs a method whose error is far below the digits wanted. RK4 with \(h=0.02\) has it; Euler does not.
What exactly does Euler do wrong here?
The equation is a nonlinear oscillator, \(u''=-u+3u^2\). Euler's method applied to an oscillator multiplies the amplitude by \(\sqrt{1+h^2}\) at every step: the numerical solution gains energy. Here the energy is the first integral \(1/b^2\), and gaining it means behaving like a ray with a smaller \(b\): Euler pulls in rays that should pass. Heun's amplification factor is \(\sqrt{1+h^4/4}\), smaller by two powers of \(h\), and RK4's is about \(1-h^6/144\) (it loses a little), so these two are far better. The same stability study is done in the Tacoma app.
Why integrate in the angle and not in time?
In Schwarzschild coordinate time a ray approaching the horizon slows down and never gets there: \(dr/dt\to0\). A method marching in \(t\) would crawl and never see the capture. In the angle \(\psi\) the equation \(u''=3u^2-u\) is smooth everywhere and the horizon is simply the value \(u=1/2\). For the movie the app uses the affine parameter, \(d\psi/d\lambda=bu^2\), which far away is almost the ordinary time and keeps the motion lively near the hole.
What goes wrong for a nearly head-on ray?
When \(b\to0\) the ray goes almost straight in and the angle \(\psi\) hardly changes, so a fixed step in \(\psi\) covers nearly the whole fall: the slider stops at \(|b|=1\) for that reason. For radial rays one uses another formulation, for instance \(r(\lambda)\) with \((dr/d\lambda)^2=1-b^2(1-2M/r)/r^2\), or an adaptive step. Choosing the independent variable well is part of the modelling.
4. Experiments and exercises
Experiment: when does Euler lie?
Choose “Euler with a big step lies” (\(b=5.3\), \(h=0.1\)). The exact answer is that the ray escapes, bent by 204°, but Euler makes it fall in, because its first-integral error moves the effective critical value up to \(b_c\approx5.356\). Press “Run the bisection” to see that number. Then lower \(h\) with the slider: below about \(h=0.07\) the ray escapes again, and the estimate of \(b_c\) tends to \(5.196\) from above, roughly in proportion to \(h\). Do the same with Heun and with RK4 at \(h=0.1\): both get the fate right.
Experiment: counting the loops
Cast \(b=6\), \(5.3\) and \(5.2\) in turn with the experiment menu and read the bending in the status line: 99°, 204° and 390°. At \(b=5.2\), only \(0.004\) above \(b_c\), the ray goes more than once around the hole before leaving. By the linearization of the previous section each extra full turn needs the ray to be \(e^{2\pi}\approx535\) times closer to \(b_c\), so the slider's step of \(0.05\) is far too coarse for a second loop; use the bisection of the Python script. Does the logarithmic law \(\alpha\approx-\ln(b-b_c)+\text{const}\) fit the green curve near \(b_c\)?
Exercises
Show from the first integral that capture happens exactly for \(b<3\sqrt3\,M\): the ray cannot turn back when \(F(u)=1/b^2-u^2+2u^3\) stays positive on \([0,1/2]\); \(F\) has its minimum there at \(u=1/3\), where \(F=1/b^2-1/27\).
Write Euler, Heun and RK4 for \(u''+u=3u^2\) in your own language and reproduce the table of the capture boundary.
Use \(\alpha\approx4M/b\) to estimate the bending of light at the rim of the Sun (\(M=1.48\,\mathrm{km}\), \(b=6.96\times10^{5}\,\mathrm{km}\)) in arcseconds, and compare with \(1.75''\).
Take \(r\) as the independent variable and the affine parameter as time, and try to integrate a ray with \(b\) just above \(b_c\). What happens at the turning point, where \(dr/d\lambda=0\)?
With the linearization about \(u=1/3\), estimate how many turns around the hole a ray with \(b-b_c=10^{-8}M\) makes.
References: Misner, Thorne and Wheeler, Gravitation (1973), chapter 25. Chandrasekhar, The Mathematical Theory of Black Holes (1983). Flamm, Physikalische Zeitschrift 17 (1916) 448–454. Dyson, Eddington and Davidson, Philosophical Transactions of the Royal Society A 220 (1920) 291–333 (the 1919 eclipse). Luminet, Astronomy and Astrophysics 75 (1979) 228–235 (the first computed image of a black hole with a disc). Event Horizon Telescope Collaboration, Astrophysical Journal Letters 875 (2019) L1.
Try it in Python
The same ODE and the same three methods in a short script, standard library only: deflection angles, the capture boundary by bisection and the observed orders of convergence.
#!/usr/bin/env python3
"""Light bending near a Schwarzschild black hole with nothing but an ODE solver (standard library only).
Units G = c = 1 and M = 1. For a light ray in the equatorial plane put u = 1/r and use the polar angle psi as the
independent variable. The orbit equation is
u'' + u = 3 u^2 (' = d/dpsi), first integral: u'^2 + u^2 - 2 u^3 = 1/b^2 ,
where b is the impact parameter. The horizon is u = 1/2. A ray with b < 3*sqrt(3) = 5.196... falls in, a ray with
b > 3*sqrt(3) comes back out, bent by the angle alpha. Run: python3 black_hole_rays.py
"""
import math
BC = 3 * math.sqrt(3) # critical impact parameter
R0 = 1000.0 # start (and finish) distance for the deflection angle
def f(y):
"""right-hand side of the first-order system y = (u, v), v = du/dpsi"""
return (y[1], 3 * y[0] ** 2 - y[0])
def step(method, y, h):
k1 = f(y)
if method == "euler":
return (y[0] + h * k1[0], y[1] + h * k1[1])
if method == "heun":
k2 = f((y[0] + h * k1[0], y[1] + h * k1[1]))
return (y[0] + h / 2 * (k1[0] + k2[0]), y[1] + h / 2 * (k1[1] + k2[1]))
k2 = f((y[0] + h / 2 * k1[0], y[1] + h / 2 * k1[1]))
k3 = f((y[0] + h / 2 * k2[0], y[1] + h / 2 * k2[1]))
k4 = f((y[0] + h * k3[0], y[1] + h * k3[1]))
return tuple(y[i] + h / 6 * (k1[i] + 2 * k2[i] + 2 * k3[i] + k4[i]) for i in range(2))
def locate(method, y, h, target, frac):
"""fraction of the step h at which the method reaches u = target (Newton iterations)"""
for _ in range(6):
z = step(method, y, frac * h)
frac = min(1.0, max(0.0, frac - (z[0] - target) / (z[1] * h)))
return frac
def trace(b, method="rk4", h=0.02, max_psi=14 * math.pi):
"""integrate one ray; returns ('captured' | 'escaped' | 'orbit', deflection angle in radians or None)"""
u0 = 1 / R0
y = (u0, math.sqrt(1 / b ** 2 - u0 ** 2 * (1 - 2 * u0)))
psi0 = math.asin(b / R0)
psi = psi0
while psi - psi0 < max_psi:
z = step(method, y, h)
if z[0] >= 0.5: # reached the horizon
return "captured", None
if z[1] < 0 and z[0] <= u0: # back at the starting distance, moving outward
fr = locate(method, y, h, u0, (y[0] - u0) / (y[0] - z[0]))
return "escaped", (psi + fr * h - psi0) - math.pi + 2 * psi0
y, psi = z, psi + h
return "orbit", None
def critical(method, h):
"""critical impact parameter by bisection on 'is the ray captured?'"""
lo, hi = 4.0, 6.5
while hi - lo > 1e-9:
mid = (lo + hi) / 2
if trace(mid, method, h)[0] == "captured":
lo = mid
else:
hi = mid
return (lo + hi) / 2
if __name__ == "__main__":
print("critical impact parameter 3*sqrt(3) = %.9f" % BC)
print("\n b fate alpha (deg) 4/b (deg) 4/b + 15 pi/(4 b^2) (deg)")
for b in (14, 10, 8, 6, 5.3, 5.2, 5.0, 3.0):
fate, al = trace(b, "rk4", 0.005)
w1, w2 = 4 / b, 4 / b + 15 * math.pi / (4 * b * b)
print("%5.2f %-9s %12s %10.3f %14.3f" % (b, fate, "%.3f" % math.degrees(al) if al is not None else "-", math.degrees(w1), math.degrees(w2)))
print("\nbisection for b_c, error versus 3*sqrt(3):")
for h in (0.1, 0.02):
for m in ("euler", "heun", "rk4"):
bc = critical(m, h)
print(" h = %-5s %-6s b_c = %.6f error %+.2e" % (h, m, bc, bc - BC))
print("\norder of convergence of the deflection angle at b = 8:")
ref = trace(8, "rk4", 0.0005)[1]
for m in ("euler", "heun", "rk4"):
e1 = abs(trace(8, m, 0.05)[1] - ref)
e2 = abs(trace(8, m, 0.025)[1] - ref)
print(" %-6s error(h=0.05) = %.2e error(h=0.025) = %.2e observed order %.2f" % (m, e1, e2, math.log(e1 / e2) / math.log(2)))
Watch the formula appear out of nothing but algebra: each equation morphs into the next, terms slide across the equals sign, fade in, fade out, and the area of a square tells the same story. Then try numbers, and see why the formula as written can lose every digit in floating point.
Derivation
Completing the square
1 / 11
Play runs the whole derivation, or step with the buttons or the slider. Each frame keeps the pieces that do not change, slides the ones that move, fades out what disappears and fades in what is new. Everything is typeset with MathJax and animated in your browser; there is no video file. It assumes only that the coefficient a is not zero.
Numerical lab
Try your own numbers
Type \(a\), \(b\) and \(c\) (decimals and exponents such as 1e8 are fine) and see the roots computed three ways: by the formula as written, by the stable form, and exactly with 120-digit arithmetic. The last two columns give the relative error and the residual \(a x^2 + b x + c\) at each computed root.
Doubles are IEEE 754 binary64 (about 16 decimal digits). The exact column truncates the true root to 22 significant digits. Residuals in double precision are themselves rounded, so a residual near \(10^{-16}\) times the size of the terms means nothing worse than rounding. The graph is drawn in scaled units (horizontal: distance from the vertex in units of the root spacing; vertical: the value in units of the vertex height), so that it stays readable whatever the size of the coefficients.
Floating point
When the formula loses every digit
Take \(x^2 + b x + 1\) with \(b = 10^2\), \(10^3\), … \(10^{14}\). The small root is about \(-1/b\). The formula as written computes \(-b + \sqrt{b^2 - 4}\), subtracting two nearly equal numbers; the stable form \(q = -(b + \sqrt{b^2 - 4})/2\), \(x=1/q\) never subtracts. Relative error of the small root, against the exact value:
exponent \(k\) in \(b = 10^k\)
b
exact root
formula as written
stable form
The stable form is exact to the last digit in every row, so its error is drawn at the floor of the chart (\(10^{-17}\)). From \(b = 2^{27} \approx 1.34 \times 10^8\) the formula as written returns exactly 0 for the small root, because \(b^2 - 4\) rounds to \(b^2\).
History
Four thousand years of one formula
Babylonian scribes, roughly 4000 years ago, solved problems that amount to quadratic equations on clay tablets, with a recipe equivalent to completing the square and no symbols for the unknown. Greek geometers solved them with areas, much like the picture on this page.
In India, Brahmagupta (628) gave a rule that already treats negative and positive cases together, and al-Khwārizmī (about 820), whose book gave us the word algebra, taught completing the square with rectangles and with the six standard types of equation, since negative numbers were not yet used as coefficients.
The compact modern way of writing the formula, with one symbolic expression for both roots, is much more recent: it is a product of the symbolic algebra of the sixteenth and seventeenth centuries, when letters began to stand for coefficients.
Honest limits
What the animation does not show
The derivation is a proof only for \(a \ne 0\), and it takes square roots in the complex numbers when \(\Delta < 0\), where “\(x + b/2a = \pm \sqrt{\Delta} / 2a\)” means a pair of conjugate numbers. The area picture covers just the case \(a=1\) with a positive root; for other cases the algebra still works, the geometry needs signs.
The animation is a presentation, not a proof checker: each frame was written by hand and verified numerically (the Python script below checks every step with exact fractions). The lab uses the true value from exact arithmetic, but only for coefficients that are decimal numbers.
Floating point is a different problem from algebra: the formula is exactly right on the reals and still wrong on a computer. The stable form fixes cancellation, but it cannot fix a discriminant that is itself inaccurate: roots of a nearly double root are ill-conditioned whatever the formula.
At a glance
The derivation, the formula and the numerics
Three equivalent forms, ending in a perfect square:
Error of the formula as written when one root is much smaller than the other:
\[\text{naive small root: } \frac{|\hat x-x|}{|x|}\approx\varepsilon\,\frac{b^2}{|ac|}\quad(b^2\gg|4ac|),\qquad \varepsilon=2^{-53}\approx1.1\times10^{-16}\]
Course
From a clay tablet to a floating-point bug
Four groups: the idea, the mathematics, the numerics and exercises. Open a question, think, then read the answer.
1. The idea
Why complete the square at all?
The equation \(y^2=k\) is easy: \(y=\pm\sqrt k\). The trouble with \(x^2+px=q\) is that \(x\) appears twice, once squared and once alone. Completing the square rewrites the left side as one square, \((x+p/2)^2\), so that \(x\) appears once and the square root can peel it off. The geometric picture shows why it works: a square plus a strip of the same height is almost a bigger square, and the missing corner has area \((p/2)^2\).
Why must \(a\) be nonzero?
The first real step divides by \(a\). If \(a=0\) the equation \(bx+c=0\) is linear, with the single solution \(x=-c/b\), and the formula would divide by zero. There is a pleasant limit: as \(a\to0\) with \(b>0\), the root \(\frac{-b+\sqrt{b^2-4ac}}{2a}\) tends to \(-c/b\) while the other root runs off to infinity. The stable form \(x=c/q\) shows this limit at once, which the naive form hides behind \(0/0\). Try the preset with a tiny \(a\).
What does the \(\pm\) mean?
Both signs give a solution: if \(y^2=k\) then \((-y)^2=k\) as well. So the formula is a short way to write two numbers, \(x_1=\frac{-b+\sqrt\Delta}{2a}\) and \(x_2=\frac{-b-\sqrt\Delta}{2a}\), which are mirror images of each other around the vertex \(x=-b/2a\). If \(\Delta=0\) they coincide (a double root, the parabola just touches the axis); if \(\Delta<0\) they are complex conjugates and the parabola does not reach the axis. In the animation the sign appears at the square-root step and survives to the end.
2. The mathematics
Why is each step reversible?
Dividing by \(a\ne0\), adding the same number to both sides and rewriting an expression as an equal one can each be undone, so every equation in the chain has exactly the same solutions as the previous one. Only the square-root step needs care: from \(y^2=k\) to \(y=\pm\sqrt k\) it keeps both solutions. A concrete check: for any \(x\), the difference between the two sides of each of the first seven frames equals \(f(x)/a\) with \(f(x)=ax^2+bx+c\) (or \(f(x)\) itself in the first frame), so all seven vanish at exactly the same \(x\). The Python script below verifies this with exact fractions.
What does the discriminant tell us?
\(\Delta=b^2-4ac\) is the number under the root, and the root is real exactly when \(\Delta\ge0\). Geometrically, the parabola \(y=ax^2+bx+c\) has its vertex at \(x=-b/2a\) with height \(-\Delta/4a\), so it crosses the axis twice when \(a\) and \(-\Delta\) have the same sign (\(\Delta>0\)), touches it once when \(\Delta=0\), and misses it when \(\Delta<0\). The distance between the two roots is \(\sqrt\Delta/|a|\). For integer coefficients \(\Delta\) is an integer, and the roots are rational exactly when \(\Delta\) is a perfect square.
How can I check the formula without redoing the derivation?
Add and multiply the two roots, with \(s=\sqrt\Delta\): \(x_1+x_2=\frac{-b+s}{2a}+\frac{-b-s}{2a}=-\frac ba\) and \(x_1x_2=\frac{(-b)^2-s^2}{4a^2}=\frac{b^2-(b^2-4ac)}{4a^2}=\frac ca\). These are Viète's relations, and they say \(a(x-x_1)(x-x_2)=ax^2+bx+c\), which is the same as saying that \(x_1\) and \(x_2\) are the roots. The product relation is also the key to the stable form of the next section.
Is the picture a proof?
For \(a=1\), \(p>0\), \(q>0\) it is: a square of side \(x\) and a strip \(p\times x\) have area \(x^2+px=q\); cutting the strip in two halves \(\frac p2\times x\) and attaching them to two sides of the square gives an L-shape of area \(q\); adding the corner \((p/2)^2\) makes a square of side \(x+p/2\) and area \(q+(p/2)^2\), so \(x+\frac p2=\sqrt{q+p^2/4}\). That is exactly the formula for the positive root, \(x=-\frac p2+\sqrt{\frac{p^2}{4}+q}\). For other signs the pieces have negative lengths and the picture stops being a picture, while the algebra does not care.
3. Numerical analysis
Why does the formula lose digits?
When \(b^2\gg|4ac|\), \(\sqrt{b^2-4ac}\) is almost \(|b|\), and for one of the two signs the formula computes \(-b+\sqrt\Delta\) (for \(b>0\)) as the difference of two nearly equal numbers. Each of them carries a rounding error of about \(\varepsilon|b|\), with \(\varepsilon=2^{-53}\approx1.1\times10^{-16}\), and the subtraction cancels the leading digits and leaves that error in front: the absolute error of the result is still about \(\varepsilon|b|/(2|a|)\), while the root itself is only \(|c/b|\). The relative error is of order \(\varepsilon\,b^2/|ac|\). For \(x^2+10^8x+1\): the small root is \(-10^{-8}\), the formula in double precision returns \(-7.45\times10^{-9}\) (error 25 %), and for \(b\ge2^{27}\approx1.34\times10^8\) it returns exactly 0. The other root, \(-b-\sqrt\Delta\), has no cancellation and is fine.
What is the stable form and why does it work?
Compute first the root that has no cancellation, by choosing the sign that adds magnitudes: \(q=-\tfrac12\bigl(b+\operatorname{sgn}(b)\sqrt\Delta\bigr)\), so that \(q\) has the size of \(b\) and no digits are lost, and then \(x_1=q/a\). The other root comes from Viète's product \(x_1x_2=c/a\): \(x_2=\frac{c/a}{q/a}=c/q\). Both are divisions and sums of numbers of equal sign, so each has a relative error of a few units of \(\varepsilon\). In the table above the stable column is correct to the last printed digit in every row. This is what libraries do: the standard routines for quadratics (and for solving small polynomial systems) use this form, not the one in the textbook.
What other things can go wrong?
First, overflow: \(b^2\) exceeds the largest double (about \(1.8\times10^{308}\)) for \(|b|>1.3\times10^{154}\) even if the roots are modest; scaling all coefficients by a power of two fixes it without changing the roots. Second, a discriminant that is nearly zero: \(b^2\) and \(4ac\) nearly equal, so \(\Delta\) loses digits, and near a double root the roots are ill-conditioned, a perturbation \(\delta\) of the coefficients moves them by about \(\sqrt\delta\), so \(10^{-16}\) turns into \(10^{-8}\) no matter how the formula is written. A compensated computation of \(b^2-4ac\) (with a fused multiply-add) recovers the lost digits when the coefficients themselves are exact. Third, \(a\) tiny: with \(a=10^{-9}\) in the preset, the formula for the root that tends to \(-c/b\) loses digits, and the stable form does not.
4. Exercises
Exercise: use the picture
Set \(p=4\) and \(q=5\) in the area picture. The positive root is \(x=-2+\sqrt{4+5}=1\). Check it against the picture: the square has side 1, the two strips are \(2\times1\), the corner is \(2^2=4\), the big square has side \(x+2=3\) and area \(9=q+4\). Now take \(p=3\), \(q=10\) and compute \(x\) and the big square by hand before looking at the readout. Why does the readout never ask for \(q<0\)?
Exercise: find where the formula as written breaks
In the lab, type \(a=1\), \(c=1\) and \(b=10^6\), then \(10^7\), \(10^8\) and \(1.5\times10^8\). Compare the formula as written with the exact root and note where the relative error crosses 1 % and where it becomes exactly 1. Check the rule of thumb \(\varepsilon\,b^2/|ac|\): for \(b=10^6\) it predicts \(10^{-4}\); for \(b=10^8\) it predicts about \(1\). Then try \(a=1\), \(b=-10^8\), \(c=1\): which root is now the bad one, and why?
Exercise: complex roots
Take \(x^2+2x+5\), whose discriminant is \(-16\). The lab prints \(x=-1\pm2i\); check that \((x+1)^2=-4\) in the form of the fifth frame, and that \((-1+2i)+(-1-2i)=-2=-b/a\) and \((-1+2i)(-1-2i)=5=c/a\). What is the vertex of the parabola, and how does its height tell you the roots are not real?
References: al-Khwārizmī, Al-Kitāb al-mukhtaṣar fī ḥisāb al-jabr wa-l-muqābala (about 820). Goldberg, “What every computer scientist should know about floating-point arithmetic”, ACM Computing Surveys 23 (1991) 5–48. Higham, Accuracy and Stability of Numerical Algorithms, 2nd ed., SIAM (2002), section 1.8. Kahan, “On the cost of floating-point computation without extra-precise arithmetic” (2004), on the quadratic. The dates and attributions in the History card are summarized and approximate.
Try it in Python
Two checks in one short script: that every frame of the derivation keeps the same solutions (the difference of its two sides is f(\(x\))/a for any \(x\), tested with exact fractions), and the cancellation table for \(x^2 + b x + 1\) with the exact roots from the decimal module. It needs nothing beyond the standard library.
"""Quadratic formula: exact check of the derivation, and the cancellation table. Standard library only.
Each derivation step is an identity between left and right sides; we verify with exact fractions that
every step keeps the same solution set, and that the final formula solves a x^2 + b x + c = 0."""
from fractions import Fraction as F
from decimal import Decimal, getcontext
import math
def steps(a, b, c, x):
"""(left, right) of each frame 0..7 evaluated at x; the difference L-R must be (a x^2+b x+c)/a^k for fixed k."""
a, b, c, x = map(F, (a, b, c, x))
p = b / (2 * a)
return [
(a*x*x + b*x + c, 0),
((a*x*x + b*x + c) / a, 0),
(x*x + b/a*x + c/a, 0),
(x*x + b/a*x, -c/a),
(x*x + b/a*x + p*p, -c/a + p*p),
((x + p)**2, -c/a + p*p),
((x + p)**2, -4*a*c/(4*a*a) + b*b/(4*a*a)),
((x + p)**2, (b*b - 4*a*c) / (4*a*a)),
]
def check(a, b, c):
a, b, c = map(F, (a, b, c))
for x in (F(0), F(1), F(-3, 7), F(5, 2)):
f = a*x*x + b*x + c
for i, (L, R) in enumerate(steps(a, b, c, x)):
want = f if i == 0 else f / a
assert L - R == want, (i, x)
return True
def exact_roots(a, b, c):
"""Roots as Decimals (60 digits) through exact discriminant; None for complex."""
getcontext().prec = 60
a, b, c = map(F, (a, b, c)); D = b*b - 4*a*c
if D < 0: return None
s = Decimal(D.numerator).sqrt() / Decimal(D.denominator).sqrt()
A, B = Decimal(a.numerator) / Decimal(a.denominator), Decimal(b.numerator) / Decimal(b.denominator)
return (-B - s) / (2*A), (-B + s) / (2*A)
def naive(a, b, c):
s = math.sqrt(b*b - 4*a*c); return (-b - s) / (2*a), (-b + s) / (2*a)
def stable(a, b, c):
s = math.sqrt(b*b - 4*a*c); q = -0.5 * (b + math.copysign(s, b)); return q / a, c / q
if __name__ == "__main__":
for abc in [(1, -5, 6), (2, 3, -7), (F(1, 3), F(-2, 5), F(1, 7))]:
print(abc, "derivation checks:", check(*abc))
print("\nroots of x^2 + 10^k x + 1: relative error of the small root")
print(" k naive stable")
for k in range(2, 15, 2):
b = 10.0**k; ex = exact_roots(1, int(b), 1)[1]
n = min(naive(1, b, 1), key=abs); s = min(stable(1, b, 1), key=abs)
err = lambda v: abs((Decimal(v) - ex) / ex)
print(f"{k:4d} {float(err(n)):.2e} {float(err(s)):.2e}")
Simulate cards with 6 to 20 numbers or many cards, see chance against cost for every scheme, and read why the mathematics rules out a winning strategy, at two levels.
One metric ⟨ , ⟩ for data and for functions: type a table of points or any function, fit up to 32 basis functions (monomials, Fourier, Legendre, Chebyshev, Laguerre, Hermite) or nine nonlinear models, see the Gram matrix and the augmented normal system, and read a full class reference.
Partitions drawn as boxes, the hook lengths that count the ways to fill them, and the Robinson–Schensted correspondence that turns a permutation into a pair of tableaux. These are the combinatorial objects that label the irreducible representations of the symmetric group, the subject of the next apps.
Diagram
A partition and its hooks
Click a box. Its arm (boxes to its right) is blue, its leg (boxes below it) is pink, and the hook length is arm + leg + 1. The diagram is drawn in English notation: the longest row is on top.
Correspondence
Robinson–Schensted: row insertion
0
\(P\)
\(Q\)
Each number is inserted into the first row: it replaces the smallest entry larger than itself, which is bumped down into the next row, and so on, until a number lands at the end of a row. Blue boxes changed; the pink box is new, and Q records its position with the step number.
Tableaux
Standard tableaux of the chosen shape
A standard tableau fills the boxes with 1, …, n, each once, increasing along every row and down every column. The hook walk of Greene, Nijenhuis and Wilf draws one of them uniformly at random.
The walk places \(n\), then \(n - 1\), and so on: pick a box at random, then keep jumping to a random box of its hook until you reach a corner, and put the largest remaining number there.
Counting
Two identities, checked
Summing over all partitions of n: the squares of the numbers f add up to n!, because the correspondence is a bijection between permutations and pairs of standard tableaux of the same shape; and the numbers f add up to the number of involutions.
n
p(n)
\(\sum f^2\)
n!
\(\sum f\)
#\(\{w=w^{-1}\}\)
Involutions are counted independently, by the recurrence \(a(n) = a(n-1) + (n-1) a(n-2)\), so the ✓ marks compare two different computations.
History
From substitutional analysis to bijections
Alfred Young introduced his diagrams and tableaux around 1900 in his work on quantitative substitutional analysis, and in the same year Ferdinand Georg Frobenius computed the characters of the symmetric group. The labels of the irreducible representations by partitions grew out of both.
The hook length formula is due to Frame, Robinson and Thrall (1954). Gilbert de Beauregard Robinson (1938) and Craige Schensted (1961) found the correspondence, in different forms, and Donald Knuth (1970) extended it to matrices and words; Schützenberger later explained its symmetry.
The probabilistic proof by the hook walk is from Greene, Nijenhuis and Wilf (1979).
Honest limits
What this app does not do
The correspondence is shown for permutations (distinct numbers, at most 12). For words with repeated letters the second tableau is semistandard and the first records positions; the same idea works, but it is not drawn here.
The identities in the table are checked for n up to 10, which is evidence and not a proof. The proofs are in the course below and in the references.
\[s_\lambda(1^m)=\#\{\text{semistandard tableaux of shape }\lambda,\ \text{entries}\le m\}=\prod_{(i,j)\in\lambda}\frac{m+j-i}{h(i,j)}\]
Course
A short course on diagrams and tableaux
Questions and worked answers, from the definitions to the correspondence. Open each one and try before reading.
Basics
What is a Young diagram?
A partition of \(n\) is a list \(\lambda=(\lambda_1\ge\lambda_2\ge\dots\ge\lambda_k>0)\) with \(\lambda_1+\dots+\lambda_k=n\), written \(\lambda\vdash n\). Its Young diagram has \(\lambda_i\) boxes in row \(i\). There are \(p(n)\) partitions: \(p(5)=7\). Reading the columns instead gives the conjugate partition \(\lambda'\); for example \((4,2,1)'=(3,2,1,1)\).
What is a standard Young tableau?
A filling of the diagram with \(1,2,\dots,n\), each used once, increasing along every row and down every column. The number of them is \(f^\lambda\). For \(\lambda=(2,1)\) there are two: first row \(1\,2\) with \(3\) below the \(1\), and first row \(1\,3\) with \(2\) below the \(1\). So \(f^{(2,1)}=2\).
What is the hook of a box?
The box itself, the boxes to its right in the same row (the arm, \(a\)) and the boxes below it in the same column (the leg, \(\ell\)). Its hook length is \(h=a+\ell+1\). In the corner of a partition \(h=1\); in box \((1,1)\) of \(\lambda\) it is \(\lambda_1+\lambda'_1-1\).
The hook length formula
Compute \(f^{(3,2)}\) with the hook length formula.
The hook lengths are \(4,3,1\) in the first row and \(2,1\) in the second. Their product is \(24\), so \(f^{(3,2)}=5!/24=120/24=5\).
Check that \(\sum_{\lambda\vdash4}(f^\lambda)^2=4!\).
The five partitions \((4),(3,1),(2,2),(2,1,1),(1^4)\) have \(f=1,3,2,3,1\). The squares add to \(1+9+4+9+1=24=4!\).
Show that \(f^{(n-1,1)}=n-1\).
The hook lengths are \(n\) for box \((1,1)\), then \(n-2,n-3,\dots,1\) for the rest of row 1, and \(1\) for the box below. The product is \(n\,(n-2)!\), so \(f=n!/(n\,(n-2)!)=n-1\). Directly: a standard tableau is determined by which of \(2,\dots,n\) sits in the second row.
How many semistandard tableaux of shape \((2,1)\) have entries in \(\{1,2,3\}\)?
Rows weakly increase, columns strictly increase. By the hook content formula with \(m=3\): the contents are \(0,1,-1\) and the hooks \(3,1,1\), so the number is \(\frac{(3+0)(3+1)(3-1)}{3\cdot1\cdot1}=8\). It is the dimension of the adjoint representation of \(\mathfrak{sl}_3\).
Robinson–Schensted
Insert \(3\,1\,4\,2\,5\) and find \(P\) and \(Q\).
Insert 3: \(P=\{3\}\). Insert 1: it bumps 3, which starts row 2. Insert 4: it ends row 1. Insert 2: it bumps 4 from row 1, and 4 goes to the end of row 2. Insert 5: it ends row 1. The result is \(P=\begin{smallmatrix}1&2&5\\3&4\end{smallmatrix}\) and, recording the steps at which the boxes appeared, \(Q=\begin{smallmatrix}1&3&5\\2&4\end{smallmatrix}\). The shape is \((3,2)\), and \(f^{(3,2)}=5\) is the number of choices for \(Q\) given the shape.
Why is it a bijection?
The last box added to \(P\) is the box where \(Q\) holds its largest entry. Remove that entry from \(P\) and run the bumping backwards: in each row above, the largest entry smaller than it is pushed up. What comes out at the top is the last letter of \(w\). Repeating this recovers \(w\) completely, so the map is injective, and counting pairs gives \(\sum_\lambda (f^\lambda)^2=n!\), so it is also onto.
What does the shape tell about w?
Schensted's theorem: the length of the first row \(\lambda_1\) is the length of the longest increasing subsequence of \(w\), and the number of rows is the length of the longest decreasing subsequence. For \(3\,1\,4\,2\,5\) these are \(3\) (for instance \(1\,2\,5\)) and \(2\) (for instance \(3\,1\)). Greene's theorem gives all the other rows too, in terms of unions of such subsequences.
Going further
How many involutions are there in \(S_n\)?
The correspondence has the symmetry \(P(w^{-1})=Q(w)\) (Schützenberger), so \(w=w^{-1}\) exactly when \(P=Q\). Then \(w\) is determined by a single standard tableau of size \(n\), and the number of involutions is \(\sum_{\lambda\vdash n}f^\lambda\). For \(n=4\): \(1+3+2+3+1=10\), and indeed \(S_4\) has the identity, \(6\) transpositions and \(3\) double transpositions.
How many standard tableaux has the staircase \((3,2,1)\)?
The hook lengths are \(5,3,1\), \(3,1\), \(1\); the product is \(45\), so \(f=720/45=16\).
Why does this matter for representations?
The irreducible complex representations of \(S_n\) are indexed by the partitions \(\lambda\vdash n\), and the dimension of the one for \(\lambda\) is exactly \(f^\lambda\). The identity \(\sum(f^\lambda)^2=n!=|S_n|\) is then the statement that the group algebra is the sum of the matrix algebras \(M_{f^\lambda}(\mathbb C)\). The next apps build these representations and their characters; the semistandard count above is the dimension of the corresponding representation of \(GL_m\).
References: W. Fulton, Young Tableaux (Cambridge, 1997); B. Sagan, The Symmetric Group (2nd ed., Springer, 2001); R. Stanley, Enumerative Combinatorics, vol. 2, chapter 7; D. Knuth, The Art of Computer Programming, vol. 3, section 5.1.4.
Try it in Python
One short script with no libraries: hook lengths, the hook length formula against a direct count of standard tableaux, the sum of squares, and Robinson–Schensted with its inverse, checked on every permutation of up to 7 letters.
"""Young diagrams and tableaux: hook lengths, the hook length formula against a direct count, sum of squares = n!,
and Robinson-Schensted with its inverse, checked on every permutation of up to 7 letters. Standard library only."""
from itertools import permutations
from math import factorial
from functools import lru_cache
def partitions(n, m=None):
"""all partitions of n as tuples in weakly decreasing order"""
m = n if m is None else m
if n == 0: yield (); return
for k in range(min(n, m), 0, -1):
for rest in partitions(n - k, k): yield (k,) + rest
def conjugate(lam):
return tuple(sum(1 for x in lam if x > j) for j in range(lam[0])) if lam else ()
def hook_lengths(lam):
c = conjugate(lam)
return [[lam[i] - j - 1 + c[j] - i - 1 + 1 for j in range(lam[i])] for i in range(len(lam))]
def f_hook(lam):
"""number of standard tableaux by the hook length formula"""
p = 1
for row in hook_lengths(lam):
for h in row: p *= h
return factorial(sum(lam)) // p
@lru_cache(maxsize=None)
def f_count(lam):
"""number of standard tableaux by removing the box that holds the largest entry"""
if sum(lam) <= 1: return 1
total = 0
for i in range(len(lam)):
if i == len(lam) - 1 or lam[i] > lam[i + 1]:
mu = list(lam); mu[i] -= 1
total += f_count(tuple(x for x in mu if x))
return total
def rsk(w):
"""row insertion: returns (P, Q) as lists of rows"""
P, Q = [], []
for t, x in enumerate(w, 1):
r = 0
while True:
if r == len(P): P.append([x]); Q.append([t]); break
row = P[r]
c = next((k for k, y in enumerate(row) if y > x), None)
if c is None: row.append(x); Q[r].append(t); break
row[c], x = x, row[c]; r += 1
return P, Q
def inverse_rsk(P, Q):
P = [r[:] for r in P]; Q = [r[:] for r in Q]; out = []
for t in range(sum(map(len, P)), 0, -1):
r = next(i for i, row in enumerate(Q) if row and row[-1] == t)
y = P[r].pop(); Q[r].pop()
for i in range(r - 1, -1, -1):
c = max(k for k, z in enumerate(P[i]) if z < y)
P[i][c], y = y, P[i][c]
out.append(y)
if not P[-1]: P.pop(); Q.pop()
return out[::-1]
def longest_increasing(w):
best = []
for i, x in enumerate(w): best.append(1 + max([best[j] for j in range(i) if w[j] < x], default=0))
return max(best, default=0)
def inverse_perm(w):
inv = [0] * len(w)
for i, x in enumerate(w, 1): inv[x - 1] = i
return inv
if __name__ == "__main__":
print("n p(n) sum f^2 n! sum f")
for n in range(1, 11):
P = list(partitions(n))
assert all(f_hook(l) == f_count(l) for l in P)
s2 = sum(f_hook(l) ** 2 for l in P); s1 = sum(f_hook(l) for l in P)
assert s2 == factorial(n)
print(f"{n:<2} {len(P):<5} {s2:<8} {factorial(n):<6} {s1}")
print("hook lengths of (3,2):", hook_lengths((3, 2)), " f =", f_hook((3, 2)))
P, Q = rsk([3, 1, 4, 2, 5]); print("RSK of 3 1 4 2 5:", P, Q)
for n in range(1, 8):
involutions = 0
for w in permutations(range(1, n + 1)):
w = list(w); P, Q = rsk(w)
assert inverse_rsk(P, Q) == w
assert len(P[0]) == longest_increasing(w) # Schensted
assert len(P) == longest_increasing([-x for x in w])
P2, Q2 = rsk(inverse_perm(w)); assert (P2, Q2) == (Q, P) # symmetry
involutions += (P == Q)
assert involutions == sum(f_hook(l) for l in partitions(n))
print(f"n = {n}: all {factorial(n)} permutations pass; involutions = {involutions}")
Build the irreducible representations of \(S_n\) with your own hands: the Specht module of a partition, spanned by polytabloids, the matrix by which a permutation acts on it, and the character table that records the traces. Everything is computed exactly in your browser.
A tabloid is a tableau with the order inside each row forgotten, written {12|3} for the rows {1,2} and {3}. The polytabloid of t adds up the tabloids of all the tableaux obtained by permuting entries inside each column of t, with the sign of the permutation. The polytabloids of the standard tableaux are a basis of the Specht module.
Action
How a permutation acts
A permutation renames the entries of every tabloid, so it sends polytabloids to polytabloids. The columns of the matrix are the coordinates of \(\pi e_t\) in the basis of standard polytabloids, found by exact linear algebra; the entries are always integers.
Characters
The character table
Row \(\lambda\), column \(\mu\): the trace of a permutation of cycle type \(\mu\) on the module \(S^\lambda\). Under each class is its size. Click a row to check it; the first entry of each row (the class of the identity) is the dimension \(f^\lambda\).
Each entry is a trace of a matrix built exactly as above, not a lookup. The Python script below recomputes the tables with the Murnaghan–Nakayama rule, a completely different method, and compares.
History
From characters to modules
Frobenius determined the characters of the symmetric groups in 1900, and Young's work from the same years gave the combinatorics of the partitions that label them. The modules used here were constructed by Wilhelm Specht in 1935.
Gordon James's 1978 lecture notes presented the construction over arbitrary fields, where the polytabloid basis is still a basis but the modules need not be irreducible or semisimple, and began the modular representation theory of the symmetric group.
Honest limits
What this app does not do
It works over the complex numbers (in fact the integers suffice for the matrices), and only for n up to 6 in the module explorer and 7 in the table, because the tabloid space grows quickly.
That the Specht modules are irreducible and pairwise non-isomorphic is a theorem, proved in the references. The checks here (orthogonality of the characters) are consistent with it and, for these n, equivalent to it.
At a glance
The formulas
Tabloids, polytabloids and the Specht module:
\[\{t\}=\text{rows of }t\text{ as sets},\qquad e_t=\sum_{\sigma\in C_t}\operatorname{sgn}(\sigma)\,\sigma\{t\},\qquad S^\lambda=\operatorname{span}\{e_t\}\subset M^\lambda\]
A basis and the dimension:
\[\{e_t:\ t\text{ standard of shape }\lambda\}\text{ is a basis of }S^\lambda,\qquad \dim S^\lambda=f^\lambda=\frac{n!}{\prod h}\]
Questions and worked answers, from the definition of a representation to the regular representation. Open each one and try before reading.
Basics
What is a representation and its character?
A representation of a group \(G\) on a vector space \(V\) is a homomorphism \(\rho:G\to GL(V)\), that is, matrices with \(\rho(gh)=\rho(g)\rho(h)\). Its character is \(\chi(g)=\operatorname{tr}\rho(g)\). Always \(\chi(e)=\dim V\). Examples for \(S_n\): the trivial representation (\(\chi=1\)), the sign representation (\(\chi=\operatorname{sgn}\)), and the permutation representation on \(\mathbb C^n\) (\(\chi(\pi)=\) the number of fixed points of \(\pi\)).
What is irreducible, and how many are there?
A representation is irreducible if \(V\neq0\) has no invariant subspace other than \(0\) and \(V\). Over \(\mathbb C\) every representation of a finite group is a direct sum of irreducibles (Maschke). The number of irreducibles equals the number of conjugacy classes. In \(S_n\) two permutations are conjugate exactly when they have the same cycle type, so the classes correspond to the partitions of \(n\): there are \(p(n)\) irreducibles, one for each partition \(\lambda\), the Specht module \(S^\lambda\).
Why does the character table suffice?
Because \(\operatorname{tr}(\rho(g)\rho(h)\rho(g)^{-1})=\operatorname{tr}\rho(h)\), the character is constant on conjugacy classes, so one value per class describes it. And characters determine representations: two representations with the same character are isomorphic, because the irreducible characters are linearly independent.
Building the modules
Build \(S^{(2,1)}\) by hand and find the matrix of \((1\,2)\).
The tabloids of shape \((2,1)\) are \(\{12|3\},\{13|2\},\{23|1\}\). For \(t=\begin{smallmatrix}1&2\\3\end{smallmatrix}\) the column group is \(\{e,(1\,3)\}\), so \(e_t=\{12|3\}-\{23|1\}\). For \(t'=\begin{smallmatrix}1&3\\2\end{smallmatrix}\) it is \(\{e,(1\,2)\}\) and \(e_{t'}=\{13|2\}-\{23|1\}\). These two are independent, so \(\dim S^{(2,1)}=2=f^{(2,1)}\). Now \((1\,2)e_t=\{12|3\}-\{13|2\}=e_t-e_{t'}\) and \((1\,2)e_{t'}=\{23|1\}-\{13|2\}=-e_{t'}\). The matrix is \(\begin{pmatrix}1&0\\-1&-1\end{pmatrix}\), with trace \(0=\chi^{(2,1)}(\text{transposition})\).
Why are the polytabloids of the standard tableaux a basis?
Independence: each standard \(e_t\) contains the tabloid \(\{t\}\), and \(\{t\}\) is the largest tabloid of \(e_t\) in the dominance order, so the leading tabloids are distinct. Spanning: any polytabloid \(e_s\) can be rewritten as a combination of standard ones by the Garnir relations, which say that \(\sum\operatorname{sgn}(\sigma)\,e_{\sigma t}=0\) when \(\sigma\) runs over all permutations of a suitable set of entries taken from two adjacent columns (a Garnir set). The details are in James's book. Consequently \(\dim S^\lambda=f^\lambda\).
What are \(S^{(n)}\) and \(S^{(1^n)}\)?
For \((n)\) there is a single tabloid and the column group is trivial, so \(e_t=\{t\}\) and \(\pi e_t=e_t\): the trivial representation. For \((1^n)\) there is again a single tabloid, but the column group is all of \(S_n\), so \(e_t=\sum\operatorname{sgn}(\sigma)\sigma\{t\}\) and \(\pi e_t=\operatorname{sgn}(\pi)e_t\): the sign representation. In the table, these are the first and the last rows.
Characters
Verify the orthogonality relations for \(S_3\).
The classes \(e,(1\,2),(1\,2\,3)\) have sizes \(1,3,2\). The characters are \(\chi^{(3)}=(1,1,1)\), \(\chi^{(2,1)}=(2,0,-1)\), \(\chi^{(1^3)}=(1,-1,1)\). Then \(\langle\chi^{(2,1)},\chi^{(2,1)}\rangle=\frac{1\cdot4+3\cdot0+2\cdot1}{6}=1\) and \(\langle\chi^{(3)},\chi^{(2,1)}\rangle=\frac{1\cdot2+3\cdot0+2\cdot(-1)}{6}=0\). Also \(1^2+2^2+1^2=6=3!\).
Find \(\chi^{(n-1,1)}\) without matrices.
The permutation representation \(M^{(n-1,1)}=\mathbb C^n\) has character \(\operatorname{fix}(\pi)\), and it splits as the trivial representation plus \(S^{(n-1,1)}\) (the vectors with coordinate sum zero). So \(\chi^{(n-1,1)}(\pi)=\operatorname{fix}(\pi)-1\). For \(n=4\) on the class \((2,2)\) it is \(0-1=-1\), which is the entry in the table.
Why do the class sizes appear in the inner product?
The inner product averages over the group, \(\frac1{n!}\sum_\pi\chi^\lambda(\pi)\overline{\chi^\mu(\pi)}\). Since characters are constant on classes, the sum over a class of size \(|C|\) is \(|C|\) times the value. For real characters, as here, the bar can be dropped.
Going further
What is the regular representation?
It is \(\mathbb C[S_n]\) acting on itself by multiplication, of dimension \(n!\). Each \(S^\lambda\) occurs in it exactly \(f^\lambda=\dim S^\lambda\) times, which gives \(n!=\sum(f^\lambda)^2\). Its character is \(n!\) at the identity and \(0\) elsewhere, so \(\sum_\lambda f^\lambda\chi^\lambda(\pi)=0\) for \(\pi\neq e\). Check for \(S_3\) at \((1\,2)\): \(1\cdot1+2\cdot0+1\cdot(-1)=0\).
How can characters be computed without building modules?
By the Murnaghan–Nakayama rule: \(\chi^\lambda(\mu)\) is a signed count of ways to peel off border strips of sizes \(\mu_1,\mu_2,\dots\) from the diagram of \(\lambda\), each strip counted with sign \((-1)^{\text{height}-1}\). The next app is about this rule, Young's rule for the permutation modules \(M^\mu\), and Schur–Weyl duality.
References: G. James, The Representation Theory of the Symmetric Groups (Springer LNM 682, 1978); B. Sagan, The Symmetric Group (2nd ed., Springer, 2001); W. Fulton and J. Harris, Representation Theory (Springer, 1991); J.-P. Serre, Linear Representations of Finite Groups (Springer, 1977).
Try it in Python
One script with only the standard library: polytabloids, exact linear algebra with fractions, the traces on Specht modules for n up to 6, a comparison with the Murnaghan–Nakayama rule, and the orthogonality relations.
"""Representations of the symmetric group S_n: Specht modules by exact linear algebra, characters as traces, and a check against the
Murnaghan-Nakayama rule and the orthogonality relations. Standard library only (fractions, itertools, math)."""
from fractions import Fraction as F
from itertools import permutations, product
from math import factorial
def partitions(n, m=None):
m = n if m is None else m
if n == 0: yield (); return
for k in range(min(n, m), 0, -1):
for rest in partitions(n - k, k): yield (k,) + rest
def standard_tableaux(lam):
n = sum(lam); out = []; T = [[0] * x for x in lam]; cnt = [0] * len(lam)
def put(k):
if k > n: out.append([r[:] for r in T]); return
for i in range(len(lam)):
if cnt[i] < lam[i] and (i == 0 or cnt[i - 1] > cnt[i]):
T[i][cnt[i]] = k; cnt[i] += 1; put(k + 1); cnt[i] -= 1; T[i][cnt[i]] = 0
put(1); return out
def tabloid(rows): return tuple(tuple(sorted(r)) for r in rows)
def polytabloid(t):
"""e_t = sum over the column group of sgn(sigma) * sigma{t}, as a dict tabloid -> coefficient"""
cols = [[t[i][j] for i in range(len(t)) if len(t[i]) > j] for j in range(len(t[0]))]
def sign(p): return (-1) ** sum(1 for a in range(len(p)) for b in range(a) if p[b] > p[a])
vec = {}
for choice in product(*[list(permutations(range(len(c)))) for c in cols]):
rows = [list(r) for r in t]; s = 1
for j, (c, p) in enumerate(zip(cols, choice)):
s *= sign(p)
for i in range(len(c)): rows[i][j] = c[p[i]]
key = tabloid(rows); vec[key] = vec.get(key, 0) + s
return {k: v for k, v in vec.items() if v}
def act(p, vec):
"""p is a tuple with p[i-1] = image of i"""
out = {}
for tab, c in vec.items():
key = tabloid([[p[x - 1] for x in r] for r in tab]); out[key] = out.get(key, 0) + c
return out
def solve_coordinates(basis, w):
"""coordinates of w in the span of the basis vectors (dicts), exact; raises if w is not in the span"""
keys = sorted({k for b in basis for k in b} | set(w)); m = len(basis)
rows = [[F(b.get(k, 0)) for b in basis] + [F(w.get(k, 0))] for k in keys]
piv = []; r = 0
for c in range(m):
p = next((i for i in range(r, len(rows)) if rows[i][c] != 0), None)
if p is None: raise ValueError("dependent basis")
rows[r], rows[p] = rows[p], rows[r]; rows[r] = [x / rows[r][c] for x in rows[r]]
for i in range(len(rows)):
if i != r and rows[i][c] != 0: rows[i] = [x - rows[i][c] * y for x, y in zip(rows[i], rows[r])]
r += 1
assert all(rows[i][m] == 0 for i in range(r, len(rows))), "vector outside the span"
return [rows[i][m] for i in range(m)]
def character(lam, p):
"""trace of p on the Specht module S^lam, in the basis of standard polytabloids"""
basis = [polytabloid(t) for t in standard_tableaux(lam)]
tr = F(0)
for j, b in enumerate(basis): tr += solve_coordinates(basis, act(p, b))[j]
assert tr.denominator == 1; return int(tr)
def perm_of_type(mu):
p = []; s = 0
for c in mu: p += [s + (k + 1) % c + 1 for k in range(c)]; s += c
return tuple(p)
def class_size(mu):
n = sum(mu); d = 1
for c in set(mu): d *= c ** mu.count(c) * factorial(mu.count(c))
return factorial(n) // d
def murnaghan_nakayama(lam, mu):
"""characters from removing border strips (beta-numbers); independent of the construction above"""
if not mu: return 1 if sum(lam) == 0 else 0
r, rest = mu[0], mu[1:]; beta = [x + len(lam) - 1 - i for i, x in enumerate(lam)]; total = 0
for i, b in enumerate(beta):
nb = b - r
if nb < 0 or nb in beta: continue
sign = (-1) ** sum(1 for x in beta if nb < x < b)
new = sorted(beta[:i] + [nb] + beta[i + 1:], reverse=True)
total += sign * murnaghan_nakayama(tuple(x - (len(new) - 1 - k) for k, x in enumerate(new) if x - (len(new) - 1 - k) > 0), rest)
return total
if __name__ == "__main__":
for n in range(2, 7):
parts = list(partitions(n)); table = [[character(l, perm_of_type(m)) for m in parts] for l in parts]
for a, l in enumerate(parts):
for b, m in enumerate(parts): assert table[a][b] == murnaghan_nakayama(l, m), (l, m)
sizes = [class_size(m) for m in parts]
for a in range(len(parts)):
for b in range(len(parts)):
ip = sum(s * table[a][k] * table[b][k] for k, s in enumerate(sizes))
assert ip == (factorial(n) if a == b else 0)
print(f"n = {n}: {len(parts)} irreducible characters; traces on Specht modules = Murnaghan-Nakayama; rows orthogonal")
print("S_3 table (rows (3), (2,1), (1,1,1); columns e, (12), (123)):")
for l in partitions(3): print(l, [character(l, perm_of_type(m)) for m in [(1, 1, 1), (2, 1), (3,)]])
Four ways to use what the first two apps built: peel border strips to compute a character, split a permutation module with Young's rule, restrict and induce by adding and removing boxes, and see Schur and Weyl's duality between \(S_n\) and the general linear group. Every output is checked against exact computations.
Murnaghan–Nakayama
Peeling border strips
A border strip is a connected skew shape with no \(2\times 2\) block, hugging the rim of the diagram. Remove strips of sizes \(\mu_1\), \(\mu_2\), … in turn, so that what remains is a diagram at every step; each full sequence is a border-strip tableau, counted with the sign (\(-1\)) to the power (sum of heights \(-\) number of strips).
Young's rule
Splitting a permutation module
The module \(M^\mu\) has a basis of tabloids of shape \(\mu\). It contains \(S^\lambda\) exactly \(K_{\lambda \mu}\) times, where \(K_{\lambda \mu}\) counts the semistandard fillings of \(\lambda\) with \(\mu_1\) ones, \(\mu_2\) twos, and so on. The Kostka matrix is unitriangular for the dominance order, which is why the \(S^\lambda\) are a basis of the characters.
Branching
Restriction and induction
Restricting \(S^\lambda\) from \(S_n\) to \(S_{n-1}\) removes one removable box in every possible way (marked in color); inducing to \(S_{n+1}\) adds one box in every possible way (marked +). Multiplicities are all 1.
Dimension check: the dimensions of the pieces add up to \(f^\lambda\) on restriction and to \((n+1) f^\lambda\) on induction. Iterating the restriction down to \(S_1\) is the same as counting standard tableaux.
Schur–Weyl duality
Two groups, one space
On the tensor power \((\mathbb{C}^m)^{\otimes n}\), the symmetric group permutes the factors and GL_m acts on each factor. The two actions commute and generate each other's commutant, so the space splits as a sum of \(S^\lambda \otimes V_\lambda\), with \(V_\lambda\) an irreducible representation of GL_m. Only partitions with at most m rows appear.
\(\lambda\)
\(f^\lambda=\dim S^\lambda\)
\(\dim V_\lambda=s_\lambda(1^m)\)
\(f^\lambda\cdot\dim V_\lambda\)
The dimension of \(V_\lambda\) is computed by the hook content formula, the product over the boxes of (m + content)/hook. Counting dimensions gives \(m^n = \Sigma f^\lambda\) dim \(V_\lambda\), which the last line checks.
History
From strips to duality
Young's rule for the permutation modules goes back to Alfred Young's work around 1900. The border-strip rule for characters was conjectured by Francis Murnaghan in 1937 and proved by Tadasi Nakayama in 1940.
The duality between \(S_n\) and the general linear group is in Issai Schur's 1901 dissertation and was popularized by Hermann Weyl in The Classical Groups (1939). The branching rule is the starting point of the Okounkov–Vershik approach to the representations of \(S_n\).
Honest limits
What is checked
The rules are implemented as stated in the references and compared with exact traces for \(n \le 7\) (Murnaghan–Nakayama, Young's rule) or with dimension counts (branching, Schur–Weyl). These are checks, not proofs.
The Python script below does the same tests with the standard library only. Nothing here covers positive characteristic, where Young's rule still holds for the permutation modules but the decomposition into irreducibles does not.
At a glance
The formulas
Murnaghan–Nakayama rule:
\[\chi^\lambda(\mu)=\sum_{T}(-1)^{\operatorname{ht}(T)},\qquad T\text{ a border-strip tableau of shape }\lambda\text{ and type }\mu=(\mu_1,\mu_2,\dots)\]
Young's rule:
\[M^\mu\cong\bigoplus_{\lambda\trianglerighteq\mu}K_{\lambda\mu}\,S^\lambda,\qquad K_{\lambda\mu}=\#\{\text{semistandard tableaux of shape }\lambda\text{ and content }\mu\},\quad K_{\mu\mu}=1\]
\[\pi_\mu(\sigma)=\#\{\text{tabloids of shape }\mu\text{ fixed by }\sigma\}=\sum_\lambda K_{\lambda\mu}\chi^\lambda(\sigma)\]
Course
A short course on computing characters
Questions and worked answers, each with a computation you can reproduce in the apps above.
Border strips
Compute \(\chi^{(2,1)}\) on a 3-cycle.
The cycle type is \(\mu=(3)\), so we remove one border strip of size 3 from \((2,1)\). The whole diagram is a strip: it is connected, has no \(2\times2\) block, and spans 2 rows, so its height is 2 and its sign is \((-1)^{2-1}=-1\). Hence \(\chi^{(2,1)}(3\text{-cycle})=-1\), as in the table of the second app.
Compute \(\chi^{(2,2)}\) on \((2,2)\) and on a 4-cycle.
For the 4-cycle we need a strip of size 4 inside \((2,2)\), but the \(2\times2\) square has a \(2\times2\) block, so it is not a strip: the value is \(0\). For type \((2,2)\) we remove a strip of size 2 and then another. The first strip is either the right column (a vertical domino, height 2, sign \(-1\)) or the bottom row (a horizontal domino, height 1, sign \(+1\)); after a horizontal domino at the bottom, \((2)\) remains and is a horizontal domino again (\(+\cdot+\)); after the vertical domino, \((1,1)\) remains, a vertical domino (\(-\cdot-=+\)). Two tableaux, both positive: \(\chi^{(2,2)}(2,2)=2\).
Why does the rule work on beta-numbers?
Encode \(\lambda\) by the set of beta-numbers \(\lambda_i+(\ell-i)\), a row of beads on an abacus. Removing a border strip of size \(r\) is moving one bead down by \(r\) places to an empty position, and the height of the strip is the number of beads jumped over. So the rule becomes a count of bead moves, which is how the app computes it. The proof that this counts characters is in Sagan's book (Section 4.10).
Permutation modules
Decompose \(M^{(2,1)}\) and \(M^{(2,2)}\).
For \(\mu=(2,1)\) the semistandard tableaux of content \((2,1)\) have shape \((3)\) (one: \(112\)) or \((2,1)\) (one: first row \(11\), second row \(2\)), so \(M^{(2,1)}=S^{(3)}\oplus S^{(2,1)}\), with dimensions \(1+2=3\). For \(\mu=(2,2)\) the shapes are \((4)\), \((3,1)\), \((2,2)\), each once, so \(M^{(2,2)}=S^{(4)}\oplus S^{(3,1)}\oplus S^{(2,2)}\) and \(1+3+2=6=\binom42\).
What is \(K_{\lambda\mu}\) for \(\lambda=(3,1)\), \(\mu=(2,1,1)\)?
Fill \((3,1)\) with \(1,1,2,3\), weakly increasing along rows and strictly increasing down columns. The two ones must be in the first row. The 2 and 3 are then placed one in the first row and one in the second: \(1\,1\,2/3\) and \(1\,1\,3/2\). Both are valid, so \(K_{(3,1),(2,1,1)}=2\). By contrast \(K_{(2,2),(2,2)}=1\): only \(1\,1/2\,2\) works.
Why is the Kostka matrix triangular?
A semistandard tableau of shape \(\lambda\) and content \(\mu\) exists only if \(\lambda\trianglerighteq\mu\): the ones, twos, \dots, \(k\)-s must all fit in the first \(k\) rows, so \(\lambda_1+\dots+\lambda_k\ge\mu_1+\dots+\mu_k\). When \(\lambda=\mu\) the filling is forced (row \(i\) full of \(i\)'s), so \(K_{\mu\mu}=1\). A unitriangular integer matrix is invertible over the integers, so the characters of the \(M^\mu\) and of the \(S^\lambda\) generate the same lattice.
Branching
Restrict \(S^{(3,2,1)}\) to \(S_5\).
The removable boxes of \((3,2,1)\) are at the ends of the three rows, giving \(\operatorname{Res}S^{(3,2,1)}=S^{(2,2,1)}\oplus S^{(3,1,1)}\oplus S^{(3,2)}\). Dimensions: \(5+6+5=16=f^{(3,2,1)}\).
Induce \(S^{(2,1)}\) from \(S_3\) to \(S_4\).
Boxes can be added at the end of the first row, of the second row, and in a new third row, giving \(S^{(3,1)}\oplus S^{(2,2)}\oplus S^{(2,1,1)}\). Dimensions \(3+2+3=8=4\cdot2=[S_4:S_3]\cdot\dim S^{(2,1)}\), as the induced module must have.
How does branching give the hook length formula?
Restricting step by step from \(S_n\) to \(S_1\) removes a box at each step, so the chain of diagrams is a standard tableau read backwards. Since each constituent appears once, \(f^\lambda\) is the number of such chains, \(f^\lambda=\sum_{\nu=\lambda-\square}f^\nu\). The hook length formula is the closed form of this recursion.
Duality
Check Schur–Weyl for \(n=3\), \(m=2\).
The partitions of 3 are \((3),(2,1),(1,1,1)\) with \(f=1,2,1\). With \(m=2\), \(\dim V_{(3)}=4\), \(\dim V_{(2,1)}=2\), and \(V_{(1,1,1)}=0\) because the shape has 3 rows and \(m=2\). Then \(1\cdot4+2\cdot2+1\cdot0=8=2^3\). The four dimensions of \(V_{(3)}\) are the symmetric cubes of \(\mathbb C^2\), the two of \(V_{(2,1)}\) the standard representation twisted by the determinant.
Why is this duality useful?
It moves information between the two groups. The character of \(S_n\) on the tensor power at \(\sigma\) of cycle type \(\mu\) is \(m^{\ell(\mu)}\), and the same trace is \(\sum_\lambda\chi^\lambda(\mu)\dim V_\lambda\); in the limit of formal variables this identity is the expansion of the power sums in Schur functions, \(p_\mu=\sum_\lambda\chi^\lambda(\mu)s_\lambda\), which is the Frobenius character formula.
References: B. Sagan, The Symmetric Group (2nd ed., Springer, 2001), chapters 2, 4 and 5; W. Fulton, Young Tableaux (Cambridge, 1997); R. Stanley, Enumerative Combinatorics vol. 2 (Cambridge, 1999), chapter 7; G. James, The Representation Theory of the Symmetric Groups (Springer LNM 682, 1978).
Try it in Python
One script with only the standard library: Murnaghan–Nakayama by border strips, Kostka numbers by brute force, permutation characters, branching dimensions and the Schur–Weyl count, all cross-checked for n up to 7.
"""Characters at work: the Murnaghan-Nakayama rule (border strips), Kostka numbers by listing semistandard tableaux, Young's rule checked on
permutation characters, the branching rule on dimensions, and Schur-Weyl dimensions. Standard library only."""
from itertools import permutations, product
from math import factorial, prod
from functools import lru_cache
def partitions(n, m=None):
m = n if m is None else m
if n == 0: yield (); return
for k in range(min(n, m), 0, -1):
for rest in partitions(n - k, k): yield (k,) + rest
def hooks(lam):
conj = [sum(1 for x in lam if x > j) for j in range(lam[0])] if lam else []
return [[lam[i] - j - 1 + conj[j] - i - 1 + 1 for j in range(lam[i])] for i in range(len(lam))]
def f_hook(lam): return factorial(sum(lam)) // prod(h for r in hooks(lam) for h in r)
def dim_gl(lam, m):
"""number of semistandard tableaux with entries 1..m, by the hook content formula"""
num = prod(m + j - i for i, r in enumerate(lam) for j in range(r)); den = prod(h for r in hooks(lam) for h in r)
assert num % den == 0; return num // den
def remove_strips(lam, r):
"""border strips of size r that can be removed from lam: (smaller partition, height = rows spanned - 1)"""
out = []
# every partition nu contained in lam with |lam/nu| = r whose skew shape is connected and has no 2x2 block
n = sum(lam)
for nu in partitions(n - r):
if len(nu) > len(lam) or any(nu[i] > lam[i] for i in range(len(nu))): continue
cells = {(i, j) for i in range(len(lam)) for j in range(lam[i]) if j >= (nu[i] if i < len(nu) else 0)}
if any((i + 1, j) in cells and (i, j + 1) in cells and (i + 1, j + 1) in cells for (i, j) in cells): continue
seen, stack = set(), [next(iter(cells))] if cells else []
while stack:
c = stack.pop()
if c in seen: continue
seen.add(c); stack += [d for d in [(c[0]+1, c[1]), (c[0]-1, c[1]), (c[0], c[1]+1), (c[0], c[1]-1)] if d in cells]
if seen == cells: out.append((nu, len({i for i, _ in cells}) - 1))
return out
@lru_cache(maxsize=None)
def mn(lam, mu):
if not mu: return 1 if not lam else 0
return sum((-1) ** h * mn(nu, mu[1:]) for nu, h in remove_strips(lam, mu[0]))
def kostka_bruteforce(lam, mu):
"""list semistandard tableaux of shape lam with content mu: rows weakly increase, columns strictly increase"""
cells = [(i, j) for i in range(len(lam)) for j in range(lam[i])]; k = len(mu); count = 0
def rec(idx, T, used):
nonlocal count
if idx == len(cells):
count += (used == list(mu)); return
i, j = cells[idx]
for v in range(k):
if used[v] >= mu[v]: continue
if j > 0 and T[(i, j - 1)] > v: continue
if i > 0 and T[(i - 1, j)] >= v: continue
T[(i, j)] = v; used[v] += 1; rec(idx + 1, T, used); used[v] -= 1; del T[(i, j)]
rec(0, {}, [0] * k); return count
def perm_char(mu, nu):
"""number of tabloids of shape mu fixed by a permutation of cycle type nu"""
@lru_cache(maxsize=None)
def f(i, rem):
if i == len(nu): return 1 if not any(rem) else 0
return sum(f(i + 1, rem[:r] + (c - nu[i],) + rem[r + 1:]) for r, c in enumerate(rem) if c >= nu[i])
return f(0, tuple(mu))
def removable(lam): return [tuple(x for x in lam[:i] + (lam[i] - 1,) + lam[i + 1:] if x) for i in range(len(lam)) if i == len(lam) - 1 or lam[i] > lam[i + 1]]
if __name__ == "__main__":
for n in range(1, 8):
P = list(partitions(n))
for mu in P:
for nu in P:
assert perm_char(mu, nu) == sum(kostka_bruteforce(lam, mu) * mn(lam, nu) for lam in P), (mu, nu) # Young's rule + MN
for lam in P:
if n > 1: assert sum(f_hook(r) for r in removable(lam)) == f_hook(lam) # branching, dimensions
for m in range(1, 6): assert sum(f_hook(l) * (dim_gl(l, m) if len(l) <= m else 0) for l in P) == m ** n # Schur-Weyl
print(f"n = {n}: Murnaghan-Nakayama + Kostka numbers reproduce the permutation characters; branching and Schur-Weyl dimensions agree")
print("chi^(2,2) at (2,2) =", mn((2, 2), (2, 2)), "; chi^(3,1) at (4) =", mn((3, 1), (4,)), "; chi^(3,2,1) at (3,2,1) =", mn((3, 2, 1), (3, 2, 1)))
print("Kostka (3,1) vs (2,1,1):", kostka_bruteforce((3, 1), (2, 1, 1)), "; (2,2) vs (2,2):", kostka_bruteforce((2, 2), (2, 2)))
Practice what the series built. Endless exercises on hook lengths, Robinson–Schensted, characters, Kostka numbers, branching, Schur–Weyl and conjugacy classes, each with an exact answer and a worked solution, or a ten-question quiz with a score at the end.
Practice
An exercise
All answers are integers. The generator is deterministic: the same seed gives the same sequence of exercises, so you can share a seed with a class. Each answer was checked against independent brute-force computations (counting tableaux, permutations, tabloids and Frobenius's formula).
Recap
What the four apps covered
Young diagrams and tableaux: partitions as boxes, hook lengths, the hook length formula for the number of standard tableaux, and the Robinson–Schensted correspondence between permutations and pairs of tableaux.
Representations of \(S_n\): Specht modules built from polytabloids, the matrices of permutations acting on them, the character table and its orthogonality relations.
Characters at work: the Murnaghan–Nakayama rule, Young's rule and Kostka numbers, branching, and Schur–Weyl duality. The exercises here draw on all of it.
Next
Where to go from here
Symmetric functions: the characters assemble into the Frobenius characteristic, which sends \(S^\lambda\) to the Schur function \(s_\lambda\) and induction products to products of functions; the Littlewood–Richardson rule then describes induction from Young subgroups \(S_a \times S_b\).
Other constructions and fields: the Okounkov–Vershik approach builds \(S^\lambda\) from the chain \(S_1 \subset S_2 \subset\) … using only branching, Hecke algebras deform the group algebra, and over fields of positive characteristic the modules \(S^\lambda\) need no longer be irreducible (James).
Random permutations: under RSK a uniformly random permutation gives a random shape (Plancherel measure). The longest increasing subsequence is about \(2\sqrt{n}\) (Vershik–Kerov, Logan–Shepp) and its fluctuations follow the Tracy–Widom law (Baik–Deift–Johansson, 1999).
Short answers to the questions that come up when working the exercises.
In what order should I do the topics?
Hook lengths and counting tableaux first, since they need only the diagram. Then Robinson–Schensted (a mechanical procedure, but Greene's theorem relates it to subsequences), then Murnaghan–Nakayama characters, then Kostka numbers and fixed tabloids, which together explain Young's rule. Branching and Schur–Weyl use everything before.
How can I check a character value without the table?
Three quick tests: the value at the identity is \(f^\lambda\); the conjugate shape satisfies \(\chi^{\lambda'}(\mu)=\operatorname{sgn}(\mu)\chi^\lambda(\mu)\), with \(\operatorname{sgn}(\mu)=(-1)^{n-\ell(\mu)}\); and for \(\mu\neq(1^n)\) the column sums \(\sum_\lambda f^\lambda\chi^\lambda(\mu)\) vanish (regular representation).
What mistakes are common with border strips?
Removing cells that leave a non-partition (the rest must still be a diagram); taking a strip that contains a \(2\times2\) block (not allowed); taking a disconnected set of cells; and counting the height as the number of rows instead of rows minus one (the sign is \((-1)^{\text{rows}-1}\)). A strip of size 1 always has sign \(+1\).
Any shortcuts for Kostka numbers?
\(K_{\lambda\mu}=0\) unless \(\lambda\trianglerighteq\mu\); \(K_{\lambda\lambda}=1\); \(K_{\lambda,(1^n)}=f^\lambda\); and \(K_{(n),\mu}=1\) for every \(\mu\). In general, build the tableau by adding a horizontal strip of size \(\mu_1\), then \(\mu_2\), and so on, and count the ways to reach \(\lambda\); the solution shown by the app lists those counts step by step.
References: B. Sagan, The Symmetric Group (2nd ed., Springer, 2001), which has exercises for every chapter; W. Fulton, Young Tableaux (Cambridge, 1997); R. Stanley, Enumerative Combinatorics vol. 2 (Cambridge, 1999); D. Romik, The Surprising Mathematics of Longest Increasing Subsequences (Cambridge, 2015).
Make worksheets in Python
One script with only the standard library that prints a worksheet and an answer key from a seed. Its answers come from methods different from the app's: corner removal, the Frobenius formula for characters, listing tableaux, and counting permutations and tabloids.
"""Exercises on Young tableaux and the symmetric group, with an answer key computed by methods independent of the apps:
dimensions by removing corners, characters by the Frobenius formula (not Murnaghan-Nakayama), Kostka numbers by listing tableaux,
fixed tabloids by brute force, class sizes by counting permutations. Standard library only.
Usage: python3 rep_exercises.py [seed] [how_many] prints a worksheet and then the answer key."""
import sys, random
from itertools import permutations
from math import factorial
from functools import lru_cache
def partitions(n, m=None):
m = n if m is None else m
if n == 0: yield (); return
for k in range(min(n, m), 0, -1):
for rest in partitions(n - k, k): yield (k,) + rest
def corners(lam):
return [i for i in range(len(lam)) if i == len(lam) - 1 or lam[i] > lam[i + 1]]
@lru_cache(None)
def dim(lam):
"""standard Young tableaux of shape lam, by removing the corner that holds the largest entry"""
if sum(lam) <= 1: return 1
t = 0
for i in corners(lam):
q = list(lam); q[i] -= 1
t += dim(tuple(x for x in q if x))
return t
def hook(lam, i, j):
return lam[i] - j - 1 + sum(1 for r in range(i + 1, len(lam)) if lam[r] > j) + 1
def rsk(w):
P, Q = [], []
for t, x in enumerate(w, 1):
r = 0
while True:
if r == len(P): P.append([x]); Q.append([t]); break
row = P[r]; c = next((k for k, v in enumerate(row) if v > x), None)
if c is None: row.append(x); Q[r].append(t); break
row[c], x = x, row[c]; r += 1
return P, Q
def frobenius(lam, mu):
"""chi^lam(mu) = coefficient of x^(lam+delta) in (Vandermonde) * p_mu, with k = len(lam) variables"""
k = len(lam); delta = list(range(k - 1, -1, -1)); target = [lam[i] + delta[i] for i in range(k)]
@lru_cache(None)
def ways(rem, idx):
if idx == len(mu): return 1 if all(x == 0 for x in rem) else 0
t = 0
for j in range(k):
if rem[j] >= mu[idx]:
r = list(rem); r[j] -= mu[idx]; t += ways(tuple(r), idx + 1)
return t
total = 0
for s in permutations(range(k)):
sign = 1
for a in range(k):
for b in range(a + 1, k):
if s[a] > s[b]: sign = -sign
rem = tuple(target[i] - delta[s[i]] for i in range(k))
if min(rem) >= 0: total += sign * ways(rem, 0)
return total
def kostka(lam, mu):
cells = [(i, j) for i, r in enumerate(lam) for j in range(r)]
T = {}; used = [0] * len(mu)
def rec(k):
if k == len(cells): return 1
i, j = cells[k]; t = 0
for v in range(1, len(mu) + 1):
if used[v - 1] >= mu[v - 1]: continue
if j and T[(i, j - 1)] > v: continue
if i and T[(i - 1, j)] >= v: continue
T[(i, j)] = v; used[v - 1] += 1; t += rec(k + 1); used[v - 1] -= 1
return t
return rec(0)
def perm_of_type(nu):
p, s = [], 0
for c in nu:
p += [s + (k + 1) % c for k in range(c)]; s += c
return p
def fixed_tabloids(mu, nu):
n = sum(mu); p = perm_of_type(nu); a = [0] * n; c = [0] * len(mu)
def rec(k):
if k == n: return 1 if all(a[p[x]] == a[x] for x in range(n)) else 0
t = 0
for r in range(len(mu)):
if c[r] < mu[r]: a[k] = r; c[r] += 1; t += rec(k + 1); c[r] -= 1
return t
return rec(0)
def weyl_dim(lam, m):
if len(lam) > m: return 0
l = list(lam) + [0] * (m - len(lam)); num = den = 1
for i in range(m):
for j in range(i + 1, m): num *= l[i] - l[j] + j - i; den *= j - i
return num // den
def cycle_type(p):
seen, out = set(), []
for i in range(len(p)):
if i not in seen:
c, j = 0, i
while j not in seen: seen.add(j); j = p[j]; c += 1
out.append(c)
return tuple(sorted(out, reverse=True))
def class_size_count(mu):
return sum(1 for p in permutations(range(sum(mu))) if cycle_type(p) == tuple(mu))
def exercise(rnd, kind):
if kind == 0:
n = rnd.randint(4, 8); lam = rnd.choice(list(partitions(n)))
return f"How many standard Young tableaux have shape {lam}?", dim(lam)
if kind == 1:
n = rnd.randint(4, 8); lam = rnd.choice(list(partitions(n))); i = rnd.randrange(len(lam)); j = rnd.randrange(lam[i])
return f"Hook length of the box in row {i+1}, column {j+1} of the diagram {lam}?", hook(lam, i, j)
if kind == 2:
n = rnd.randint(5, 7); w = rnd.sample(range(1, n + 1), n); P, Q = rsk(w)
return f"Row insertion of the word {w}: last entry of the first row of P?", P[0][-1]
if kind == 3:
n = rnd.randint(4, 6); lam = rnd.choice(list(partitions(n))); mu = rnd.choice(list(partitions(n)))
return f"Character value chi^{lam} on the cycle type {mu}?", frobenius(lam, mu)
if kind == 4:
n = rnd.randint(4, 6); mu = rnd.choice([p for p in partitions(n) if len(p) > 1]); lam = rnd.choice(list(partitions(n)))
return f"Kostka number K_({lam},{mu})?", kostka(lam, mu)
if kind == 5:
n = rnd.randint(4, 7); lam = rnd.choice(list(partitions(n)))
return f"Irreducible constituents of the restriction of S^{lam} from S_{n} to S_{n-1}?", len(corners(lam))
if kind == 6:
n = rnd.randint(3, 6); m = rnd.randint(2, 4); lam = rnd.choice(list(partitions(n)))
return f"Dimension of the GL_{m} module V_{lam} (0 if more than {m} rows)?", weyl_dim(lam, m)
if kind == 7:
n = rnd.randint(4, 6); mu = rnd.choice(list(partitions(n)))
return f"Permutations in S_{n} of cycle type {mu}?", class_size_count(mu)
n = rnd.randint(4, 6); mu = rnd.choice([p for p in partitions(n) if len(p) > 1]); nu = rnd.choice(list(partitions(n)))
return f"Tabloids of shape {mu} fixed by a permutation of cycle type {nu}?", fixed_tabloids(mu, nu)
def self_test():
for n in range(1, 7):
P = list(partitions(n)); fact = factorial(n)
for lam in P: assert frobenius(lam, (1,) * n) == dim(lam)
assert sum(dim(l) ** 2 for l in P) == fact
for a in P:
for b in P:
s = sum(class_size_count(mu) * frobenius(a, mu) * frobenius(b, mu) for mu in P)
assert s == (fact if a == b else 0), (a, b)
print("self-test passed: Frobenius characters are orthogonal for n <= 6, dimensions agree with corner removal")
if __name__ == "__main__":
seed = int(sys.argv[1]) if len(sys.argv) > 1 else 1; count = int(sys.argv[2]) if len(sys.argv) > 2 else 9
self_test(); rnd = random.Random(seed); items = [exercise(rnd, k % 9) for k in range(count)]
print(f"\nWorksheet (seed {seed})")
for k, (q, a) in enumerate(items, 1): print(f"{k:2d}. {q}")
print("\nAnswer key")
for k, (q, a) in enumerate(items, 1): print(f"{k:2d}. {a}")
Solving the quintic: from the polynomial to its Galois group
Pick an irreducible quintic, or let the app draw one, and follow the method step by step: discriminant, real roots, factorization mod \(p\), the resolvent sextic, the verdict. When the group is solvable, watch the Lagrange resolvents build the roots from fifth roots. Then practise on fresh exercises with instant feedback.
Workbench
Diagnose a quintic
Everything is computed exactly with big integers, and roots to about 80 digits. Open the "Recall" boxes for the theorems behind each step.
Monodromy
Roots in motion: loops build the group
Vary the constant term: \(f(x)+t\). At a few special values \(t_k\) two roots collide. Walk once around one of them and the roots come back permuted. The permutations from all such loops generate the monodromy group.
Caveat: the loops compute the Galois group of the family \(f(x)+t\) over \(\mathbb C(t)\) (the geometric monodromy group), which is not the Galois group of your \(f\) over \(\mathbb Q\). Your \(f\) is only the member \(t=0\) of the family, and its group is a subgroup of the family's group over \(\mathbb Q(t)\). Compare \(x^5-x-1\) (loops give \(S_5\), and so does \(\mathbb Q\)), the cyclic example (loops give \(S_5\), yet \(G=C_5\): a very special member) and \(x^5-2\) (loops give only \(C_5\), while \(G=F_{20}\) because \(\zeta_5\notin\mathbb Q\)).
Solving
Lagrange resolvents: the roots from fifth roots
Practice
Exercises with instant feedback
Each exercise is a fresh quintic (the base examples are shifted and flipped at random). Answer the five questions in order; the answers are computed by the same exact core that powers the workbench.
Remember
The five transitive groups at a glance
Group
\(\Delta\)
Resolvent sextic
Patterns mod \(p\)
Solvable?
\(C_5\) (5)
square
rational root
5, 1+1+1+1+1
yes
\(D_5\) (10)
square
rational root
5, 1+2+2, 1+1+1+1+1
yes
\(F_{20}\) (20)
non-square
rational root
5, 1+4, 1+2+2, 1+1+1+1+1
yes
\(A_5\) (60)
square
no rational root
5, 1+1+3, 1+2+2, 1+1+1+1+1
no
\(S_5\) (120)
non-square
no rational root
5, 1+4, 2+3, 1+1+3, 1+2+2, 1+1+1+2, 1+1+1+1+1
no
Galois: \(f\) is solvable by radicals exactly when \(G\) is a solvable group. Of the five, only \(A_5\) (simple, non-abelian) and \(S_5\) are not. That is the Abel-Ruffini theorem, with an algorithm to decide each case.
Tower for \(F_{20}\): \(\mathbb Q\subset L\subset L(\zeta_5)\subset\) splitting field, with \(\mathrm{Gal}=C_4\), then \(C_4\) acting through \(\zeta_5\), then \(C_5\). Each floor is a cyclic extension: adjoin a root of unity, then a radical (Kummer theory).
Lagrange: with \(\zeta=e^{2\pi i/5}\) and the roots numbered cyclically, \(R_j=\sum_k\zeta^{jk}\alpha_k\) satisfies \(R_j^5\in\) the field below, and \(\alpha_k=\frac15\sum_j\zeta^{-jk}R_j\), with \(R_0=-a_4\). That is how the roots are written with radicals.
Graduate levelAssumes Galois theory, complex analysis and some elliptic functions (theta constants, the modular function \(\lambda\)). Full formulas are given; proofs are sketched in the "Recall" boxes.
Algebra and number theory · Galois theory · elliptic functions
The quintic by elliptic functions: Hermite–Kronecker–Brioschi, with certified roots
Type any quintic. The app reduces it to Bring–Jerrard form with Tschirnhaus steps that need only a quadratic and a cubic, solves \(w^5-w+\rho=0\) in closed form with Jacobi theta functions (Hermite, 1858), maps the roots back, and then proves, in exact integer arithmetic, that every root lies in its own tiny disc. Below: the monodromy of the Bring radical, the modular equation of degree 5, a short history and exercises.
Workbench
Solve a quintic
Roots are computed in big-integer fixed point with several hundred bits of working precision; the certificate uses exact integer arithmetic only. Open the "Recall" boxes for the mathematics behind each step.
Riemann surface
Monodromy of the Bring radical
The roots of \(w^5-w+\rho=0\) form a five-sheeted cover of the \(\rho\)-plane with four finite branch points \(\beta_j=\tfrac45\,5^{-1/4}\,i^{\,j-1}\), where \(\rho^4=4^4/5^5\) and two roots collide. The base point is \(\rho=0\), with roots \(0,1,-1,i,-i\) numbered 1 to 5. Click a loop: \(\rho\) walks once around \(\beta_j\) and back, and the roots, tracked by continuation, exchange two of them. Compose several loops and watch the generated group grow.
\(\rho\)-plane: branch points \(\times\), base point 0, current loop.
\(w\)-plane: the five roots, numbered at \(\rho=0\), with trails.
Each simple branch point gives a transposition. The cover is connected of prime degree, so the monodromy group is a transitive subgroup of \(S_5\) generated by transpositions, hence \(S_5\): the Galois group of \(w^5-w+\rho\) over \(\mathbb C(\rho)\) is not solvable, which is Abel–Ruffini in geometric form. Hermite's formula does not contradict this. The modulus \(k\) is algebraic of degree 4 over \(\mathbb C(\rho^2)\), but the nome \(q\) is transcendental over it: the elliptic functions are the new ingredient.
Theory
The elliptic ingredients
Jacobi's theta constants with nome \(q\): \(\theta_2=2\sum_{n\ge0}q^{(n+1/2)^2}\) and \(\theta_3=\sum_{n\in\mathbb Z}q^{n^2}\). The modular function \(\lambda=k^2=\theta_2^4/\theta_3^4=16q-128q^2+704q^3-\cdots\) is a Hauptmodul for \(\Gamma(2)\). Its inverse series \(q=x+8x^2+84x^3+992x^4+\cdots\), with \(x=\lambda/16\), seeds the Newton iteration that finds \(q\) here to any precision, with no complex AGM needed.
Jacobi's modular equation of degree 5: with \(u=\lambda(q)^{1/8}\) and \(v=\lambda(q^5)^{1/8}\), \(u^6-v^6+5u^2v^2(u^2-v^2)-4uv(1-u^4v^4)=0\). For fixed \(u\) this is the Jacobi sextic in \(v\); its six roots belong to the six subgroups of order 5 of the period lattice modulo 5, with nomes \(q^5\) and \(\zeta^jq^{1/5}\). Its Galois group is \(PSL_2(\mathbb F_5)\cong A_5\), which acts on 6 points and also on 5. This isomorphism is the bridge between the quintic and the elliptic functions.
Hermite and Kronecker showed that the resolvent of the Bring–Jerrard quintic is, up to a Tschirnhaus transformation, Jacobi's sextic; this is why the roots are quotients of theta values at \(q^5\) and \(\zeta^jq^{1/5}\). Brioschi preferred the one-parameter normal form \(x^5-10Cx^3+45C^2x-C^2=0\). Klein (1884) explained all these solutions geometrically: \(A_5\) acts as the icosahedral group on the Riemann sphere (the modular curve \(X(5)\) has genus 0), and the quintic is solved by inverting the icosahedral invariants, with Schwarz's hypergeometric \({}_2F_1\).
Cockle and Harley (1860s) gave yet another closed form: the roots of \(x^5-x+\rho\) as hypergeometric \({}_4F_3\) series in \(\tfrac{3125}{256}\rho^4\). All of them converge only up to the nearest branch point \(|\rho|=\tfrac45\,5^{-1/4}\approx0.536\); the elliptic formula has no such limit, which is why it is the one used here.
What this app adds
Improving the classical recipe
Arithmetic
Typical numerical implementation Floating point at one working precision.
This app Big-integer fixed point at a precision you choose (20 to 250 digits); doubles only provide starting values.
Branches
Typical numerical implementation Eighth roots and arguments taken from the library; a wrong branch silently gives a non-root.
This app Arguments are tracked explicitly; all four moduli and both signs are tried and each is verified, so no wrong branch can pass.
Correctness
Typical numerical implementation A small residual is printed.
This app An exact certificate: \(f\) is evaluated in integers at dyadic points and Smith's discs prove that each root is isolated and located; the number of certified digits is reported.
This app Any integer quintic, non-monic, with huge coefficients. Repeated roots are detected by an exact gcd; \(d_1=0\) is solved by a fifth root; a vanishing quadratic step is handled.
Pedagogy
Typical numerical implementation A black box.
This app Every step shows its numbers and a Recall box; the Riemann surface and its monodromy are drawn; all intermediate quantities are exposed.
These are properties of this implementation, not a verdict on others. The 1994 poster of Trott and Adamchik showed the whole method at a glance; here the same mathematics is rebuilt so that every number can be checked.
History
From Bring to the elliptic solution
1786 Erland Bring reduces the general quintic to \(x^5+px+q\) by a quartic Tschirnhaus transformation.
1799–1824 Ruffini's incomplete proof, then Abel's proof (1824), that the general quintic is not solvable by radicals.
1832 Galois: solvability by radicals is solvability of the Galois group; \(A_5\) is simple.
1834 George Jerrard rediscovers and extends Bring's reduction: the Bring–Jerrard form.
1858 Hermite, Kronecker and Brioschi solve the quintic with elliptic modular functions; Hermite's theta formula is the one used here.
1884 Felix Klein, Lectures on the Icosahedron: the quintic as the problem of inverting the icosahedral invariants.
1994 Michael Trott and Victor Adamchik present Solving the Quintic with Mathematica at the International Congress of Mathematicians in Zürich.
2026 This app: the same route, with certified arithmetic and the monodromy on view.
Dates are those usually given in the literature; see the references below.
Exercises
Graduate exercises
Try them before opening the hints.
1. Show that the branch points of \(w^5-w+\rho\) are \(\rho^4=4^4/5^5\).
The discriminant of \(x^5+ax+b\) is \(5^5b^4+4^4a^5\); with \(a=-1\), \(b=\rho\) it vanishes iff \(5^5\rho^4=4^4\). Equivalently, \(5w^4=1\) and \(\rho=w-w^5=\tfrac45w\).
2. Prove that the Galois group of \(w^5-w+\rho\) over \(\mathbb C(\rho)\) is \(S_5\).
Since \(\rho=w-w^5\), the field \(\mathbb C(w)\) has degree 5 over \(\mathbb C(\rho)\), so the polynomial is irreducible. At a branch point two roots collide simply, so the inertia group is generated by a transposition. A transitive subgroup of \(S_p\) containing a transposition is \(S_p\).
3. Check that Jacobi's modular equation holds to the first two orders in \(q\) when \(u^8=\lambda(q)\) and \(v^8=\lambda(q^5)\).
From \(\lambda=16q(1-8q+\cdots)\): \(u^6=\lambda^{3/4}=8q^{3/4}(1-6q+\cdots)\) and \(uv=2q^{3/4}(1-q+\cdots)\), so \(4uv=8q^{3/4}(1-q+\cdots)\). The terms of order \(q^{3/4}\) in \(u^6-4uv\) cancel, leaving \(-40q^{7/4}\), which cancels against \(5u^4v^2=40q^{7/4}+\cdots\).
4. Why does Bring's reduction not contradict the Abel–Ruffini theorem?
It adjoins only square and cube roots, a solvable extension of the base field, and over the enlarged field the quintic and its Bring–Jerrard form have the same splitting field. So if \(z^5+d_1z+d_0\) were solvable by radicals for generic \(d_1,d_0\), so would be the general quintic; the whole difficulty is already in the two-parameter family, and after scaling in the one parameter \(\rho\).
5. Find the Lagrange series of the root of \(w^5-w+\rho\) that is near 0, and its radius of convergence.
The root solves \(w=\rho+w^5\), so \(w=\sum_{n\ge0}\binom{5n}{n}\frac{\rho^{4n+1}}{4n+1}=\rho+\rho^5+5\rho^9+35\rho^{13}+\cdots\). Its radius is the distance to the nearest branch point it meets, \(\tfrac45\,5^{-1/4}\approx0.536\).
References: C. Hermite, Sur la résolution de l'équation du cinquième degré, C. R. Acad. Sci. 46 (1858); L. Kronecker, same volume; F. Klein, Vorlesungen über das Ikosaeder (1884); B. T. Smith, Error bounds for zeros of a polynomial based upon Gerschgorin's theorems, J. ACM 17 (1970); V. Adamchik and D. Jeffrey, Polynomial transformations of Tschirnhaus, Bring and Jerrard, ACM SIGSAM Bulletin 37 (2003); R. B. King, Beyond the Quartic Equation (1996).