The Discretized Linear Wave
The linear wave equation in one dimension has the following Hamiltonian (see e.g. [2]):
\[ \mathcal{H}_\mathrm{cont}(q, p; \mu) := \frac{1}{2}\int_\Omega \mu^2(\partial_\xi q(t, \xi; \mu))^2 + p(t, \xi; \mu)^2 d\xi,\]
where the domain is $\Omega = (-1/2, 1/2)$. We then divide the interior of the domain into $\tilde{N}$ equidistantly spaced points $\xi_i = i\Delta_\xi - 1/2$ for $i = 1, \ldots, \tilde{N}$ and $\Delta_\xi := 1/(\tilde{N} + 1)$. The homogeneous Dirichlet boundary conditions fix $q_0 = q_{\tilde{N}+1} = 0$, so the two boundary points are not part of the state, which has $\tilde{N}$ positions and $\tilde{N}$ momenta. The resulting (semi-discrete) Hamiltonian is the one of [2]:
\[ \mathcal{H}_h(q, p) = \frac{1}{2} \sum_{i = 1}^{\tilde{N}} p_i^2 + \frac{\mu^2}{2\Delta_\xi^2} \sum_{i = 0}^{\tilde{N}} (q_{i+1} - q_i)^2 = \frac{1}{2} p^Tp - \frac{\mu^2}{2} q^T D_{\xi\xi} q,\]
where $D_{\xi\xi} = \mathrm{tridiag}(1, -2, 1)/\Delta_\xi^2$ is the central-difference matrix, and the equations of motion $\dot{q} = p$, $\dot{p} = \mu^2 D_{\xi\xi} q$ are Hamilton's equations with the canonical Poisson matrix.
The discretized linear wave equation is an example of a completely integrable system, i.e. a Hamiltonian system evolving in $\mathbb{R}^{2n}$ that has $n$ Poisson-commuting invariants of motion (see [3]).
For evaluating the system we specify the following initial[2] and boundary conditions:
\[\begin{aligned} q_0(\omega;\mu) := & q(0, \omega; \mu) \\ p(0, \omega; \mu) = \partial_tq(0,\xi;\mu) = & -\mu\partial_\omega{}q_0(\xi;\mu) \\ q(t,\omega;\mu) = & 0, \text{ for } \omega\in\partial\Omega. \end{aligned}\]
Solution
By default GeometricProblems uses the following parameters:
using GeometricIntegrators
using CairoMakie
import GeometricProblems.LinearWave as lw
lw.default_parameters()(μ = 0.6,)And if we integrate we get:
# A smaller lattice than the default Ñ = 256. The figure is indistinguishable, but an implicit step
# solves a dense 2n-dimensional nonlinear system, so the cost of the integration grows as n³.
N = 128
sol = integrate(lw.hodeproblem(N), ImplicitMidpoint())
Ω = lw.interior_domain(N) # the N interior points; q = 0 at ξ = ±1/2
nt = length(sol.t) - 1 # the time axis of a DataSeries runs 0:nt
fig = Figure(size = (800, 700))
ax1 = Axis(fig[1, 1]; ylabel = "q", title = "Snapshots")
for step in round.(Int, range(0, nt, length = 6)) # `0:nt÷5:nt` would stop short of the last step
lines!(ax1, Ω, sol.q[step, :]; label = "t = $(round(sol.t[step]; digits = 2))")
end
axislegend(ax1; position = :lt, labelsize = 10)
# q over the whole (ξ, t) plane: a single band with one slope, which is the one-directional travel
# that the snapshots above only hint at.
Q = [sol.q[n][i] for i in eachindex(Ω), n in 0:nt]
ax2 = Axis(fig[2, 1]; xlabel = "ξ", ylabel = "t", title = "Space-time")
hm = heatmap!(ax2, Ω, collect(sol.t), Q)
Colorbar(fig[2, 2], hm; label = "q")
linkxaxes!(ax1, ax2)
hidexdecorations!(ax1; grid = false)
fig
As we can see the pulse travels in one direction, at the speed $\mu$ set by the initial momentum $p(0, \omega; \mu) = -\mu \partial_\omega q_0$, and keeps its shape: the discretization is non-dispersive to the resolution of the figure.
Implementation
The equations of motion are written by hand. The force $-\partial V/\partial q = \mu^2 D_{\xi\xi} q$ is the three-point stencil $\mu^2 (q_{j-1} - 2q_j + q_{j+1})/\Delta_\xi^2$, in which the Dirichlet values $q_0 = q_{\tilde{N}+1} = 0$ drop one neighbor from the first and the last row; this is how ∇V! is written.
The Lagrangian $L = \tfrac{1}{2} \sum_k \dot q_k^2 - V(q)$ is regular: $\vartheta = \partial L/\partial\dot q = \dot q$, so the mass matrix $M = \partial\vartheta/\partial\dot q$ is the identity. It is therefore a second-order system of $n = \tilde{N}$ equations, equivalently first order in $2n$, and its Lagrange two-form is the $2n \times 2n$ canonical $\omega = [\,0\; -I;\; I\; 0\,]$.
Passing symbolic = true to any of the four constructors generates the equations of motion with EulerLagrange instead. The two agree to round-off and the tests check that they do, but the symbolic route does not scale: EulerLagrange builds $\omega$ by differentiating a dense $2n \times 2n$ matrix, so at the default $\tilde{N} = 256$ lodeproblem(; symbolic = true) takes 155 s and emits 14 MB of code for a two-form that no integrator evaluates. benchmark/linear_wave.jl has the measurements.
Library functions
GeometricProblems.LinearWave — Module
The discretized version of the 1d linear wave equation with homogeneous Dirichlet boundary conditions.
It is a prime example of a non-trivial completely integrable system.
The system is discretized on N equidistant interior points $ξ_i = iΔξ - 1/2$, $i = 1, …, N$, with $Δξ = 1/(N+1)$; N is the $\tilde{N}$ of the documentation page. The two boundary values $q_0 = q_{N+1} = 0$ are fixed and are not part of the state, which therefore has exactly N components. This is the full-order model of [2]: the Hamiltonian is $H = \tfrac12 p^Tp - \tfrac{μ^2}{2} q^T D_{ξξ} q$ with the central-difference matrix $D_{ξξ}$ and the canonical Poisson matrix.
Like the number of lattice sites of TodaLattice, N fixes the size of the system: it sets the number of degrees of freedom and the summation bounds, so it cannot survive symbolize and hence cannot be a system parameter. It is instead a leading positional argument of the problem constructors, defaulting to Ñ = 256, and hamiltonian/lagrangian take it as a trailing argument. The only system parameter proper is $\mu$.
hodeproblem, lodeproblem, hodeensemble and lodeensemble use hand-written vector fields by default: in-place v, f, ϑ, g and ω! over a shared ∇V! kernel. Passing symbolic = true generates the equations of motion with EulerLagrange via hamiltonian_system or lagrangian_system instead, which agrees to round-off and is kept for cross-checking.
The symbolic route is unusable at the default size. EulerLagrange builds the $2n × 2n$ Lagrange two-form by symbolically differentiating a dense matrix, which at $n = 258$ is 266 k entries: lagrangian_system(256, …) takes 155 s and emits 14 MB of code for an ω whose first evaluation costs a further 147 s — all for a two-form that no integrator in GeometricIntegrators ever evaluates and that GeometricEquations.check_methods skips. Construction grows as $n^{2.5}$ and the generated ω as $n^2$, while hodeproblem/lodeproblem in their default form build in a tenth of a millisecond at any size. Per call the hand-written code is the faster one too, by 1.4–1.9× for the force and 6–8× for H and L. See benchmark/linear_wave.jl. (These figures were measured before 0.10, with the two boundary points still in the state; the scaling is unchanged.)
GeometricProblems.LinearWave.compute_initial_condition — Method
Produces initial conditions for the bump function. Here the $p$-part is initialized with zeros.
GeometricProblems.LinearWave.compute_initial_condition2 — Method
Produces initial condition for the bump function. Here the $p$-part is initialized as $-\mu\partial_\xi u_0(\xi, \mu)$.
GeometricProblems.LinearWave.h — Method
Third-degree spline that is used as a basis to construct the initial conditions.
GeometricProblems.LinearWave.hamiltonian_system — Method
hamiltonian_system(N, parameters)The EulerLagrange HamiltonianSystem for the linear wave equation on N interior points, from which hodeproblem(…; symbolic = true) takes its v, f and H.
Cheap at every size — 1.9 s at the default Ñ = 256 — since a Hamiltonian system carries no two-form. Contrast lagrangian_system.
GeometricProblems.LinearWave.hodeensemble — Function
hodeensemble(N, q₀, p₀; timespan, timestep, parameters, symbolic)Hamiltonian ensemble for the linear wave equation (varying initial conditions and/or parameters).
Takes the same arguments as hodeproblem, with q₀, p₀ and/or parameters given as vectors of samples.
GeometricProblems.LinearWave.hodeproblem — Function
hodeproblem(N, q₀, p₀; timespan, timestep, parameters, symbolic)Hamiltonian problem for the linear wave equation on N interior points.
Constructor with default arguments:
hodeproblem(
N = 256,
q₀ = initial_condition(μ̃, N).q,
p₀ = initial_condition(μ̃, N).p;
timespan = (0, 1),
timestep = 0.005025125628140704,
parameters = (μ = 0.6,),
symbolic = false
)With symbolic = true the equations of motion are generated with EulerLagrange via hamiltonian_system instead of using the hand-written vector fields. The two agree to round-off; the symbolic route is kept for cross-checking and costs 1.9 s to build at N = 256.
GeometricProblems.LinearWave.initial_condition — Method
initial_condition(μ, N)The initial condition $q(0, ξ_i) = u_0(ξ_i; μ)$, $p(0, ξ_i) = -μ ∂_ξ u_0(ξ_i; μ)$ on the N interior_domain points, as a named tuple (q, p). Before it reaches a boundary, the solution of the continuous problem from this initial condition is the travelling wave $u_0(ξ - μt; μ)$.
GeometricProblems.LinearWave.interior_domain — Function
interior_domain(N, T = Float64)The N interior grid points $ξ_i = i/(N+1) - 1/2$, $i = 1, …, N$, at which the state is defined. compute_domain(N + 2) is the same grid with the two boundary points included.
GeometricProblems.LinearWave.lagrangian_system — Method
lagrangian_system(N, parameters)The EulerLagrange LagrangianSystem for the linear wave equation on N interior points, from which lodeproblem(…; symbolic = true) takes its ϑ, f, g, ω and L.
This is the expensive one: it builds the $2n × 2n$ two-form by differentiating a dense matrix, so its cost grows as $n^{2.5}$ and reaches 155 s at the default Ñ = 256. See the module docstring and benchmark/linear_wave.jl.
GeometricProblems.LinearWave.lodeensemble — Function
lodeensemble(N, q₀, p₀; timespan, timestep, parameters, symbolic)Lagrangian ensemble for the linear wave equation (varying initial conditions and/or parameters).
Takes the same arguments as lodeproblem, with q₀, p₀ and/or parameters given as vectors of samples.
GeometricProblems.LinearWave.lodeproblem — Function
lodeproblem(N, q₀, p₀; timespan, timestep, parameters, symbolic)Lagrangian problem for the linear wave equation on N interior points.
Constructor with default arguments:
lodeproblem(
N = 256,
q₀ = initial_condition(μ̃, N).q,
p₀ = initial_condition(μ̃, N).p;
timespan = (0, 1),
timestep = 0.005025125628140704,
parameters = (μ = 0.6,),
symbolic = false
)With symbolic = true the equations of motion are generated with EulerLagrange via lagrangian_system instead of using the hand-written vector fields. The two agree to round-off, but the symbolic route takes 155 s to build at N = 256 — see the module docstring.
GeometricProblems.LinearWave.potential — Method
potential(q, parameters, N)The discretized gradient energy on N interior points with $q_0 = q_{N+1} = 0$,
\[V(q) = \frac{\mu^2}{2Δξ^2} \sum_{i = 0}^{N} (q_{i+1} - q_i)^2 = -\frac{\mu^2}{2} q^T D_{ξξ} q, \qquad Δξ = \frac{1}{N+1},\]
where $D_{ξξ}$ is the $N × N$ central-difference matrix $\mathrm{tridiag}(1, -2, 1)/Δξ^2$.
Both the Hamiltonian and the Lagrangian take N as a trailing argument (as in TodaLattice) rather than reading it from parameters: it fixes the number of degrees of freedom and the summation bounds, so it has to be a plain integer and cannot survive symbolize.
GeometricProblems.LinearWave.ω! — Method
ω!(Ω, t, q, params)The Lagrange two-form $ω = dθ$ of this regular Lagrangian, where $θ = ϑ_i \, dq^i$.
A regular Lagrangian is a second-order system of $n$ equations, equivalent to a first-order system of $2n$, so Ω is the $2n × 2n$ form on $(q, \dot q)$ with $n = N$. In block form it is $[∂ϑ_i/∂q_j - ∂ϑ_j/∂q_i \; -M^T; \; M \; 0]$; since $ϑ = ∂L/∂\dot q = \dot q$ depends on the velocities alone and the mass matrix is the identity, the upper-left block vanishes and what remains is the canonical $[0 \; -I; \; I \; 0]$.
This is the convention EulerLagrange's LagrangianSystem produces, so lodeproblem agrees whether or not symbolic = true. Note that no integrator in GeometricIntegrators evaluates ω and GeometricEquations.check_methods skips it — which is what makes the 14 MB of code EulerLagrange emits for it at the default size, and the 147 s its first evaluation then costs, pure overhead.
GeometricProblems.LinearWave.∇V! — Function
∇V!(dV, q, parameters, N, α = 1)$α$ times the gradient $∂V/∂q = -μ^2 D_{ξξ} q$ of the discretized gradient energy potential, written into dV. α = -1 gives the force $μ^2 D_{ξξ} q$ directly, which is what the force functions want and saves a second pass over the state compared with negating afterwards.
The boundary values $q_0 = q_{N+1} = 0$ enter the stencil at the first and the last point, so the two end rows drop one neighbor and the loop over the interior rows is branch-free. test/linear_wave.jl checks this against a central finite difference of potential at several N, including N = 1, where the two end rows coincide.
- [2]
- 2The precise shape of $q_0(\cdot;\cdot)$ is described in the chapter on initial conditions.