Skip to content

TimeDependentPDE: wave equations, time-dependent boundary values, stable high-order operators - #116

Merged
maroba merged 5 commits into
masterfrom
stable-boundary-closure
Oct 22, 2026
Merged

maroba merged 5 commits into
masterfrom
stable-boundary-closure

Conversation

@maroba

@maroba maroba commented Oct 22, 2026

Copy link
Copy Markdown
Owner

Improvements to TimeDependentPDE towards wave equations (#63), plus a fix for unstable time-dependent problems. Five commits that build on each other.

1. Faster implicit methods (ff09bd8)

The implicit steppers converted the full system matrix to LIL format in every step to inject boundary values, rebuilt the system whenever dt differed by round-off (as with np.linspace time points), and did not reuse the factorization. Now boundary rows are injected once with sparse masks, dt is compared with a relative tolerance, and the LU factorization is reused. Results are unchanged up to round-off; a Crank-Nicolson step on a 100x100 grid takes 0.8 ms instead of 148 ms.

2. Time-dependent boundary values and source terms (7b6a575)

bc[0] = lambda t: np.sin(2 * np.pi * t)        # also for Neumann and Robin
TimeDependentPDE(L, u0, bc, t, source=lambda t: ...)   # u_t = L(u) + s(t)

Values are evaluated at the times each method requires (including RK4 stages); with a manufactured solution all methods reach their order (Euler 1, RK4 4, Crank-Nicolson 2). PDE.solve() rejects time-dependent boundary conditions.

3. Second order in time (002f0f2)

Passing the initial velocity v0 solves u_tt = L(u) + s(t), e.g. the wave equation. New explicit method verlet (Störmer-Verlet, symplectic); rk4, crank-nicolson and backward-euler work via the equivalent first-order system (Crank-Nicolson is then equivalent to Newmark with β = 1/4, γ = 1/2). forward-euler is rejected (unstable for undamped waves). MOLSolution.v holds the velocities.

4. Stable second derivatives (60b3814)

While testing wave equations it turned out that one-sided high-order boundary stencils make time-dependent problems grow exponentially, regardless of the time stepper:

  • Equidistant grids: complex eigenvalues (fatal for waves) from acc=4, eigenvalues with positive real part (fatal for diffusion) from acc=6 on. E.g. the heat equation with acc=6 blows up to 1e+68 instead of decaying.
  • Non-uniform grids: since the wider central stencils of 0.14 (Fix accuracy-order loss for even derivatives on non-uniform grids #112), points next to the boundary used one-sided stencils starting at the point itself, which made even acc=2 unstable on Chebyshev grids.

Changes:

  • New option Diff(..., boundary="centered") for equidistant grids: points near the boundary use the widest centered stencil that fits. The spectrum stays real and negative for any acc; the global error of time-dependent problems still converges with 4th–5th order (e.g. waves with acc=6: error 1e-5 instead of blow-up).
  • Non-uniform grids: points near the boundary now use the stencil points closest to the boundary, including the boundary point. acc=2 and acc=4 are stable for diffusion and waves on Chebyshev, tanh and power-law grids, and mostly more accurate. Interior points and the boundary points themselves are unchanged.
  • TimeDependentPDE checks the spectrum of each second derivative (1D, at most 201 points, a few ms) and warns if the problem would be unstable.

5. Docs (8645148)

  • All Diff parameters documented in the API reference.
  • Guides for time-dependent PDEs, boundary conditions and non-uniform grids updated; README and index; fixed a broken link to the compact schemes guide.
  • The docs workflow now also builds on pull requests (without deploying).

Behavior changes (see CHANGES.md)

  • Non-uniform grids: results at the points next to the boundary (not the boundary points themselves) change; they become more accurate.
  • Equidistant grids: default unchanged; boundary="centered" is opt-in.

Test plan

  • Full test suite passes locally (531 passed, 32 skipped); all new tests fail on the previous code
  • Results of unchanged functionality compared numerically with the previous code (implicit steppers, static boundary conditions, interior of non-uniform operators)
  • Docs built locally with Sphinx 9.2, no new warnings
  • CI green
  • Docs build in CI

The implicit steppers converted the full system matrix to LIL format in
every time step just to inject boundary values into the right-hand side,
rebuilt the system whenever dt differed by round-off (as with time
points from np.linspace), and called spsolve without reusing the
factorization.

Boundary conditions are now injected once with sparse masks, only the
RHS boundary values are set per step, dt is compared with a relative
tolerance of 1e-10, and the LU factorization of the direct solver is
reused. Results are unchanged up to round-off; a step takes 0.8 ms
instead of 148 ms on a 100x100 grid.
Boundary values may now be callables g(t) (Dirichlet, Neumann and
Robin), returning a scalar, an array of the boundary shape or of the
grid shape. TimeDependentPDE accepts a source term (array or callable
s(t)), solving u_t = L(u) + s(t).

The steppers receive the current time and evaluate boundary values and
sources at the times each method requires, including the RK4 stages,
so all methods keep their order of accuracy (verified with a
manufactured solution). PDE.solve() rejects time-dependent boundary
conditions with a clear error. Results for static problems are
unchanged.
Passing the initial velocity v0 solves u_tt = L(u) + s(t), e.g. the
wave equation. New explicit method 'verlet' (Stoermer-Verlet, second
order, symplectic); 'rk4', 'crank-nicolson' and 'backward-euler' are
applied to the equivalent first-order system (Crank-Nicolson is then
equivalent to Newmark with beta = 1/4, gamma = 1/2). 'forward-euler' is
rejected since it is unstable for undamped waves. Time-dependent
boundary values and sources are supported; MOLSolution.v holds the
velocities.

Verified with a manufactured solution (all methods reach their order)
and long-time standing waves. Documents a known issue: one-sided
high-order boundary stencils for second derivatives make time-dependent
problems unstable from acc=4 (waves) or acc=6 (heat equation) on.
One-sided high-order boundary stencils give the discrete second
derivative eigenvalues that make time-dependent problems grow
exponentially regardless of the time stepper: complex eigenvalues
(fatal for waves) from acc=4 and eigenvalues with positive real part
(fatal for diffusion) from acc=6 on equidistant grids.

- New option Diff(..., boundary="centered") for equidistant
  non-periodic grids: points near the boundary use the widest centered
  stencil that fits. The spectrum stays real and negative for any acc;
  the global error of time-dependent problems still converges with
  fourth to fifth order.
- Non-uniform grids: since the wider central stencils of 0.14, points
  next to the boundary used one-sided stencils starting at the point
  itself, which made even acc=2 unstable on Chebyshev grids. They now
  use the stencil points closest to the boundary, including the
  boundary point; acc=2 and acc=4 are stable for diffusion and waves.
  Interior points and the boundary points themselves are unchanged.
- TimeDependentPDE checks the spectrum of each second derivative in the
  operator (1D, at most 201 points) and warns if the problem would be
  unstable.
- Document all parameters of Diff in its docstring (used by the API
  reference), including the new `boundary` option.
- Time-dependent PDE guide: mention source terms and second order in
  time in the introduction, add 'verlet' to the methods table.
- Boundary conditions guide: time-dependent boundary values.
- Non-uniform grids guide: new treatment of points near the boundary.
- Index and README: feature lists and "new in 0.16"; fix the broken
  link to the compact finite differences guide in the README.
- Build the docs on pull requests as well (without deploying).
@codecov

codecov Bot commented Oct 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.10997% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.18%. Comparing base (08c8455) to head (8645148).

Files with missing lines Patch % Lines
findiff/ivp.py 95.19% 7 Missing and 3 partials ⚠️
findiff/findiff.py 98.27% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #116      +/-   ##
==========================================
+ Coverage   94.87%   95.18%   +0.31%     
==========================================
  Files          42       43       +1     
  Lines        5915     6423     +508     
  Branches      342      399      +57     
==========================================
+ Hits         5612     6114     +502     
- Misses        251      254       +3     
- Partials       52       55       +3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@maroba
maroba merged commit 89d1551 into master Oct 22, 2026
13 checks passed
vnmabus added a commit to GAA-UAM/scikit-fda that referenced this pull request Nov 5, 2026
It turns out that the results of the derivatives change again in this
version: maroba/findiff#116
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant