1111[ ![ ColPrac: Contributor's Guide on Collaborative Practices for Community Packages] ( https://img.shields.io/badge/ColPrac-Contributor's%20Guide-blueviolet )] ( https://github.com/SciML/ColPrac )
1212[ ![ SciML Code Style] ( https://img.shields.io/static/v1?label=code%20style&message=SciML&color=9558b2&labelColor=389826 )] ( https://github.com/SciML/SciMLStyle )
1313
14- ` SciMLOperators ` is a package for managing linear, nonlinear, and
15- time-dependent operators acting on vectors, (or column-vectors of matrices).
16- We provide wrappers for matrix-free operators, fast tensor-product
17- evaluations, pre-cached mutating evaluations, as well as ` Zygote ` -compatible
18- non-mutating evaluations.
14+ ` SciMLOperators ` is a package for managing linear, nonlinear,
15+ time-dependent, and parameter dependent operators acting on vectors,
16+ (or column-vectors of matrices). We provide wrappers for matrix-free
17+ operators, fast tensor-product evaluations, pre-cached mutating
18+ evaluations, as well as ` Zygote ` -compatible non-mutating evaluations.
1919
2020The lazily implemented operator algebra allows the user to update the
2121operator state by passing in an update function that accepts arbirary
@@ -37,8 +37,8 @@ julia> Pkg.add("SciMLOperators")
3737
3838## Examples
3939
40- Let ` M ` , ` D ` , ` F ` be matrix, diagonal, and function-based ` SciMLOperators `
41- respectively.
40+ Let ` M ` , ` D ` , ` F ` be matrix, diagonal matrix , and function-based
41+ ` SciMLOperators ` respectively.
4242
4343``` julia
4444N = 4
@@ -52,37 +52,56 @@ F = FunctionOperator(f, zeros(N), zeros(N))
5252Then, the following codes just work.
5353
5454``` julia
55- L1 = 2 M + 3 F + LinearAlgebra. I
55+ L1 = 2 M + 3 F + LinearAlgebra. I + rand (N, N)
5656L2 = D * F * M'
5757L3 = kron (M, D, F)
5858L4 = M \ D
5959L5 = [M; D]' * [M F; F D] * [F; D]
6060```
6161
62- Each ` L# ` can be applied to vectors of appropriate sizes:
62+ Each ` L# ` can be applied to ` AbstractVector ` s of appropriate sizes:
6363
6464``` julia
65+ p = nothing # parameter struct
66+ t = 0.0 # time
67+
6568u = rand (N)
66- v = zeros (N)
67- u_kron = rand (N ^ 3 )
69+ v = L1 (u, p, t) # == L1 * u
6870
69- v = L1 * u
70- v_kron = L3 (u_kron, p, t)
71+ u_kron = rand (N ^ 3 )
72+ v_kron = L3 (u_kron, p, t) # == L3 * u_kron
7173```
7274
7375For mutating operator evaluations, call ` cache_operator ` to generate
7476in-place cache so the operation is nonallocating.
7577
7678``` julia
79+ α, β = rand (2 )
80+
7781# allocate cache
7882L2 = cache_operator (L2, u)
7983L4 = cache_operator (L4, u)
8084
8185# allocation-free evaluation
82- mul! (v, L2, u)
83- L4 (v, u, p, t)
86+ L2 (v, u, p, t) # == mul!(v, L2, u)
87+ L4 (v, u, p, t, α, β) # == mul!(v, L4, u, α, β )
8488```
8589
90+ The calling signature ` L(u, p, t) ` , for out-of-place evaluations is
91+ equivalent to ` L * u ` , and the in-place evaluation ` L(v, u, p, t, args...) `
92+ is equivalent to ` LinearAlgebra.mul!(v, L, u, args...) ` , where the arguments
93+ ` p, t ` are passed to ` L ` to update its state. More details are provided
94+ in the operator update section below. While overloads to ` Base.* `
95+ and ` LinearAlgebra.mul! ` are available, where a ` SciMLOperator ` behaves
96+ like an ` AbstractMatrix ` , we recommend sticking with the
97+ ` L(u, p, t) ` , ` L(v, u, p, t) ` , ` L(v, u, p, t, α, β) ` calling signatures
98+ as the latter internally update the operator state.
99+
100+ The ` (u, p, t) ` calling signature is standardized over the ` SciML `
101+ ecosystem and is flexible enough to support use cases such as time-evolution
102+ in ODEs, as well as sensitivity computation with respect to the parameter
103+ object ` p ` .
104+
86105Thanks to overloads defined for evaluation methods and traits in
87106` Base ` , ` LinearAlgebra ` , the behaviour of a ` SciMLOperator ` is
88107indistinguishable from an ` AbstractMatrix ` . These operators can be
@@ -94,8 +113,120 @@ interface include, but are not limited, the following:
94113- ` LinearAlgebra: mul!, ldiv!, lmul!, rmul!, factorize, issymmetric, ishermitian, isposdef `
95114- ` SparseArrays: sparse, issparse `
96115
116+ ## Multidimension arrays and batching
117+
118+ SciMLOperator can also be applied to ` AbstractMatrix ` subtypes where
119+ operator-evaluation is done column-wise.
120+
121+ ``` julia
122+ K = 10
123+ u_mat = rand (N, K)
124+
125+ v_mat = F (u_mat, p, t) # == mul!(v_mat, F, u_mat)
126+ size (v_mat) == (N, K) # true
127+ ```
128+
129+ ` L# ` can also be applied to ` AbstractArray ` s that are not
130+ ` AbstractVecOrMat ` s so long as their size in the first dimension is appropriate
131+ for matrix-multiplication. Internally, ` SciMLOperator ` s reshapes an
132+ ` N ` -dimensional array to an ` AbstractMatrix ` , and applies the operator via
133+ matrix-multiplication.
134+
97135## Operator update
98136
137+ This package can also be used to write time-dependent, and
138+ parameter-dependent operators, whose state can be updated per
139+ a user-defined function.
140+ The updates can be done in-place, i.e. by mutating the object,
141+ or out-of-place, i.e. in a non-mutating, ` Zygote ` -compatible way.
142+
143+ For example,
144+
145+ ``` julia
146+ u = rand (N)
147+ p = rand (N)
148+ t = rand ()
149+
150+ # out-of-place update
151+ mat_update_func = (A, u, p, t) -> t * (p * p' )
152+ sca_update_func = (a, u, p, t) -> t * sum (p)
153+
154+ M = MatrixOperator (zero (N, N); update_func = mat_update_func)
155+ α = ScalarOperator (zero (Float64); update_func = sca_update_func)
156+
157+ L = α * M
158+ L = cache_operator (L, u)
159+
160+ # L is initialized with zero state
161+ L * u == zeros (N) # true
162+
163+ # update operator state with `(u, p, t)`
164+ L = update_coefficients (L, u, p, t)
165+ # and multiply
166+ L * u != zeros (N) # true
167+
168+ # updates state and evaluates L at (u, p, t)
169+ L (u, p, t) != zeros (N) # true
170+ ```
171+
172+ The out-of-place evaluation function ` L(u, p, t) ` calls
173+ ` update_coefficients ` under the hood, which recursively calls
174+ the ` update_func ` for each component ` SciMLOperator ` .
175+ Therefore the out-of-place evaluation function is equivalent to
176+ calling ` update_coefficients ` followed by ` Base.* ` . Notice that
177+ the out-of-place evaluation does not return the updated operator.
178+
179+ On the other hand,, the in-place evaluation function, ` L(v, u, p, t) ` ,
180+ mutates ` L ` , and is equivalent to calling ` update_coefficients! `
181+ followed by ` mul! ` . The in-place update behaviour works the same way
182+ with a few ` <!> ` s appended here and there. For example,
183+
184+ ``` julia
185+ v = rand (N)
186+ u = rand (N)
187+ p = rand (N)
188+ t = rand ()
189+
190+ # in-place update
191+ _A = rand (N, N)
192+ _d = rand (N)
193+ mat_update_func! = (A, u, p, t) -> (copy! (A, _A); lmul! (t, A); nothing )
194+ diag_update_func! = (diag, u, p, t) -> copy! (diag, N)
195+
196+ M = MatrixOperator (zero (N, N); update_func! = mat_update_func!)
197+ D = DiagonalOperator (zero (N); update_func! = diag_update_func!)
198+
199+ L = D * M
200+ L = cache_operator (L, u)
201+
202+ # L is initialized with zero state
203+ L * u == zeros (N) # true
204+
205+ # update L in-place
206+ update_coefficients! (L, u, p, t)
207+ # and multiply
208+ mul! (v, u, p, t) != zero (N) # true
209+
210+ # updates L in-place, and evaluates at (u, p, t)
211+ L (v, u, p, t) != zero (N) # true
212+ ```
213+
214+ The update behaviour makes this package flexible enough to be used
215+ in ` OrdianryDiffEq ` . As the parameter object ` p ` is often reserved
216+ for sensitivy computation via automatic-differentiation, a user may
217+ prefer to pass in state information via other arguments. For that
218+ reason, we allow for update functions with arbitrary keyword arguments.
219+
220+ ``` julia
221+ mat_update_func = (A, u, p, t; scale = 0.0 ) -> scale * (p * p' )
222+
223+ M = MatrixOperator (zero (N, N); update_func = mat_update_func,
224+ accepted_kwargs = (:state ,))
225+
226+ M (u, p, t) == zeros (N) # true
227+ M (u, p, t; scale = 1.0 ) != zero (N)
228+ ```
229+
99230## Features
100231
101232* Matrix-free operators with ` FunctionOperator `
@@ -143,9 +274,9 @@ Some packages providing similar functionality are
143274## Interoperability and extended Julia ecosystem
144275
145276` SciMLOperator.jl ` overloads the ` AbstractMatrix ` interface for
146- ` AbstractSciMLOperator ` s, here allowing seamless compatibility with
277+ ` AbstractSciMLOperator ` s, allowing seamless compatibility with
147278linear solves, and nonlinear solvers. Further, due to the update functionality,
148- ` AbstractSciMLOperato ` s can represent an ` ODEFunction ` in ` OrdinaryDiffEq.jl ` ,
279+ ` AbstractSciMLOperator ` s can represent an ` ODEFunction ` in ` OrdinaryDiffEq.jl ` ,
149280and downstream packages. See tutorials for example of usage with
150281` OrdinaryDiffEq.jl ` , ` LinearSolve.jl ` , ` NonlinearSolve.jl ` .
151282
@@ -157,7 +288,7 @@ An example of `Zygote.jl` usage with
157288[ ` Lux.jl ` ] ( https://github.com/LuxDL/Lux.jl ) is also provided in the tutorials.
158289
159290Please make an issue [ here] ( https://github.com/SciML/SciMLOperators.jl/issues )
160- if you come across an unexpected issue using ` SciMLOperator `
291+ if you come across an unexpected issue while using ` SciMLOperators ` .
161292
162293We provide below a list of packages that make use of ` SciMLOperators ` .
163294If you are using ` SciMLOperators ` in your work, feel free to create a PR
@@ -190,4 +321,3 @@ and add your package to this list.
190321 - [ JuliaDiffEq] ( https://gitter.im/JuliaDiffEq/Lobby ) on Gitter
191322 - On the Julia Discourse forums (look for the [ modelingtoolkit tag] ( https://discourse.julialang.org/tag/modelingtoolkit )
192323 - See also [ SciML Community page] ( https://sciml.ai/community/ )
193-
0 commit comments