Skip to content

Commit 2e21c47

Browse files
Merge pull request #269 from divital-coder/dg/vectors
SciMLOperators operator evaluation interface allowing different defining and action vectors
2 parents 351225e + 91e4312 commit 2e21c47

26 files changed

+2332
-1239
lines changed

.github/workflows/Tests.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ on:
44
pull_request:
55
branches:
66
- master
7+
- 'release-'
78
paths-ignore:
89
- 'docs/**'
910
push:
@@ -13,19 +14,26 @@ on:
1314
- 'docs/**'
1415

1516
concurrency:
17+
# Skip intermediate builds: always, but for the master branch.
18+
# Cancel intermediate builds: always, but for the master branch.
1619
group: ${{ github.workflow }}-${{ github.ref }}
17-
cancel-in-progress: ${{ github.ref_name != github.event.repository.default_branch || github.ref != 'refs/tags/v*' }}
20+
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
1821

1922
jobs:
2023
tests:
2124
name: "Tests"
2225
strategy:
26+
fail-fast: false
2327
matrix:
2428
version:
2529
- "1"
2630
- "lts"
2731
- "pre"
32+
group:
33+
- Core
34+
- Downstream
2835
uses: "SciML/.github/.github/workflows/tests.yml@v1"
2936
with:
3037
julia-version: "${{ matrix.version }}"
38+
group: "${{ matrix.group }}"
3139
secrets: "inherit"

docs/Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ SciMLOperators = "c0aeaf25-5076-4817-a8d5-81caf7dfa961"
66
[compat]
77
Documenter = "0.27, 1"
88
FFTW = "1.7"
9-
SciMLOperators = "0.2, 0.3"
9+
SciMLOperators = "0.4"

docs/src/index.md

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Let `M`, `D`, `F` be matrix-based, diagonal-matrix-based, and function-based
3232

3333
```julia
3434
N = 4
35-
f = (u, p, t) -> u .* u
35+
f = (v, u, p, t) -> u .* v
3636

3737
M = MatrixOperator(rand(N, N))
3838
D = DiagonalOperator(rand(N))
@@ -56,10 +56,11 @@ p = nothing # parameter struct
5656
t = 0.0 # time
5757

5858
u = rand(N)
59-
v = L1(u, p, t) # == L1 * u
59+
v = rand(N)
60+
w = L1(v, u, p, t) # == L1 * v
6061

61-
u_kron = rand(N^3)
62-
v_kron = L3(u_kron, p, t) # == L3 * u_kron
62+
v_kron = rand(N^3)
63+
w_kron = L3(v_kron, u, p, t) # == L3 * v_kron
6364
```
6465

6566
For mutating operator evaluations, call `cache_operator` to generate an
@@ -73,21 +74,17 @@ L2 = cache_operator(L2, u)
7374
L4 = cache_operator(L4, u)
7475

7576
# allocation-free evaluation
76-
L2(v, u, p, t) # == mul!(v, L2, u)
77-
L4(v, u, p, t, α, β) # == mul!(v, L4, u, α, β)
77+
L2(w, v, u, p, t) # == mul!(w, L2, v)
78+
L4(w, v, u, p, t, α, β) # == mul!(w, L4, v, α, β)
7879
```
7980

80-
The calling signature `L(u, p, t)`, for out-of-place evaluations, is
81-
equivalent to `L * u`, and the in-place evaluation `L(v, u, p, t, args...)`
82-
is equivalent to `LinearAlgebra.mul!(v, L, u, args...)`, where the arguments
83-
`p, t` are passed to `L` to update its state. More details are provided
84-
in the operator update section below. While overloads to `Base.*`
85-
and `LinearAlgebra.mul!` are available, where a `SciMLOperator` behaves
86-
like an `AbstractMatrix`, we recommend sticking with the
87-
`L(u, p, t)`, `L(v, u, p, t)`, `L(v, u, p, t, α, β)` calling signatures
88-
as the latter internally update the operator state.
89-
90-
The `(u, p, t)` calling signature is standardized over the `SciML`
81+
The calling signature `L(v, u, p, t)`, for out-of-place evaluations, is
82+
equivalent to `L * v`, and the in-place evaluation `L(w, v, u, p, t, args...)`
83+
is equivalent to `LinearAlgebra.mul!(w, L, v, args...)`, where the arguments
84+
`u, p, t` are passed to `L` to update its state. More details are provided
85+
in the operator update section below.
86+
87+
The `(v, u, p, t)` calling signature is standardized over the `SciML`
9188
ecosystem and is flexible enough to support use cases such as time-evolution
9289
in ODEs, as well as sensitivity computation with respect to the parameter
9390
object `p`.

docs/src/interface.md

Lines changed: 3 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -1,152 +1,7 @@
11
# The `AbstractSciMLOperator` Interface
22

3-
## Formal Properties of SciMLOperators
4-
5-
These are the formal properties that an `AbstractSciMLOperator` should obey
6-
for it to work in the solvers.
7-
8-
1. An `AbstractSciMLOperator` represents a linear or nonlinear operator, with input/output
9-
being `AbstractArray`s. Specifically, a SciMLOperator, `L`, of size `(M, N)` accepts an
10-
input argument `u` with leading length `N`, i.e. `size(u, 1) == N`, and returns an
11-
`AbstractArray` of the same dimension with leading length `M`, i.e. `size(L * u, 1) == M`.
12-
2. SciMLOperators can be applied to an `AbstractArray` via overloaded `Base.*`, or
13-
the in-place `LinearAlgebra.mul!`. Additionally, operators are allowed to be time,
14-
or parameter dependent. The state of a SciMLOperator can be updated by calling
15-
the mutating function `update_coefficients!(L, u, p, t)` where `p` represents
16-
parameters, and `t`, time. Calling a SciMLOperator as `L(du, u, p, t)` or out-of-place
17-
`L(u, p, t)` will automatically update the state of `L` before applying it to `u`.
18-
`L(u, p, t)` is the same operation as `L(u, p, t) * u`.
19-
3. To support the update functionality, we have lazily implemented a comprehensive operator
20-
algebra. That means a user can add, subtract, scale, compose and invert SciMLOperators,
21-
and the state of the resultant operator would be updated as expected upon calling
22-
`L(du, u, p, t)` or `L(u, p, t)` so long as an update function is provided for the
23-
component operators.
24-
25-
## Overloaded Traits
26-
27-
Thanks to overloads defined for evaluation methods and traits in
28-
`Base`, `LinearAlgebra`, the behavior of a `SciMLOperator` is
29-
indistinguishable from an `AbstractMatrix`. These operators can be
30-
passed to linear solver packages, and even to ordinary differential
31-
equation solvers. The list of overloads to the `AbstractMatrix`
32-
interface includes, but is not limited to, the following:
33-
34-
- `Base: size, zero, one, +, -, *, /, \, ∘, inv, adjoint, transpose, convert`
35-
- `LinearAlgebra: mul!, ldiv!, lmul!, rmul!, factorize, issymmetric, ishermitian, isposdef`
36-
- `SparseArrays: sparse, issparse`
37-
38-
## Multidimensional arrays and batching
39-
40-
SciMLOperator can also be applied to `AbstractMatrix` subtypes where
41-
operator-evaluation is done column-wise.
42-
43-
```julia
44-
K = 10
45-
u_mat = rand(N, K)
46-
47-
v_mat = F(u_mat, p, t) # == mul!(v_mat, F, u_mat)
48-
size(v_mat) == (N, K) # true
49-
```
50-
51-
`L#` can also be applied to `AbstractArray`s that are not
52-
`AbstractVecOrMat`s so long as their size in the first dimension is appropriate
53-
for matrix-multiplication. Internally, `SciMLOperator`s reshapes an
54-
`N`-dimensional array to an `AbstractMatrix`, and applies the operator via
55-
matrix-multiplication.
56-
57-
## Operator update
58-
59-
This package can also be used to write time-dependent, and
60-
parameter-dependent operators, whose state can be updated per
61-
a user-defined function.
62-
The updates can be done in-place, i.e. by mutating the object,
63-
or out-of-place, i.e. in a non-mutating, `Zygote`-compatible way.
64-
65-
For example,
66-
67-
```julia
68-
u = rand(N)
69-
p = rand(N)
70-
t = rand()
71-
72-
# out-of-place update
73-
mat_update_func = (A, u, p, t) -> t * (p * p')
74-
sca_update_func = (a, u, p, t) -> t * sum(p)
75-
76-
M = MatrixOperator(zero(N, N); update_func = mat_update_func)
77-
α = ScalarOperator(zero(Float64); update_func = sca_update_func)
78-
79-
L = α * M
80-
L = cache_operator(L, u)
81-
82-
# L is initialized with zero state
83-
L * u == zeros(N) # true
84-
85-
# update operator state with `(u, p, t)`
86-
L = update_coefficients(L, u, p, t)
87-
# and multiply
88-
L * u != zeros(N) # true
89-
90-
# updates state and evaluates L at (u, p, t)
91-
L(u, p, t) != zeros(N) # true
92-
```
93-
94-
The out-of-place evaluation function `L(u, p, t)` calls
95-
`update_coefficients` under the hood, which recursively calls
96-
the `update_func` for each component `SciMLOperator`.
97-
Therefore, the out-of-place evaluation function is equivalent to
98-
calling `update_coefficients` followed by `Base.*`. Notice that
99-
the out-of-place evaluation does not return the updated operator.
100-
101-
On the other hand, the in-place evaluation function, `L(v, u, p, t)`,
102-
mutates `L`, and is equivalent to calling `update_coefficients!`
103-
followed by `mul!`. The in-place update behavior works the same way,
104-
with a few `<!>`s appended here and there. For example,
105-
106-
```julia
107-
v = rand(N)
108-
u = rand(N)
109-
p = rand(N)
110-
t = rand()
111-
112-
# in-place update
113-
_A = rand(N, N)
114-
_d = rand(N)
115-
mat_update_func! = (A, u, p, t) -> (copy!(A, _A); lmul!(t, A); nothing)
116-
diag_update_func! = (diag, u, p, t) -> copy!(diag, N)
117-
118-
M = MatrixOperator(zero(N, N); update_func! = mat_update_func!)
119-
D = DiagonalOperator(zero(N); update_func! = diag_update_func!)
120-
121-
L = D * M
122-
L = cache_operator(L, u)
123-
124-
# L is initialized with zero state
125-
L * u == zeros(N) # true
126-
127-
# update L in-place
128-
update_coefficients!(L, u, p, t)
129-
# and multiply
130-
mul!(v, u, p, t) != zero(N) # true
131-
132-
# updates L in-place, and evaluates at (u, p, t)
133-
L(v, u, p, t) != zero(N) # true
134-
```
135-
136-
The update behavior makes this package flexible enough to be used
137-
in `OrdinaryDiffEq`. As the parameter object `p` is often reserved
138-
for sensitivity computation via automatic-differentiation, a user may
139-
prefer to pass in state information via other arguments. For that
140-
reason, we allow update functions with arbitrary keyword arguments.
141-
142-
```julia
143-
mat_update_func = (A, u, p, t; scale = 0.0) -> scale * (p * p')
144-
145-
M = MatrixOperator(zero(N, N); update_func = mat_update_func,
146-
accepted_kwargs = (:state,))
147-
148-
M(u, p, t) == zeros(N) # true
149-
M(u, p, t; scale = 1.0) != zero(N)
3+
```@docs
4+
SciMLOperators.AbstractSciMLOperator
1505
```
1516

1527
## Interface API Reference
@@ -219,6 +74,6 @@ update_coefficients!(γ, nothing, nothing, nothing; my_special_scaling = 7.0)
21974
@show γ * [2.0]
22075
22176
# Use operator application form
222-
@show γ([2.0], nothing, nothing; my_special_scaling = 5.0)
77+
@show γ([2.0], nothing, nothing, nothing; my_special_scaling = 5.0)
22378
nothing # hide
22479
```

docs/src/premade_operators.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
## Direct Operator Definitions
44

55
```@docs
6-
ScalarOperator.IdentityOperator
6+
SciMLOperators.IdentityOperator
77
SciMLOperators.NullOperator
88
ScalarOperator
99
MatrixOperator

docs/src/sciml.md

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,32 @@ Munthe-Kaas methods require defining operators of the form ``u' = A(u) u``.
1919
Thus, the operators need some form of time and state dependence, which the
2020
solvers can update and query when they are non-constant
2121
(`update_coefficients!`). Additionally, the operators need the ability to
22-
act like “normal” functions for equation solvers. For example, if `A(u,p,t)`
23-
has the same operation as `update_coefficients(A, u, p, t); A * u`, then `A`
22+
act like “normal” functions for equation solvers. For example, if `A(v,u,p,t)`
23+
has the same operation as `update_coefficients(A, u, p, t); A * v`, then `A`
2424
can be used in any place where a differential equation definition
25-
`f(u, p, t)` is used without requiring the user or solver to do any extra
26-
work. Thus while previous good efforts for matrix-free operators have existed
25+
`(u,p,t) -> A(u, u, p, t)` is used without requiring the user or solver to do any extra
26+
work.
27+
28+
Another example is state-dependent mass matrices. `M(u,p,t)*u' = f(u,p,t)`.
29+
When solving such an equation, the solver must understand how to "update M"
30+
during operations, and thus the ability to update the state of `M` is a required
31+
function in the interface. This is also required for the definition of Jacobians
32+
`J(u,p,t)` in order to be properly used with Krylov methods inside of ODE solves
33+
without reconstructing the matrix-free operator at each step.
34+
35+
Thus while previous good efforts for matrix-free operators have existed
2736
in the Julia ecosystem, such as
2837
[LinearMaps.jl](https://github.com/JuliaLinearAlgebra/LinearMaps.jl), those
2938
operator interfaces lack these aspects to actually be fully seamless
3039
with downstream equation solvers. This necessitates the definition and use of
3140
an extended operator interface with all of these properties, hence the
3241
`AbstractSciMLOperator` interface.
3342

34-
Some packages providing similar functionality are
43+
!!! warn
3544

36-
- [LinearMaps.jl](https://github.com/JuliaLinearAlgebra/LinearMaps.jl)
37-
- [`DiffEqOperators.jl`](https://github.com/SciML/DiffEqOperators.jl/tree/master) (deprecated)
45+
This means that LinearMaps.jl is fundamentally lacking and is incompatible
46+
with many of the tools in the SciML ecosystem, except for the specific cases
47+
where the matrix-free operator is a constant!
3848

3949
## Interoperability and extended Julia ecosystem
4050

docs/src/tutorials/fftw.md

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,18 @@ L = 2π
1616
1717
dx = L / n
1818
x = range(start = -L / 2, stop = L / 2 - dx, length = n) |> Array
19-
u = @. sin(5x)cos(7x);
20-
du = @. 5cos(5x)cos(7x) - 7sin(5x)sin(7x);
19+
v = @. sin(5x)cos(7x);
20+
w = @. 5cos(5x)cos(7x) - 7sin(5x)sin(7x);
2121
2222
k = rfftfreq(n, 2π * n / L) |> Array
2323
m = length(k)
2424
P = plan_rfft(x)
2525
26-
fwd(u, p, t) = P * u
27-
bwd(u, p, t) = P \ u
26+
fwd(v, u, p, t) = P * v
27+
bwd(v, u, p, t) = P \ v
2828
29-
fwd(du, u, p, t) = mul!(du, P, u)
30-
bwd(du, u, p, t) = ldiv!(du, P, u)
29+
fwd(w, v, u, p, t) = mul!(w, P, v)
30+
bwd(w, v, u, p, t) = ldiv!(w, P, v)
3131
3232
F = FunctionOperator(fwd, x, im * k;
3333
T = ComplexF64, op_adjoint = bwd,
@@ -38,10 +38,10 @@ F = FunctionOperator(fwd, x, im * k;
3838
ik = im * DiagonalOperator(k)
3939
Dx = F \ ik * F
4040
41-
Dx = cache_operator(Dx, x)
41+
Dx = cache_operator(Dx, v)
4242
43-
@show ≈(Dx * u, du; atol = 1e-8)
44-
@show ≈(mul!(copy(u), Dx, u), du; atol = 1e-8)
43+
@show ≈(Dx * v, w; atol = 1e-8)
44+
@show ≈(mul!(copy(w), Dx, v), w; atol = 1e-8)
4545
```
4646

4747
## Explanation
@@ -61,8 +61,8 @@ n = 256
6161
dx = L / n
6262
x = range(start = -L / 2, stop = L / 2 - dx, length = n) |> Array
6363
64-
u = @. sin(5x)cos(7x);
65-
du = @. 5cos(5x)cos(7x) - 7sin(5x)sin(7x);
64+
v = @. sin(5x)cos(7x);
65+
w = @. 5cos(5x)cos(7x) - 7sin(5x)sin(7x);
6666
```
6767

6868
Now, we define the Fourier transform. Since our input is purely Real, we use the real
@@ -79,15 +79,15 @@ P = plan_rfft(x)
7979

8080
Now we are ready to define our wrapper for the FFT object. To `FunctionOperator`, we
8181
pass the in-place forward application of the transform,
82-
`(du,u,p,t) -> mul!(du, transform, u)`, its inverse application,
83-
`(du,u,p,t) -> ldiv!(du, transform, u)`, as well as input and output prototype vectors.
82+
`(w,v,u,p,t) -> mul!(w, transform, v)`, its inverse application,
83+
`(w,v,u,p,t) -> ldiv!(w, transform, v)`, as well as input and output prototype vectors.
8484

8585
```@example fft_explanation
86-
fwd(u, p, t) = P * u
87-
bwd(u, p, t) = P \ u
86+
fwd(v, u, p, t) = P * v
87+
bwd(v, u, p, t) = P \ v
8888
89-
fwd(du, u, p, t) = mul!(du, P, u)
90-
bwd(du, u, p, t) = ldiv!(du, P, u)
89+
fwd(w, v, u, p, t) = mul!(w, P, v)
90+
bwd(w, v, u, p, t) = ldiv!(w, P, v)
9191
F = FunctionOperator(fwd, x, im * k;
9292
T = ComplexF64, op_adjoint = bwd,
9393
op_inverse = bwd,
@@ -106,6 +106,6 @@ Dx = F \ ik * F
106106
107107
Dx = cache_operator(Dx, x)
108108
109-
@show ≈(Dx * u, du; atol = 1e-8)
110-
@show ≈(mul!(copy(u), Dx, u), du; atol = 1e-8)
109+
@show ≈(Dx * v, w; atol = 1e-8)
110+
@show ≈(mul!(copy(w), Dx, v), w; atol = 1e-8)
111111
```

0 commit comments

Comments
 (0)