tensormesh.ode¶
Runge-Kutta methods¶
Base classes¶
- class ExplicitRungeKutta(a, b)[source]¶
Bases:
objectBase class for explicit Runge-Kutta schemes.
Integrates the ODE
\[\frac{\partial u}{\partial t} = f(t, u)\]with a user-supplied right-hand side
forward(). The scheme is encoded by its Butcher tableau(a, b)and a singlestep()advances one time step.- Parameters:
a (Tensor) –
2D tensor of shape
[s, s]; should be lower triangular.\[\begin{split}a = \begin{bmatrix} 0 & \cdots & 0 & 0 \\ a_{21} & \cdots & 0 & 0 \\ \vdots & \ddots & \vdots & \vdots \\ a_{s1} & \cdots & a_{s,s-1} & 0 \end{bmatrix}\end{split}\]b (Tensor) – 1D tensor of shape
[s]with \(\sum_i b_i = 1\).
Examples
Solve \(\frac{\mathrm{d}u}{\mathrm{d}t} = u\) with explicit Euler:
import torch from tensormesh.ode import ExplicitRungeKutta class MyExplicitRungeKutta(ExplicitRungeKutta): def forward(self, t, u): return u a = torch.zeros(1, 1) b = torch.ones(1) u0 = torch.rand(4) dt = 0.1 ut = MyExplicitRungeKutta(a, b).step(0, u0, dt)
- forward(t, u)[source]¶
Right-hand side of the ODE.
\[\frac{\partial u}{\partial t} = f(t, u)\]Default returns
u(i.e. \(f(t, u) = u\)); subclasses are expected to override.
- class ImplicitLinearRungeKutta(a, b)[source]¶
Bases:
objectBase class for implicit linear Runge-Kutta schemes.
Integrates the linear (in \(u\)) system
\[M(t) \frac{\partial u}{\partial t} = A(t) u + B(t)\]where \(M(t),\,A(t) \in \mathbb{R}^{n \times n}\) and \(B(t) \in \mathbb{R}^{n}\). Each
step()assembles and solves the block system\[\begin{split}\begin{bmatrix} M_0 - A_0\tau a_{0,0} & -A_0\tau a_{0,1} & \cdots & -A_0\tau a_{0,s-1} \\ -A_1\tau a_{1,0} & M_1 - A_1\tau a_{1,1} & \cdots & -A_1\tau a_{1,s-1} \\ \vdots & \vdots & \ddots & \vdots \\ -A_{s-1}\tau a_{s-1,0} & -A_{s-1}\tau a_{s-1,1} & \cdots & M_{s-1} - A_{s-1}\tau a_{s-1,s-1} \end{bmatrix} \begin{bmatrix} k_0 \\ k_1 \\ \vdots \\ k_{s-1} \end{bmatrix} = \begin{bmatrix} B_0 + A_0 u \\ B_1 + A_1 u \\ \vdots \\ B_{s-1} + A_{s-1} u \end{bmatrix}\end{split}\]for the stage values \(k_i\), then advances \(u_{n+1} = u_n + \tau \sum_i b_i\,k_i\).
- Parameters:
Examples
Solve \(\frac{\mathrm{d}u}{\mathrm{d}t} = u\) with backward Euler:
import torch from tensormesh.ode import ImplicitLinearRungeKutta class MyImplicitLinearRungeKutta(ImplicitLinearRungeKutta): def forward_M(self, t): return torch.eye(4) def forward_A(self, t): return torch.eye(4) def forward_B(self, t): return torch.zeros(4) a = torch.ones(1, 1) b = torch.ones(1) u0 = torch.rand(4).double() dt = 0.1 ut = MyImplicitLinearRungeKutta(a, b).step(0, u0, dt)
- forward_M(t)[source]¶
Mass-like operator \(M(t)\).
\[M(t) \frac{\partial u}{\partial t} = A(t) u + B(t)\]- Parameters:
t (float) – Current time.
- Returns:
2D operator of shape
[D, D]. If returned asint/float, \(M\) is taken to be that scalar multiple of the identity. Default returns1.0(i.e. \(M = I\)).- Return type:
tensormesh.sparse.SparseMatrixor torch.Tensor or float
- forward_A(t)[source]¶
Linear operator \(A(t)\) on the right-hand side.
\[M(t) \frac{\partial u}{\partial t} = A(t) u + B(t)\]- Parameters:
t (float) – Current time.
- Returns:
2D operator of shape
[D, D]. If returned asint/float, \(A\) is taken to be that scalar multiple of the identity. Default returns1.0(i.e. \(A = I\)).- Return type:
tensormesh.sparse.SparseMatrixor torch.Tensor or float
- forward_B(t)[source]¶
Source / forcing term \(B(t)\).
\[M(t) \frac{\partial u}{\partial t} = A(t) u + B(t)\]
- pre_solve_lhs(K)[source]¶
Preprocess the assembled block matrix before solving.
Hook for boundary-condition condensation (or similar). Called once per
[i][j]block.- Parameters:
K (torch.Tensor or
tensormesh.sparse.SparseMatrix) – One block of the left-hand-side matrix.- Returns:
The (possibly condensed) block. Default returns
Kunchanged.- Return type:
torch.Tensor or
tensormesh.sparse.SparseMatrix
- pre_solve_rhs(f)[source]¶
Preprocess each stage right-hand side before solving.
Hook for boundary-condition condensation (or similar). Called once per stage.
- recover_stage(k_i)[source]¶
Lift one stage slope back from the solve space to full DOF.
Default: identity. Override when
pre_solve_lhs()/pre_solve_rhs()reduce the system size — e.g. static condensation viaCondenser. In that case each stage value \(k_i\) is solved in the inner-DOF subspace and must be prolonged back to the full DOF layout before being combined with \(u_0\).For Dirichlet boundary conditions the boundary entries of \(k_i\) must be zero, regardless of the prescribed value of \(u\) there: a Dirichlet DOF is fixed, so its time-derivative is zero. Use
prolong()(or an equivalent scatter-with-zero-boundary) here, notrecover()(which writes the prescribed Dirichlet value into the boundary slots).
- post_solve(u)[source]¶
Postprocess the combined solution after the linear solve.
Runs after the stage slopes have already been lifted by
recover_stage()and combined with \(u_0\), souis always full-DOF here. Use this hook for things that act on the final state — clamping, normalization, projecting onto a manifold, etc. For static-condensation BCs, overriderecover_stage()instead.
- step(t0, u0, dt)[source]¶
Advance one implicit-linear Runge-Kutta step from
t0tot0 + dt.Builds the block system described in the class docstring, applies
pre_solve_lhs()/pre_solve_rhs()to each block, solves it (viatensormesh.sparse.SparseMatrix.solve()when the operators are sparse, otherwisetorch.linalg.solve()), appliespost_solve(), and combines the stage values:\[u_{n+1} = u_0 + \tau \sum_{i=1}^{s} b_i\,k_i\]
Built-in schemes¶
- class ExplicitEuler[source]¶
Bases:
ExplicitRungeKutta\[\begin{split}\begin{array}{c|c} \textbf{c} & \mathfrak{A} \\ \hline & \textbf{b}^\top \end{array} = \begin{array}{c|c} 0 & 0 \\ \hline & 1 \end{array}\end{split}\]\[\Psi^{t,t+\tau}\textbf{u} \approx \textbf{u} + \tau \textbf{f}(t,\textbf{u})\]Examples
\[\frac{\text{d}u}{\text{d}t} = u\]import torch from tensormesh.ode import ExplicitEuler class MyExplicitEuler(ExplicitEuler): def forward(self, t, u): return u u0 = torch.rand(4) dt = 0.1 ut_gt = u0 + dt * u0 ut_my = MyExplicitEuler().step(0, u0, dt) assert torch.allclose(ut_gt, ut_my)
- class ImplicitLinearEuler[source]¶
Bases:
ImplicitLinearRungeKutta\[\begin{split}\begin{array}{c|c} \textbf{c} & \mathfrak{A} \\ \hline & \textbf{b}^\top \end{array} = \begin{array}{c|c} 1 & 1 \\ \hline & 1 \end{array}\end{split}\]\[\Psi^{t,t+\tau}\textbf{u} \approx \textbf{w}\quad \textbf{w}=\textbf{u}+\tau\textbf{f}(t+\tau,\textbf{w})\]Examples
\[\frac{\text{d}u}{\text{d}t} = u\]import torch from tensormesh.ode import ImplicitLinearEuler u0 = torch.rand(4).double() dt = 0.1 ut_gt = (1/(1-dt)) * u0 ut_my = ImplicitLinearEuler().step(0, u0, dt) assert torch.allclose(ut_gt, ut_my), f"expected {ut_gt}, got {ut_my}"
- class MidPointLinearEuler[source]¶
Bases:
ImplicitLinearRungeKutta\[\begin{split}\begin{array}{c|c} \textbf{c} & \mathfrak{A} \\ \hline & \textbf{b}^\top \end{array} = \begin{array}{c|c} \frac{1}{2} & \frac{1}{2} \\ \hline & 1 \end{array}\end{split}\]\[\Psi^{t,t+\tau}\textbf{u} \approx \textbf{w}\quad \textbf{w} = \textbf{u} +\tau \textbf{f}\left(t+\frac{\tau}{2},\frac{\textbf{w}+\textbf{u}}{2}\right)\]Examples
\[\frac{\text{d} u}{\text{d} t} = u\]import torch from tensormesh.ode import MidPointLinearEuler u0 = torch.rand(4) dt = 0.1 ut_gt = ((dt+2)/(2-dt)) * u0 ut_my = MidPointLinearEuler().step(0, u0, dt) assert torch.allclose(ut_gt, ut_my), f"expected {ut_gt}, got {ut_my}"