Skip to content

Commit acea03f

Browse files
Merge branch 'master' into rosenbrock-tableau-refactor
2 parents 74637dc + a6c6e19 commit acea03f

File tree

39 files changed

+426
-33
lines changed

39 files changed

+426
-33
lines changed

docs/Project.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
DiffEqDevTools = "f3b72e0c-5b89-59e1-b016-84e28bfd966d"
33
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
44
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
5+
OrdinaryDiffEqAMF = "08082164-f6a1-4363-b3df-3aa6fcf571ad"
56

67
[sources]
78
DiffEqDevTools = {path = "../lib/DiffEqDevTools"}
9+
OrdinaryDiffEqAMF = {path = "../lib/OrdinaryDiffEqAMF"}
810

911
[compat]
1012
DiffEqDevTools = "2"
1113
Documenter = "0.27, 1"
1214
OrdinaryDiffEq = "6"
15+
OrdinaryDiffEqAMF = "1"

docs/make.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Documenter, OrdinaryDiffEq, DiffEqDevTools
2+
using OrdinaryDiffEqAMF
23

34
cp(joinpath(@__DIR__, "Manifest.toml"), joinpath(@__DIR__, "src", "assets", "Manifest.toml"), force = true)
45
cp(joinpath(@__DIR__, "Project.toml"), joinpath(@__DIR__, "src", "assets", "Project.toml"), force = true)
@@ -39,6 +40,7 @@ makedocs(
3940
OrdinaryDiffEq.OrdinaryDiffEqSymplecticRK,
4041
OrdinaryDiffEq.OrdinaryDiffEqTsit5,
4142
OrdinaryDiffEq.OrdinaryDiffEqVerner,
43+
OrdinaryDiffEqAMF,
4244
DiffEqDevTools,
4345
],
4446
linkcheck_ignore = [r"https://github.com/JuliaDiff/ForwardDiff.jl"],

docs/pages.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pages = [
1717
],
1818
"Semi-Implicit Solvers" => [
1919
"semiimplicit/Rosenbrock.md",
20+
"semiimplicit/AMF.md",
2021
"semiimplicit/StabilizedRK.md",
2122
"semiimplicit/ExponentialRK.md",
2223
],

docs/src/semiimplicit/AMF.md

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
```@meta
2+
CollapsedDocStrings = true
3+
```
4+
5+
# OrdinaryDiffEqAMF
6+
7+
**Approximate Matrix Factorization (AMF)** is a wrapper around Rosenbrock-W methods that replaces the dense `W = I - γJ` solve at each stage with a product of simpler factors:
8+
9+
```
10+
W ≈ -∏ᵢ (I - γJᵢ) / γ
11+
```
12+
13+
where the `Jᵢ` are individually-structured pieces of the Jacobian (e.g. upper and lower triangular parts, directional operators in dimensional splittings, mode blocks in spectral discretizations). Each factor can be solved independently with a cheap structured solver, so the overall per-step work drops from "one dense solve" to "a handful of structured solves".
14+
15+
## Key Properties
16+
17+
AMF methods provide:
18+
19+
- **Structured linear solves** — each factor is handled by its own `SciMLOperator`-aware solver, so you never materialize a dense `W`.
20+
- **Any Rosenbrock-W base method**`AMF` is an algorithm wrapper, not a fixed integrator; plug in `Rosenbrock23`, `ROS34PW1a`, `Rodas4`, etc.
21+
- **Works with the existing SciML split/operator infrastructure** — you describe the Jacobian split as `SciMLOperators` and pass them through `build_amf_function`.
22+
- **Preserves the Rosenbrock-W order** under the usual AMF consistency assumptions.
23+
24+
## When to Use AMF
25+
26+
AMF is recommended when:
27+
28+
- Your Jacobian has **exploitable structure** that a dense LU solve would throw away — e.g. ADI-style dimensional splits, upper+lower triangular decompositions, block-diagonal plus a low-rank coupling, FFT/diagonalizable pieces.
29+
- You're using a **Rosenbrock-W method** and the `W` solve is the dominant per-step cost.
30+
- You can write the structured factors as `SciMLOperators`.
31+
32+
If your Jacobian is genuinely dense and unstructured, plain Rosenbrock with a standard `LinearSolve` algorithm will be simpler and faster — AMF only helps when the product-of-factors approximation is significantly cheaper than the true `W` solve.
33+
34+
## How It Works
35+
36+
`AMF(AlgType)` wraps a Rosenbrock-W algorithm type. When you call `solve(prob, AMF(ROS34PW1a))`, the wrapper:
37+
38+
1. Instantiates the inner algorithm with `linsolve = SciMLOpFactorization()`, a `LinearSolve` backend that respects `SciMLOperator` products and solves them factor-by-factor rather than concretizing them.
39+
2. Delegates the rest of the integration to the inner Rosenbrock-W method unchanged.
40+
41+
To use it, the `ODEFunction` must carry a `jac_prototype` (the true Jacobian, as a `SciMLOperator`) *and* a `W_prototype` (the AMF approximation of `-(I - γJ)/γ`, built from the factors). `build_amf_function` assembles both for you.
42+
43+
## Usage: 2D reaction-diffusion with ADI-style splitting
44+
45+
The canonical AMF use case is a PDE whose discrete Laplacian separates along each spatial dimension — the split reduces the dense Rosenbrock-W `W` solve to a pair of Kronecker-structured solves that can be done much faster. This example is a 2D reaction-diffusion problem,
46+
47+
```
48+
uₜ = A uₓₓ + B u_yy + g(u, t), g(u, t) = u²(1 − u) + eᵗ,
49+
```
50+
51+
on the unit square with homogeneous Dirichlet boundary conditions. After second-order finite differencing on an `N × N` grid, the Jacobian of the linear (diffusion) part splits as
52+
53+
```
54+
J = A (Lx ⊗ I) + B (I ⊗ Lx) = Jx + Jy
55+
```
56+
57+
where `Lx` is the tridiagonal 1D Laplacian. AMF approximates the stage operator as
58+
59+
```
60+
W ≈ −(I − γ Jx)(I − γ Jy) / γ,
61+
```
62+
63+
so each stage does two independent block-diagonal tridiagonal solves instead of one dense solve — the standard ADI preconditioner, applied automatically by `AMF(...)` whenever you hand it the split.
64+
65+
Install the package alongside a Rosenbrock-W subpackage:
66+
67+
```julia
68+
using Pkg
69+
Pkg.add(["OrdinaryDiffEqAMF", "OrdinaryDiffEqRosenbrock"])
70+
```
71+
72+
Set up the problem:
73+
74+
```julia
75+
using OrdinaryDiffEqAMF
76+
using OrdinaryDiffEqRosenbrock
77+
using SciMLOperators
78+
using LinearAlgebra
79+
80+
function setup_fd2d_problem(; A = 0.1, B = 0.1, N = 40, final_t = 1.0)
81+
h = 1 / (N + 1)
82+
83+
# 1D Laplacian with Dirichlet zero BCs, as a SciMLOperator.
84+
D = (1 / h^2) * Tridiagonal(ones(N - 1), -2 * ones(N), ones(N - 1))
85+
D_op = MatrixOperator(D)
86+
87+
# 2D Jacobian pieces: diffusion in x and in y, via Kronecker products.
88+
Jx_op = A * Base.kron(D_op, IdentityOperator(N))
89+
Jy_op = B * Base.kron(IdentityOperator(N), D_op)
90+
J_op = cache_operator(Jx_op + Jy_op, zeros(N^2))
91+
92+
# Nonlinear reaction term.
93+
g!(du, u, p, t) = (@. du += u^2 * (1 - u) + exp(t); return nothing)
94+
95+
# Full RHS: linear diffusion + nonlinear reaction.
96+
function f!(du, u, p, t)
97+
mul!(du, J_op, u)
98+
g!(du, u, p, t)
99+
return nothing
100+
end
101+
102+
# Initial condition: a bump that vanishes on the boundary.
103+
u0 = [16 * (h * i) * (h * j) * (1 - h * i) * (1 - h * j) for i in 1:N for j in 1:N]
104+
105+
# Hand the split to build_amf_function so Rosenbrock-W sees the
106+
# structured W_prototype instead of a dense one.
107+
amf_func = build_amf_function(f!; jac = J_op, split = (Jx_op, Jy_op))
108+
return ODEProblem(amf_func, u0, (0.0, final_t))
109+
end
110+
111+
prob = setup_fd2d_problem()
112+
sol = solve(prob, AMF(ROS34PW1a); abstol = 1e-8, reltol = 1e-8)
113+
```
114+
115+
For this 1600-unknown problem the AMF stage solve is substantially faster than the dense equivalent, and the speedup grows with `N` because the dense `W` solve is `O(N⁶)` while the two Kronecker-structured AMF solves are `O(N³)` total. A regression test inside the package (`lib/OrdinaryDiffEqAMF/test/test_fd2d.jl`) measures the speedup on the same problem against a baseline `solve(..., ROS34PW1a())` without AMF.
116+
117+
### Supplying AMF factors directly
118+
119+
Sometimes you want to control the factors yourself — for instance to pre-factorize each 1D operator with something cheaper than a generic structured solver, or to bake in an FFT plan. Pass the custom factors as `amf_factors`:
120+
121+
```julia
122+
using SciMLOperators: ScalarOperator
123+
124+
gamma_op = ScalarOperator(
125+
1.0;
126+
update_func = (old, u, p, t; gamma = 1.0) -> gamma,
127+
accepted_kwargs = Val((:gamma,)),
128+
)
129+
130+
# Each factor is (I − γ Jᵢ), pre-assembled as a Kronecker-structured operator.
131+
custom_factors = (
132+
Base.kron(IdentityOperator(N) - gamma_op * A * D_op, IdentityOperator(N)),
133+
Base.kron(IdentityOperator(N), IdentityOperator(N) - gamma_op * B * D_op),
134+
)
135+
136+
amf_func = build_amf_function(
137+
f!;
138+
jac = J_op,
139+
split = (Jx_op, Jy_op),
140+
amf_factors = custom_factors,
141+
)
142+
143+
sol = solve(ODEProblem(amf_func, u0, (0.0, 1.0)), AMF(ROS34PW1a); reltol = 1e-8)
144+
```
145+
146+
`split` and `amf_factors` must contain the same number of operators: the split is used to propagate Jacobian updates, and the factors determine the actual `W_prototype` that the linear solver sees.
147+
148+
### Forwarding keyword arguments
149+
150+
Any extra kwargs passed to `AMF(alg; kwargs...)` are forwarded to the inner algorithm constructor:
151+
152+
```julia
153+
sol = solve(prob, AMF(ROS34PW1a; autodiff = AutoFiniteDiff()))
154+
```
155+
156+
## Full list of exports
157+
158+
```@docs
159+
OrdinaryDiffEqAMF.AMF
160+
OrdinaryDiffEqAMF.AMFOperator
161+
OrdinaryDiffEqAMF.build_amf_function
162+
```

lib/DelayDiffEq/Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "DelayDiffEq"
22
uuid = "bcd4f6db-9728-5f36-b5f7-82caef46ccdb"
33
authors = ["Chris Rackauckas <accounts@chrisrackauckas.com>"]
4-
version = "5.72.0"
4+
version = "5.73.0"
55

66
[deps]
77
ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"

lib/DiffEqBase/Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "DiffEqBase"
22
uuid = "2b5f629d-d688-5b77-993f-72d75c75574e"
33
authors = ["Chris Rackauckas <accounts@chrisrackauckas.com>"]
4-
version = "6.214.2"
4+
version = "6.215.0"
55

66
[deps]
77
ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"
@@ -72,7 +72,7 @@ ADTypes = "1"
7272
Aqua = "0.8"
7373
ArrayInterface = "7.8"
7474
BracketingNonlinearSolve = "1.10"
75-
CUDA = "5"
75+
CUDA = "5, 6"
7676
ChainRulesCore = "1"
7777
ConcreteStructs = "0.2.3"
7878
DiffEqCallbacks = "4"

lib/DiffEqDevTools/LICENSE.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
The OrdinaryDiffEq.jl package is licensed under the MIT "Expat" License:
2+
3+
> Copyright (c) 2016-2020: ChrisRackauckas, Yingbo Ma, Julia Computing Inc, and
4+
> other contributors:
5+
>
6+
> https://github.com/SciML/OrdinaryDiffEq.jl/graphs/contributors
7+
>
8+
> Permission is hereby granted, free of charge, to any person obtaining a copy
9+
> of this software and associated documentation files (the "Software"), to deal
10+
> in the Software without restriction, including without limitation the rights
11+
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
> copies of the Software, and to permit persons to whom the Software is
13+
> furnished to do so, subject to the following conditions:
14+
>
15+
> The above copyright notice and this permission notice shall be included in all
16+
> copies or substantial portions of the Software.
17+
>
18+
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24+
> SOFTWARE.

lib/DiffEqDevTools/Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "DiffEqDevTools"
22
uuid = "f3b72e0c-5b89-59e1-b016-84e28bfd966d"
33
authors = ["Chris Rackauckas <accounts@chrisrackauckas.com>"]
4-
version = "2.49.0"
4+
version = "2.52.0"
55

66
[deps]
77
DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e"

lib/OrdinaryDiffEqAMF/LICENSE.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
The OrdinaryDiffEq.jl package is licensed under the MIT "Expat" License:
2+
3+
> Copyright (c) 2016-2020: ChrisRackauckas, Yingbo Ma, Julia Computing Inc, and
4+
> other contributors:
5+
>
6+
> https://github.com/SciML/OrdinaryDiffEq.jl/graphs/contributors
7+
>
8+
> Permission is hereby granted, free of charge, to any person obtaining a copy
9+
> of this software and associated documentation files (the "Software"), to deal
10+
> in the Software without restriction, including without limitation the rights
11+
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
> copies of the Software, and to permit persons to whom the Software is
13+
> furnished to do so, subject to the following conditions:
14+
>
15+
> The above copyright notice and this permission notice shall be included in all
16+
> copies or substantial portions of the Software.
17+
>
18+
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24+
> SOFTWARE.

lib/OrdinaryDiffEqAMF/Project.toml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "OrdinaryDiffEqAMF"
22
uuid = "08082164-f6a1-4363-b3df-3aa6fcf571ad"
33
authors = ["Utkarsh <rajpututkarsh530@gmail.com>"]
4-
version = "0.1.0"
4+
version = "1.0.0"
55

66
[deps]
77
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
@@ -12,7 +12,13 @@ SciMLBase = "0bca4576-84f4-4d90-8ffe-ffa030f20462"
1212
SciMLOperators = "c0aeaf25-5076-4817-a8d5-81caf7dfa961"
1313

1414
[compat]
15-
julia = "1"
15+
LinearAlgebra = "1.10"
16+
LinearSolve = "3.46"
17+
OrdinaryDiffEqCore = "3.30"
18+
Reexport = "1.2"
19+
SciMLBase = "2.146"
20+
SciMLOperators = "1.4"
21+
julia = "1.10"
1622

1723
[sources.OrdinaryDiffEqCore]
1824
path = "../OrdinaryDiffEqCore"

0 commit comments

Comments
 (0)