Finding Nature’s Optimal Path with Python
Every trajectory a physical system actually follows — a thrown ball, a planet, a beam of light — has a remarkable property: among all imaginable paths connecting a start point to an end point, the real one is the single path that makes the action stationary. This single idea, the principle of least (stationary) action, quietly underlies the whole of classical mechanics, and reproduces Newton’s laws as a special case.
In this article we take the simplest non-trivial case — a mass thrown straight up and falling back down under gravity — and use it to make the abstract idea concrete. We won’t just solve the equations of motion; we will build a small numerical laboratory that computes the action for a whole family of candidate paths and shows, visually, that the true physical trajectory sits exactly at the bottom of the action “valley.”
1. Setting up the problem
For a particle of mass $m$ moving along a single coordinate $x(t)$ under gravity, the Lagrangian is kinetic minus potential energy:
$$L(x,\dot x) = \frac{1}{2}m\dot x^2 - mgx$$
The action is the time integral of the Lagrangian along a path:
$$S[x] = \int_0^T L\big(x(t),\dot x(t)\big),dt$$
The principle of stationary action says that the physically realized path $x_{cl}(t)$ is the one for which $S$ does not change to first order under any small variation of the path that keeps the endpoints $x(0)=x_0$ and $x(T)=x_T$ fixed. Formally, this condition is expressed by the Euler–Lagrange equation:
$$\frac{d}{dt}\left(\frac{\partial L}{\partial \dot x}\right) - \frac{\partial L}{\partial x} = 0$$
Substituting our Lagrangian gives $m\ddot x = -mg$, i.e. $\ddot x = -g$ — Newton’s second law falls straight out. Integrating twice and fixing the boundary conditions gives the familiar parabola:
$$x_{cl}(t) = x_0 + v_0 t - \frac{1}{2}g t^2, \qquad v_0 = \frac{x_T-x_0}{T} + \frac{1}{2}gT$$
That’s the answer everyone already knows. The interesting part is verifying the variational principle itself: showing numerically that this particular parabola is truly the minimum of $S$ among a whole space of nearby competing paths.
2. Building a family of trial paths (the Ritz method)
To test many paths at once, we describe a trial path as the classical solution plus a correction built from a couple of sine modes that automatically vanish at both endpoints:
$$x(t;a_1,a_2) = x_{cl}(t) + a_1\eta_1(t) + a_2\eta_2(t), \qquad \eta_n(t) = \sin!\left(\frac{n\pi t}{T}\right)$$
Because $\eta_n(0)=\eta_n(T)=0$, every choice of $(a_1,a_2)$ satisfies the same boundary conditions as $x_{cl}$. Since $L$ is a quadratic function of $\dot x$ and a linear function of $x$, the action restricted to this two-parameter family is exactly a quadratic form:
$$S(a_1,a_2) = S_0 + b_1a_1 + b_2a_2 + \frac{1}{2}\Big(Q_{11}a_1^2 + 2Q_{12}a_1a_2 + Q_{22}a_2^2\Big)$$
If $x_{cl}$ truly satisfies the Euler–Lagrange equation, the linear (first-order) coefficients $b_1, b_2$ must vanish, meaning $(a_1,a_2)=(0,0)$ is a genuine stationary point of this paraboloid. That’s precisely what the code below checks and visualizes.
3. Python implementation
1 | import numpy as np |
4. Reading the code
Physical setup. x0, xT, and T define a boundary-value problem: where the particle starts, where it ends up, and how long the trip takes. v0 is derived directly from the boundary conditions using the closed-form parabola, so x_classical is the exact Euler–Lagrange solution — no ODE solver is needed here because the equation is simple enough to integrate by hand.
Trial-path basis. eta1 and eta2 are two independent sine modes that are exactly zero at $t=0$ and $t=T$. Adding any multiple of them to x_classical produces a new candidate path that still satisfies the same boundary conditions — this is the essence of the calculus of variations: comparing the true path only against paths that share its endpoints.
Why no loop is needed. Because the Lagrangian is quadratic in velocity and linear in position, the action of x_classical + a1*eta1 + a2*eta2 is algebraically a quadratic function of $(a_1,a_2)$. Rather than looping over a grid and numerically integrating the action thousands of times, the code integrates the six fixed coefficients ($S_0$, $b_1$, $b_2$, $Q_{11}$, $Q_{12}$, $Q_{22}$) once, and then evaluates action_quadratic(a1, a2) with plain NumPy arithmetic. This makes the full 80×80 surface (6,400 points) essentially free to compute, since it’s just element-wise operations on arrays instead of thousands of independent numerical integrations.
The stationarity check. b1 and b2 are the first-order sensitivities of the action to each perturbation mode. If x_classical truly extremizes the action, these must come out numerically negligible (they’ll print as very small floating-point residuals, not exactly zero, due to the discretization). Solving Q @ a = -b for the true minimum of the paraboloid and finding it lands on $(0,0)$ is the quantitative proof that the physical trajectory is the stationary point of the action.
Custom trapz. A hand-written trapezoidal integrator is used instead of relying on a specific NumPy/SciPy function name, since integration helpers have moved across library versions — this keeps the notebook robust regardless of the exact NumPy version Colab happens to have installed.
5. What the plots show
Panel 1 — trajectories. The solid blue parabola is the classical, physically-realized path. The dashed lines are competitor paths built by adding sine-mode “wiggles” — they still start and end at the same place and time, but bulge away from the parabola. Each one is printed with its own action value.
Panel 2 — the 1D action slice. This is a clean parabola in $\varepsilon$ with its minimum sitting exactly at $\varepsilon = 0$ — the classical path. Every other value of $\varepsilon$, in either direction, costs strictly more action.
Panel 3 — the 3D action landscape. This is the most direct visualization of “least action”: the whole surface is a paraboloid (bowl) in the two-parameter trial-path space, and the cyan marker — the classical solution — sits precisely at the bottom of that bowl. Any direction you move away from it, the action only increases.
=== Stationary-action check === S0 (action of the classical path) = -32.013734 J*s b1 (1st-order term, mode n=1) = -1.934e-04 b2 (1st-order term, mode n=2) = 7.105e-15 Q11 = 2.467350 Q22 = 9.868789 Q12 = 8.882e-16 Optimal (a1*, a2*) found by solving Q a = -b : [ 7.83781287e-05 -7.19996876e-16] -> should be numerically indistinguishable from (0, 0)

6. Takeaways
What makes this example satisfying is that it turns an abstract variational statement into something you can literally see: a bowl-shaped surface with the true law of motion sitting at the bottom of it. The same machinery — build a family of trial paths, form the action as a function of the trial parameters, and look for the stationary point — is exactly how far more sophisticated problems (geodesics in general relativity, optimal control, path-integral formulations of quantum mechanics) are approached numerically. Free fall under gravity is just the smallest possible sandbox in which to watch the principle of least action do its work.































