mathematicskit.pde#
The heat equation in 1D and 2D by the method of lines (finite
differences in space, mathematicskit.integrators in time) and by the
theta-method family (FTCS, Crank-Nicolson, backward Euler via
scipy.sparse.linalg.splu); the 1D wave equation with symplectic
leapfrog stepping and d’Alembert’s solution; linear advection with
upwind, Lax-Friedrichs, and Lax-Wendroff schemes; Poisson and Laplace
problems solved directly (scipy.sparse.linalg.spsolve) or with
mathematicskit.linalg’s CG/Jacobi/Gauss-Seidel/SOR iterations; CFL
checks and von Neumann amplification factors; P1 finite elements;
multigrid V-cycles; Godunov’s method for shocks in Burgers’ equation and
the Hopf-Cole exact solution; and Fourier and Chebyshev spectral methods.
mathematicskit.pde: partial differential equations, built on mathematicskit.integrators and mathematicskit.linalg.
The heat equation in 1D and 2D, by the method of lines (space discretized
with finite differences, time integrated by
mathematicskit.integrators’ rk4/dopri5) and by the
theta-method family – explicit FTCS, Crank-Nicolson, backward Euler –
with scipy.sparse.linalg.splu(); Fourier’s sine-series solution;
the 1D wave equation, advanced symplectically by
mathematicskit.integrators.leapfrog_integrate(), against
d’Alembert’s traveling-wave solution; linear advection with upwind,
Lax-Friedrichs, Lax-Wendroff, and FTCS schemes (hand-rolled – no library
equivalent); Poisson and Laplace problems with sparse direct solves
(scipy.sparse.linalg.spsolve()) or mathematicskit.linalg’s
conjugate gradient, Jacobi, Gauss-Seidel, and SOR iterations; CFL and
diffusion-number checks and von Neumann amplification factors; and
spectral methods – Fourier differentiation (numpy.fft and
differentiation matrices), a pseudo-spectral viscous Burgers solver, and
Chebyshev collocation for boundary-value problems built on
mathematicskit.numerical_analysis.chebyshev_nodes().
- class mathematicskit.pde.AdvectionEquation1D(u0, length=1.0, n=200, c=1.0)[source]#
Bases:
objectLinear advection \(u_t + c\,u_x = 0\) on the periodic interval \([0, L)\).
- Parameters:
Examples
>>> import numpy as np >>> adv = AdvectionEquation1D(lambda x: np.sin(2 * np.pi * x), n=200) >>> sol = adv.solve(1.0, dt=0.004, scheme="lax_wendroff") # one full period >>> sol.extra["stability"].number 0.8 >>> bool(np.max(np.abs(sol.final - adv.exact(1.0))) < 1e-2) True
- class mathematicskit.pde.BurgersConservationLaw1D(u0, x_range=(0.0, 1.0), n=200, bc='outflow')[source]#
Bases:
objectInviscid Burgers’ equation \(u_t + (u^2/2)_x = 0\) on \([a, b]\), finite-volume schemes.
- Parameters:
Examples
>>> import numpy as np >>> law = BurgersConservationLaw1D(lambda x: np.where(x < 0.0, 1.0, 0.0), x_range=(-1.0, 1.0), n=200) >>> sol = law.solve(0.8, dt=0.005, scheme="godunov") >>> exact = burgers_riemann_solution(1.0, 0.0, sol.x, 0.8) >>> bool(np.mean(np.abs(sol.final - exact)) < 0.01) True
- class mathematicskit.pde.BurgersEquation1D(u0, length=6.283185307179586, n=64, nu=0.05)[source]#
Bases:
MethodOfLinesPDEViscous Burgers’ equation \(u_t + u\,u_x = \nu\,u_{xx}\), periodic, Fourier pseudo-spectral.
Derivatives come from
fourier_differentiation_matrix(); the nonlinear product \(u\,u_x\) is formed pointwise in physical space (the pseudo-spectral, or collocation, approach of Orszag 1969-1971 and Kreiss & Oliger 1972), without dealiasing. The resulting ODE system is integrated bymathematicskit.integratorsthrough an@njitright-hand side that receives the flattened matrices inparams.- Parameters:
Examples
>>> import numpy as np >>> burgers = BurgersEquation1D(lambda x: np.sin(x), n=32, nu=0.1) >>> sol = burgers.solve(1.0, dt=1e-3, save_every=100) >>> bool(abs(sol.final.mean() - sol.u[0].mean()) < 1e-10) # the mean is conserved True
- class mathematicskit.pde.EllipticSolution(x, u, y=None, method='', residual_norm=0.0, solver_result=None)[source]#
Bases:
objectOutput of a steady boundary-value solve (Laplace, Poisson,
u'' = f).u[i](1D) oru[i, j](2D,"ij"indexing) is the discrete solution atx[i](andy[j]), boundary points included.- Parameters:
- residual_norm: float = 0.0#
2-norm of the residual of the assembled linear system at the solution.
- Type:
- solver_result: object | None = None#
The full
IterativeSolveResult(residual history, iteration count) when an iterativemathematicskit.linalgsolver was used.- Type:
IterativeSolveResult, optional
- class mathematicskit.pde.HeatEquation1D(u0, length=1.0, n=51, alpha=1.0, bc='dirichlet', boundary_values=(0.0, 0.0))[source]#
Bases:
MethodOfLinesPDEThe 1D heat equation \(u_t = \alpha u_{xx}\) on \([0, L]\).
- Parameters:
u0 (
Callable|ndarray) – Initial temperature,u0(x)or its grid values.length (
float) – Rod length \(L\).n (
int) – Number of grid points (both endpoints included for Dirichlet; the right endpoint omitted for periodic).alpha (
float) – Diffusivity.bc (
str)boundary_values (tuple of float) – Fixed end temperatures
(u(0), u(L))forbc="dirichlet".
Examples
>>> import numpy as np >>> heat = HeatEquation1D(lambda x: np.sin(np.pi * x), n=41) >>> sol = heat.solve(0.1, dt=1e-4) >>> exact = np.sin(np.pi * sol.x) * np.exp(-np.pi**2 * 0.1) >>> bool(np.max(np.abs(sol.final - exact)) < 1e-3) True >>> cn = heat.solve_theta(0.1, dt=1e-3, theta=0.5) >>> bool(np.max(np.abs(cn.final - exact)) < 1e-3) True
- max_stable_dt(method='rk4')[source]#
Largest stable step:
2.785 dx^2 / (4 alpha)for"rk4",dx^2 / (2 alpha)for"ftcs",inffor implicit schemes.
- solve_theta(t_final, dt, theta=0.5, save_every=1)[source]#
March the theta-method: FTCS (
theta=0), Crank-Nicolson (0.5), backward Euler (1).- Parameters:
- Return type:
- Returns:
PDESolution –
methodis"ftcs","crank_nicolson","backward_euler", or"theta=<value>";extra["stability"]ischeck_diffusion_stability()for this step.
- class mathematicskit.pde.HeatEquation2D(u0, lengths=(1.0, 1.0), n=(41, 41), alpha=1.0, boundary_value=0.0)[source]#
Bases:
MethodOfLinesPDEThe 2D heat equation \(u_t = \alpha (u_{xx} + u_{yy})\) on a rectangle, fixed boundary temperature.
Semi-discretized with the five-point Laplacian and integrated by the method of lines through
mathematicskit.integrators(solve()), or by the theta-method (solve_theta()).- Parameters:
u0 (
Callable|ndarray) – Initial temperatureu0(X, Y)(called on"ij"meshgrids) or its grid values.lengths (tuple of float) – Rectangle side lengths
(Lx, Ly); the domain is[0, Lx] x [0, Ly].n (tuple of int) – Grid points
(nx, ny), boundaries included.alpha (
float) – Diffusivity.boundary_value (
float) – Temperature held on the whole boundary.
Examples
>>> import numpy as np >>> heat = HeatEquation2D(lambda X, Y: np.sin(np.pi * X) * np.sin(np.pi * Y), n=(21, 21)) >>> sol = heat.solve(0.02, dt=2e-4, save_every=10) >>> sol.u.shape (11, 21, 21) >>> exact = np.exp(-2 * np.pi**2 * 0.02) >>> bool(abs(sol.final[10, 10] - exact) < 5e-3) True
- max_stable_dt(method='rk4')[source]#
Largest stable step for
"rk4"or"ftcs";inffor implicit schemes.
- solve_theta(t_final, dt, theta=0.5, save_every=1)[source]#
March the theta-method on the five-point Laplacian; see
HeatEquation1D.solve_theta().- Return type:
- Returns:
PDESolution –
extra["stability"]is the step-size check fromstability().- Parameters:
- class mathematicskit.pde.MethodOfLinesPDE[source]#
Bases:
ABCCommon base for a time-dependent PDE semi-discretized in space.
Concrete subclasses set
self._rhs_njit(an@njitfunction with signature(state, t, params) -> dstate),self.params, andself.state0(the flattened initial ODE state), and implement_to_field()to turn a batch of ODE states back into grid values (re-attaching boundary points, reshaping 2D grids).- max_stable_dt(method='rk4')[source]#
Largest step size for which method is linearly stable on this grid.
Subclasses override this; the default
infmeans “no known limit”.
- solve(t_final, dt=None, method='rk4', save_every=1, **kwargs)[source]#
Integrate the semi-discrete system from
t = 0to t_final.- Parameters:
t_final (
float)dt (
Optional[float]) – Fixed step (required for"rk4"), shrunk if needed so a whole number of steps lands exactly on t_final; initial step attempt for"dopri5".method (
str) –"rk4"or"dopri5"(subclasses may add more, e.g."leapfrog").save_every (
int) – Keep every save_every-th time sample (the last one is always kept).**kwargs – Forwarded to
mathematicskit.integrators.dopri5_integrate().
- Return type:
- Returns:
PDESolution –
extra["stability"]holdsstability()for the fixed step dt (omitted for"dopri5", which controls its own step).
- stability(dt, method='rk4')[source]#
Check dt against
max_stable_dt()for method.- Parameters:
- Return type:
- Returns:
StabilityResult –
numberandlimitare expressed as step sizes here.
- class mathematicskit.pde.PDESolution(t, x, u, y=None, method='', extra=<factory>)[source]#
Bases:
objectOutput of a time-dependent PDE solve, sampled on a fixed spatial grid.
For a 1D problem
u[k, i]is the solution at timet[k]and positionx[i]; for a 2D problemu[k, i, j]is the solution at(x[i], y[j])("ij"indexing). Boundary points are included.- extra: dict#
Free-form diagnostics, e.g.
"stability"(aStabilityResultfor the step size used) or"v"(the velocity field of a wave-equation solve).- Type:
- method: str = ''#
Time-stepping scheme used, e.g.
"rk4","dopri5","leapfrog","crank_nicolson", or"upwind".- Type:
- class mathematicskit.pde.StabilityResult(number, limit, stable, scheme='', quantity='')[source]#
Bases:
objectOutcome of a CFL / diffusion-number stability check for one step size.
- limit: float#
The largest stable value of number for this scheme (
inffor an unconditionally stable scheme,0for an unconditionally unstable one).- Type:
- class mathematicskit.pde.WaveEquation1D(u0, v0=None, length=1.0, n=101, c=1.0)[source]#
Bases:
MethodOfLinesPDEA vibrating string \(u_{tt} = c^2 u_{xx}\) on \([0, L]\) with \(u(0) = u(L) = 0\).
- Parameters:
Examples
>>> import numpy as np >>> string = WaveEquation1D(lambda x: np.sin(np.pi * x), n=101) >>> sol = string.solve(1.0, dt=0.01, method="leapfrog") # Courant number 1 >>> sol.extra["stability"].stable True >>> bool(np.allclose(sol.final, -np.sin(np.pi * sol.x), atol=1e-3)) # half a period later True
- energy(solution)[source]#
Discrete energy \(E = \tfrac12 \sum_i v_i^2\,\Delta x + \tfrac12 c^2 \sum_i \big((u_{i+1} - u_i)/\Delta x\big)^2 \Delta x\) at each saved time.
Conserved by the exact PDE; leapfrog keeps it bounded (oscillating near its initial value), RK4 slowly dissipates it.
- Parameters:
solution (
PDESolution) – Fromsolve().- Return type:
- Returns:
ndarray, shape (n_t,)
- max_stable_dt(method='leapfrog')[source]#
dx / cfor"leapfrog"(the CFL limit), \(\sqrt2\,dx/c\) for"rk4",infotherwise.
- solve(t_final, dt=None, method='leapfrog', save_every=1, **kwargs)[source]#
Integrate from
t = 0to t_final with"leapfrog"(default),"rk4", or"dopri5".See
MethodOfLinesPDE.solve(). The velocity field is returned inextra["v"](same shape asu).- Return type:
- Parameters:
- mathematicskit.pde.amplification_factor(scheme, number, xi)[source]#
Von Neumann amplification factor \(G(\xi)\) of a linear scheme.
With \(r\) the diffusion number and \(\nu\) the Courant number (advection speed \(c > 0\)), and \(s = \sin^2(\xi/2)\):
"ftcs_heat"\(1 - 4 r s\)
"backward_euler_heat"\(1 / (1 + 4 r s)\)
"crank_nicolson_heat"\((1 - 2 r s) / (1 + 2 r s)\)
"upwind"\(1 - \nu (1 - e^{-i\xi})\)
"lax_friedrichs"\(\cos\xi - i \nu \sin\xi\)
"lax_wendroff"\(1 - i \nu \sin\xi - \nu^2 (1 - \cos\xi)\)
"ftcs_advection"\(1 - i \nu \sin\xi\)
See Strikwerda, Ch. 2.2, and LeVeque, Ch. 9.6 and 10.5.
- Parameters:
- Return type:
- Returns:
ndarray of complex
Examples
>>> import numpy as np >>> float(abs(amplification_factor("upwind", 1.0, np.pi))) 1.0 >>> float(abs(amplification_factor("ftcs_heat", 0.5, np.pi))) 1.0
- mathematicskit.pde.burgers_cole_hopf_solution(u0, t, nu, length=6.283185307179586, n=256)[source]#
Exact periodic solution of viscous Burgers’ equation by the Hopf-Cole transformation.
Hopf (1950) and Cole (1951) showed that \(u = -2\nu\,\varphi_x/\varphi\) turns \(u_t + u u_x = \nu u_{xx}\) into the heat equation \(\varphi_t = \nu \varphi_{xx}\), with \(\varphi(x, 0) = \exp\!\big(-\tfrac{1}{2\nu}\int_0^x u_0\big)\). Here the antiderivative, the heat evolution (each Fourier mode times \(e^{-\nu k^2 t}\)), and \(\varphi_x\) are all computed spectrally with
numpy.fft, so the result is exact up to the resolution of then-point grid. \(\varphi\) spans roughly \(e^{\pm \max|\int u_0| / (2\nu)}\), so very small nu exhausts double precision.- Parameters:
- Return type:
- Returns:
x (ndarray, shape (n,))
u (ndarray, shape (n,))
Examples
>>> import numpy as np >>> x, u = burgers_cole_hopf_solution(np.sin, t=1.0, nu=0.1, n=128) >>> sol = BurgersEquation1D(np.sin, n=128, nu=0.1).solve(1.0, dt=1e-3) >>> bool(np.max(np.abs(sol.final - u)) < 1e-10) True
- mathematicskit.pde.burgers_riemann_solution(u_left, u_right, x, t, x0=0.0)[source]#
Exact entropy solution of the Burgers Riemann problem with a jump at x0.
A shock moving at the Rankine-Hugoniot speed \(s = (u_L + u_R)/2\) if \(u_L > u_R\); otherwise a rarefaction fan \(u = (x - x_0)/t\) between \(u_L\) and \(u_R\).
- Parameters:
- Return type:
- Returns:
ndarray
Examples
>>> burgers_riemann_solution(1.0, 0.0, [0.4, 0.6], t=1.0).tolist() # shock at x = 0.5 [1.0, 0.0] >>> burgers_riemann_solution(0.0, 1.0, [-0.5, 0.25, 2.0], t=1.0).tolist() # rarefaction [0.0, 0.25, 1.0]
- mathematicskit.pde.chebyshev_differentiation_matrix(n, a=-1.0, b=1.0)[source]#
Chebyshev collocation differentiation matrix on
nChebyshev points of[a, b].\[D_{ij} = \frac{c_i}{c_j} \frac{(-1)^{i+j}}{x_i - x_j}\ (i \ne j), \qquad D_{ii} = -\sum_{j \ne i} D_{ij},\]with \(c_0 = c_{n-1} = 2\) and \(c_i = 1\) otherwise; the “negative sum trick” for the diagonal makes \(D\) exact on constants and improves rounding (Trefethen 2000, Ch. 6,
cheb.m). Points come frommathematicskit.numerical_analysis.chebyshev_nodes(), in increasing order.- Parameters:
- Return type:
- Returns:
D (ndarray, shape (n, n))
x (ndarray, shape (n,))
Examples
>>> import numpy as np >>> D, x = chebyshev_differentiation_matrix(12) >>> bool(np.allclose(D @ x**5, 5 * x**4)) # exact for polynomials of degree < n True
- mathematicskit.pde.chebyshev_poisson_1d(f, n=32, a=-1.0, b=1.0, ua=0.0, ub=0.0)[source]#
Solve \(u'' = f\) on \([a, b]\) with \(u(a) = u_a\), \(u(b) = u_b\) by Chebyshev collocation.
Collocates \(D^2 u = f\) at the interior Chebyshev points and moves the known boundary columns to the right-hand side (Trefethen 2000, Ch. 7). For analytic f the error decays geometrically in n.
- Parameters:
- Return type:
- Returns:
EllipticSolution
Examples
>>> import numpy as np >>> sol = chebyshev_poisson_1d(lambda x: np.exp(x), n=16, ua=np.exp(-1), ub=np.exp(1)) >>> bool(np.max(np.abs(sol.u - np.exp(sol.x))) < 1e-12) True
- mathematicskit.pde.chebyshev_poisson_2d(f, n=24, a=-1.0, b=1.0)[source]#
Solve \(u_{xx} + u_{yy} = f\) on the square \([a, b]^2\) with \(u = 0\) on the boundary.
Tensor-product Chebyshev collocation: the interior operator is \(\tilde D^2 \otimes I + I \otimes \tilde D^2\) (Trefethen 2000, Ch. 7, program
p16), solved densely withmathematicskit.linalg.lu_solve_system().- Parameters:
- Return type:
- Returns:
EllipticSolution
Examples
>>> import numpy as np >>> f = lambda X, Y: -2 * np.pi**2 * np.sin(np.pi * X) * np.sin(np.pi * Y) >>> sol = chebyshev_poisson_2d(f, n=20) >>> X, Y = np.meshgrid(sol.x, sol.y, indexing="ij") >>> bool(np.max(np.abs(sol.u - np.sin(np.pi * X) * np.sin(np.pi * Y))) < 1e-8) True
- mathematicskit.pde.check_cfl(c, dt, dx, scheme='upwind')[source]#
Check the Courant-Friedrichs-Lewy condition for an advection/wave scheme.
The limits (LeVeque 2007, Ch. 10) are \(\nu \le 1\) for
"upwind","godunov","lax_friedrichs","lax_wendroff"and the"leapfrog"(velocity-Verlet) wave scheme; forward-time centered-space"ftcs"advection is unstable for every \(\nu > 0\).- Parameters:
- Return type:
- Returns:
StabilityResult
Examples
>>> check_cfl(1.0, 0.009, 0.01).stable True >>> check_cfl(1.0, 0.011, 0.01).stable False
- mathematicskit.pde.check_diffusion_stability(alpha, dt, dx, theta=0.0, ndim=1)[source]#
Check the diffusion-number limit of the theta-method for the heat equation.
The theta-method (\(\theta = 0\) explicit FTCS, \(\tfrac12\) Crank-Nicolson, 1 backward Euler) on the standard second-difference Laplacian in ndim dimensions (equal spacing) is stable iff
\[r \le \frac{1}{2\,\text{ndim}\,(1 - 2\theta)} \quad (\theta < \tfrac12),\]and unconditionally stable for \(\theta \ge \tfrac12\) (Strikwerda, Ch. 6.3; LeVeque, Ch. 9.6).
- Parameters:
- Return type:
- Returns:
StabilityResult
Examples
>>> check_diffusion_stability(1.0, 0.004, 0.1).stable # r = 0.4 True >>> check_diffusion_stability(1.0, 0.006, 0.1).stable # r = 0.6 False >>> check_diffusion_stability(1.0, 1.0, 0.1, theta=0.5).limit inf
- mathematicskit.pde.courant_number(c, dt, dx)[source]#
Courant number \(\nu = |c|\,\Delta t / \Delta x\).
- Parameters:
- Return type:
- Returns:
float
Examples
>>> courant_number(2.0, 0.01, 0.04) 0.5
- mathematicskit.pde.dalembert_solution(f, x, t, c=1.0, g_antiderivative=None, length=None)[source]#
D’Alembert’s solution of the wave equation (d’Alembert, 1747).
\[u(x, t) = \frac{f(x - ct) + f(x + ct)}{2} + \frac{G(x + ct) - G(x - ct)}{2c}\]with \(f\) the initial displacement and \(G' = g\) the initial velocity. On the whole line this is exact as written. Passing length solves the fixed-end string on \([0, L]\) instead, by replacing \(f\) (and \(g\)) with their odd \(2L\)-periodic extensions (method of images; Strauss, Ch. 4.1).
- Parameters:
f (
Callable) – Initial displacement, vectorized over NumPy arrays.x (array-like)
t (
float)c (
float)g_antiderivative (
Optional[Callable]) – An antiderivative \(G\) of the initial velocity (on \([0, L]\) when length is given); zero velocity if omitted.length (
Optional[float]) – String length for the fixed-end problem.
- Return type:
- Returns:
ndarray, same shape as x
Examples
>>> import numpy as np >>> bump = lambda s: np.exp(-100 * s**2) >>> u = dalembert_solution(bump, np.array([-1.0, 0.0, 1.0]), t=1.0) >>> np.round(u, 6).tolist() # the bump splits into two half-height copies [0.5, 0.0, 0.5]
- mathematicskit.pde.diffusion_number(alpha, dt, dx)[source]#
Diffusion number \(r = \alpha\,\Delta t / \Delta x^2\).
- Parameters:
- Return type:
- Returns:
float
Examples
>>> round(diffusion_number(1.0, 0.001, 0.1), 12) 0.1
- mathematicskit.pde.fem_poisson_1d(f, a=0.0, b=1.0, n_elements=10, ua=0.0, ub=0.0, nodes=None, quad_points=3)[source]#
Solve \(u'' = f\) on \([a, b]\), \(u(a) = u_a\), \(u(b) = u_b\), with P1 finite elements.
- Parameters:
f (
float|Callable) – Source term, vectorized over NumPy arrays if callable.a (
float) – Interval, ignored when nodes is given.b (
float) – Interval, ignored when nodes is given.n_elements (
int) – Number of equal elements, ignored when nodes is given.ua (
float) – Boundary values.ub (
float) – Boundary values.nodes (
Optional[ndarray]) – Strictly increasing mesh nodes (a nonuniform mesh), endpoints included.quad_points (
int) – Gauss-Legendre points per element for the load integrals (exact forfpolynomial of degree<= 2 * quad_points - 2).
- Return type:
- Returns:
EllipticSolution –
xare the mesh nodes anduthe nodal values.
Examples
>>> import numpy as np >>> nodes = np.array([0.0, 0.1, 0.35, 0.4, 0.8, 1.0]) # deliberately uneven >>> sol = fem_poisson_1d(lambda x: 6 * x, nodes=nodes) # exact u = x^3 - x >>> bool(np.allclose(sol.u, nodes**3 - nodes)) # exact at the nodes True
- mathematicskit.pde.fourier_derivative(u, length=6.283185307179586, order=1)[source]#
Spectral derivative of periodic grid values via the FFT.
Multiplies each Fourier coefficient by \((i k)^{\text{order}}\) (
numpy.fft.rfft()/irfft()). For an even number of points the unpaired Nyquist mode is zeroed for odd orders, so the derivative of real data stays real (Trefethen 2000, Ch. 3).- Parameters:
- Return type:
- Returns:
ndarray, shape (n,)
Examples
>>> import numpy as np >>> x = np.linspace(0, 2 * np.pi, 16, endpoint=False) >>> bool(np.allclose(fourier_derivative(np.sin(x)), np.cos(x))) True
- mathematicskit.pde.fourier_differentiation_matrix(n, length=6.283185307179586, order=1)[source]#
Dense Fourier spectral differentiation matrix on
nperiodic points (neven).With \(h = 2\pi/n\) and \(d = i - j\),
\[D^{(1)}_{ij} = \tfrac12 (-1)^{d} \cot(d h / 2)\ (d \ne 0), \qquad D^{(2)}_{ij} = -\frac{(-1)^{d}}{2 \sin^2(d h/2)}\ (d \ne 0), \quad D^{(2)}_{ii} = -\frac{\pi^2}{3h^2} - \frac16,\]rescaled by \((2\pi/\text{length})^{\text{order}}\) (Trefethen 2000, Ch. 3, eqs. (3.10)-(3.12)).
- Parameters:
- Return type:
- Returns:
ndarray, shape (n, n)
Examples
>>> import numpy as np >>> x = np.linspace(0, 2 * np.pi, 16, endpoint=False) >>> D2 = fourier_differentiation_matrix(16, order=2) >>> bool(np.allclose(D2 @ np.sin(3 * x), -9 * np.sin(3 * x))) True
- mathematicskit.pde.fourier_sine_coefficients(f, n_terms, length=1.0)[source]#
Fourier sine-series coefficients of f on \([0, L]\).
\[b_k = \frac{2}{L} \int_0^L f(x) \sin\!\left(\frac{k\pi x}{L}\right) dx, \qquad k = 1, \dots, n_{\text{terms}}\]computed with
scipy.integrate.quad(). See Fourier, Theorie analytique de la chaleur (1822), and Strauss, Ch. 5.1.- Parameters:
- Return type:
- Returns:
ndarray, shape (n_terms,) –
b[k - 1]is the coefficient of \(\sin(k\pi x/L)\).
Examples
>>> import numpy as np >>> b = fourier_sine_coefficients(lambda x: np.sin(2 * np.pi * x), 3) >>> np.round(b, 10).tolist() [0.0, 1.0, 0.0]
- mathematicskit.pde.heat_series_solution(coefficients, x, t, length=1.0, alpha=1.0)[source]#
Fourier’s series solution of the heat equation on a rod with zero end temperatures.
\[u(x, t) = \sum_{k \ge 1} b_k\, e^{-\alpha (k\pi/L)^2 t} \sin\!\left(\frac{k\pi x}{L}\right)\]- Parameters:
coefficients (array-like, shape (n_terms,)) – Sine coefficients of the initial condition, e.g. from
fourier_sine_coefficients().x (array-like)
t (
float)length (
float)alpha (
float)
- Return type:
- Returns:
ndarray, same shape as x
Examples
>>> import numpy as np >>> u = heat_series_solution([1.0], np.array([0.5]), t=0.1) >>> bool(np.isclose(u[0], np.exp(-np.pi**2 * 0.1))) True
- mathematicskit.pde.laplacian_1d(n, dx, bc='dirichlet')[source]#
Second-difference matrix approximating \(d^2/dx^2\), \(O(dx^2)\).
\[(L u)_i = \frac{u_{i-1} - 2 u_i + u_{i+1}}{dx^2}\]- Parameters:
n (
int) – Number of unknowns.dx (
float) – Grid spacing.bc (
str) –"dirichlet": the n unknowns are interior points and the (known) boundary values are dropped – add their contribution to the right-hand side separately."periodic":u_{-1} = u_{n-1}andu_n = u_0."neumann": the n unknowns include both endpoints, with zero slope imposed by reflecting ghost points (u_{-1} = u_1).
- Return type:
- Returns:
scipy.sparse.csr_matrix, shape (n, n)
Examples
>>> L = laplacian_1d(4, 1.0) >>> L.toarray().astype(int).tolist() [[-2, 1, 0, 0], [1, -2, 1, 0], [0, 1, -2, 1], [0, 0, 1, -2]]
- mathematicskit.pde.laplacian_2d(nx, ny, dx, dy)[source]#
Five-point Laplacian on an
nxbynygrid of interior unknowns (Dirichlet).Unknown
u[i, j]is stored at flat indexi * ny + j(row-major, matchingu.ravel()for an"ij"-indexed array), so the operator is the Kronecker sum \(L_x \otimes I_{n_y} + I_{n_x} \otimes L_y\).- Parameters:
- Return type:
- Returns:
scipy.sparse.csr_matrix, shape (nx * ny, nx * ny)
Examples
>>> import numpy as np >>> L = laplacian_2d(3, 3, 1.0, 1.0) >>> L.shape, float(L[4, 4]) ((9, 9), -4.0)
- mathematicskit.pde.max_amplification(scheme, number, n_xi=721)[source]#
Largest \(|G(\xi)|\) over \(\xi \in [0, \pi]\); the scheme is stable iff this is \(\le 1\).
- Parameters:
- Return type:
- Returns:
float
Examples
>>> max_amplification("lax_wendroff", 0.8) <= 1.0 True >>> max_amplification("ftcs_advection", 0.1) > 1.0 True
- mathematicskit.pde.multigrid_poisson_2d(f, n=65, boundary=0.0, tol=1e-08, max_cycles=50, pre_smooth=2, post_smooth=2, omega=0.8)[source]#
Solve \(u_{xx} + u_{yy} = f\) on the unit square by multigrid V-cycles.
Discretization and boundary handling match
solve_poisson_2d()(five-point Laplacian, Dirichlet data), so both converge to the same discrete solution.- Parameters:
f (
float|Callable|ndarray) – Source term; a callable is evaluated asf(X, Y)on"ij"meshgrids.n (
int) – Grid points per side, boundaries included; must be2**k + 1.tol (
float) – Stop when the residual 2-norm falls below tol times its initial value.max_cycles (
int)pre_smooth (
int) – Weighted-Jacobi sweeps before and after each coarse-grid correction.post_smooth (
int) – Weighted-Jacobi sweeps before and after each coarse-grid correction.omega (
float) – Jacobi damping (4/5 is optimal for smoothing the 2D five-point Laplacian).
- Return type:
- Returns:
EllipticSolution –
solver_resultis anIterativeSolveResultwhoseresidual_historyholds the relative residual after each V-cycle.
Examples
>>> import numpy as np >>> f = lambda X, Y: -2 * np.pi**2 * np.sin(np.pi * X) * np.sin(np.pi * Y) >>> sol = multigrid_poisson_2d(f, n=33) >>> sol.solver_result.converged, sol.solver_result.iterations < 15 (True, True)
- mathematicskit.pde.solve_laplace_2d(boundary, n=(33, 33), x_range=(0.0, 1.0), y_range=(0.0, 1.0), method='direct', **kwargs)[source]#
Solve Laplace’s equation \(u_{xx} + u_{yy} = 0\) with Dirichlet data boundary.
Equivalent to
solve_poisson_2d()withf = 0; see it for the parameters. By the discrete maximum principle, the solution’s extreme values lie on the boundary.- Return type:
- Returns:
EllipticSolution
- Parameters:
Examples
>>> import numpy as np >>> sol = solve_laplace_2d(lambda X, Y: X + 2 * Y, n=(9, 9)) # harmonic data is reproduced exactly >>> X, Y = np.meshgrid(sol.x, sol.y, indexing="ij") >>> bool(np.allclose(sol.u, X + 2 * Y)) True
- mathematicskit.pde.solve_poisson_1d(f, a=0.0, b=1.0, n=101, ua=0.0, ub=0.0)[source]#
Solve \(u'' = f\) on \([a, b]\) with \(u(a) = u_a\), \(u(b) = u_b\).
Second-order central differences, \(O(\Delta x^2)\); the tridiagonal system is solved with
scipy.sparse.linalg.spsolve().- Parameters:
- Return type:
- Returns:
EllipticSolution
Examples
>>> import numpy as np >>> sol = solve_poisson_1d(2.0, n=11) # u'' = 2, u(0) = u(1) = 0 => u = x^2 - x >>> bool(np.allclose(sol.u, sol.x**2 - sol.x)) True
- mathematicskit.pde.solve_poisson_2d(f, n=(33, 33), x_range=(0.0, 1.0), y_range=(0.0, 1.0), boundary=0.0, method='direct', tol=1e-08, max_iter=20000, omega=None)[source]#
Solve \(u_{xx} + u_{yy} = f\) on a rectangle with Dirichlet boundary data.
- Parameters:
f (
float|Callable|ndarray) – Source term; a callable is evaluated asf(X, Y)on"ij"meshgrids.n (tuple of int) – Grid points
(nx, ny), boundaries included.boundary (
float|Callable|ndarray) – Dirichlet data; a callable is evaluated asg(X, Y), and for an array only the boundary entries are used.method (
str) –"direct"usesscipy.sparse.linalg.spsolve(); the others useConjugateGradient,JacobiIteration,GaussSeidel, andSORon the dense matrix.tol (
float) – Relative residual tolerance for the iterative methods.max_iter (
int) – Iteration cap for the iterative methods.omega (float, optional) – SOR relaxation factor; defaults to
optimal_sor_omega()(Young, 1950).
- Return type:
- Returns:
EllipticSolution –
solver_resultholds the iterative solver’sIterativeSolveResult.
Examples
>>> import numpy as np >>> f = lambda X, Y: -2 * np.pi**2 * np.sin(np.pi * X) * np.sin(np.pi * Y) >>> sol = solve_poisson_2d(f, n=(33, 33)) >>> X, Y = np.meshgrid(sol.x, sol.y, indexing="ij") >>> bool(np.max(np.abs(sol.u - np.sin(np.pi * X) * np.sin(np.pi * Y))) < 1e-2) True
- mathematicskit.pde.total_variation(u)[source]#
Total variation \(\sum_j |u_{j+1} - u_j|\) of grid values.
Monotone schemes such as Godunov’s never increase it (they are TVD); oscillatory ones such as Lax-Wendroff do at shocks.
- Parameters:
u (array-like)
- Return type:
- Returns:
float
Examples
>>> total_variation([0.0, 1.0, 0.0, 2.0]) 4.0
- mathematicskit.pde.uniform_grid(a, b, n, periodic=False)[source]#
Return
nequally spaced points on[a, b]and their spacing.- Parameters:
- Return type:
- Returns:
x (ndarray, shape (n,))
dx (float)
Examples
>>> x, dx = uniform_grid(0.0, 1.0, 5) >>> x.tolist(), dx ([0.0, 0.25, 0.5, 0.75, 1.0], 0.25) >>> uniform_grid(0.0, 1.0, 4, periodic=True)[0].tolist() [0.0, 0.25, 0.5, 0.75]