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
Example block output

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.)

source
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.

source
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.

source
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.

source
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; μ)$.

source
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.

source
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.

source
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.

source
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.

source
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.

source
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.

source
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.

source
[2]
P. Buchfink, S. Glas and B. Haasdonk. Symplectic model reduction of Hamiltonian systems on nonlinear manifolds and approximation with weakly symplectic autoencoder. SIAM Journal on Scientific Computing 45, A289–A311 (2023). ↩1 ↩2 ↩3 ↩4