Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
ff55ed8
SciMLOperators Different defining vectors
divital-coder May 5, 2025
41108ec
Operators in work, modificataions -> operator update interface, opera…
divital-coder May 9, 2025
44f7121
Updated interface for operators, addition of new and modified tests
divital-coder May 12, 2025
17a6a58
Update SciMLOperators.jl
ChrisRackauckas May 12, 2025
06083e9
Update interface.jl
ChrisRackauckas May 12, 2025
81adab6
Modified tests, fixed left.jl as per reviews
divital-coder May 15, 2025
7cd3adf
Fixed Zygote tests
divital-coder May 15, 2025
d38b165
Update runtests.jl
ChrisRackauckas May 15, 2025
51776eb
Added original test lines back
divital-coder May 16, 2025
9926df0
Update test/func.jl
ChrisRackauckas May 16, 2025
7df820d
Update test/func.jl
ChrisRackauckas May 16, 2025
b43e25a
Update test/func.jl
ChrisRackauckas May 16, 2025
d830399
Update test/func.jl
ChrisRackauckas May 16, 2025
ea686c6
Update test/scalar.jl
ChrisRackauckas May 16, 2025
6dc91ef
Update test/scalar.jl
ChrisRackauckas May 16, 2025
3ab5da1
Update test/scalar.jl
ChrisRackauckas May 16, 2025
09c80a2
Update test/scalar.jl
ChrisRackauckas May 16, 2025
efdb2e7
Update test/total.jl
ChrisRackauckas May 16, 2025
3dc0ae0
fix a lot of docstrings
ChrisRackauckas May 16, 2025
7451c30
one more docstring
ChrisRackauckas May 16, 2025
f7a39df
update docs
ChrisRackauckas May 16, 2025
5892a2f
let doc tests run
ChrisRackauckas May 16, 2025
d3a36fc
fix variable names
ChrisRackauckas May 16, 2025
f6f11aa
some more renaming and fix matrix tests
ChrisRackauckas May 16, 2025
9d4d0b5
Fix FunctionOperator definition
ChrisRackauckas May 16, 2025
b70b102
get tests passing
ChrisRackauckas May 17, 2025
b7d2132
fix doc builds
ChrisRackauckas May 17, 2025
fcc3833
fix typo
ChrisRackauckas May 17, 2025
7376381
fix docs
ChrisRackauckas May 17, 2025
3cd098a
alloc test passes on later versions
ChrisRackauckas May 17, 2025
500b89f
fix alloc tests
ChrisRackauckas May 17, 2025
7d105da
split allocs checks
ChrisRackauckas May 17, 2025
098264b
fix new test setup
ChrisRackauckas May 17, 2025
1c47fe6
alloc tests throw
ChrisRackauckas May 17, 2025
596f239
fix pre
ChrisRackauckas May 17, 2025
91e4312
finalize
ChrisRackauckas May 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "SciMLOperators"
uuid = "c0aeaf25-5076-4817-a8d5-81caf7dfa961"
authors = ["Vedant Puri <[email protected]>"]
version = "0.3.13"
version = "0.4.0"

[deps]
ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9"
Expand Down
28 changes: 18 additions & 10 deletions src/SciMLOperators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,29 @@ Subtypes of `AbstractSciMLOperator` represent linear, nonlinear,
time-dependent operators acting on vectors, or matrix column-vectors.
A lazy operator algebra is also defined for `AbstractSciMLOperator`s.

# Mathematical Notation

An `AbstractSciMLOperator` ``L`` is an operator which is used to represent
the following type of equation:

```math
w = L(u,p,t)[v]
```

where `L[v]` is the operator application of ``L`` on the vector ``v``.

# Interface

An `AbstractSciMLOperator` can be called like a function. This behaves
like multiplication by the linear operator represented by the
`AbstractSciMLOperator`. Possible signatures are
An `AbstractSciMLOperator` can be called like a function in the following ways:

- `L(v, u, p, t)` for in-place operator evaluation
- `v = L(u, p, t)` for out-of-place operator evaluation
- `L(v, u, p, t)` - Out-of-place application where `v` is the action vector and `u` is the update vector
- `L(w, v, u, p, t)` - In-place application where `w` is the destination, `v` is the action vector, and `u` is the update vector
- `L(w, v, u, p, t, α, β)` - In-place application with scaling: `w = α*(L*v) + β*w`

Operator evaluation methods update its coefficients with `(u, p, t)`
information using the `update_coefficients(!)` method. The methods
are exported and can be called as follows:
Operator state can be updated separately from application:

- `update_coefficients!(L, u, p, t)` for out-of-place operator update
- `L = update_coefficients(L, u, p, t)` for in-place operator update
- `update_coefficients!(L, u, p, t)` for in-place operator update
- `L = update_coefficients(L, u, p, t)` for out-of-place operator update

SciMLOperators also overloads `Base.*`, `LinearAlgebra.mul!`,
`LinearAlgebra.ldiv!` for operator evaluation without updating operator state.
Expand Down
187 changes: 187 additions & 0 deletions src/basic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,27 @@ function LinearAlgebra.ldiv!(ii::IdentityOperator, u::AbstractVecOrMat)
u
end

# Out-of-place: v is action vector, u is update vector
function (ii::IdentityOperator)(v::AbstractVecOrMat, u, p, t; kwargs...)
@assert size(v, 1) == ii.len
update_coefficients(ii, u, p, t; kwargs...)
copy(v)
end

# In-place: w is destination, v is action vector, u is update vector
function (ii::IdentityOperator)(w::AbstractVecOrMat, v::AbstractVecOrMat, u, p, t; kwargs...)
@assert size(v, 1) == ii.len
update_coefficients!(ii, u, p, t; kwargs...)
copy!(w, v)
end

# In-place with scaling: w = α*(ii*v) + β*w
function (ii::IdentityOperator)(w::AbstractVecOrMat, v::AbstractVecOrMat, u, p, t, α, β; kwargs...)
@assert size(v, 1) == ii.len
update_coefficients!(ii, u, p, t; kwargs...)
mul!(w, I, v, α, β)
end

# operator fusion with identity returns operator itself
for op in (:*, :∘)
@eval function Base.$op(ii::IdentityOperator, A::AbstractSciMLOperator)
Expand Down Expand Up @@ -146,6 +167,26 @@ function LinearAlgebra.mul!(v::AbstractVecOrMat,
lmul!(β, v)
end

# Out-of-place: v is action vector, u is update vector
function (nn::NullOperator)(v::AbstractVecOrMat, u, p, t; kwargs...)
@assert size(v, 1) == nn.len
zero(v)
end

# In-place: w is destination, v is action vector, u is update vector
function (nn::NullOperator)(w::AbstractVecOrMat, v::AbstractVecOrMat, u, p, t; kwargs...)
@assert size(v, 1) == nn.len
lmul!(false, w)
w
end

# In-place with scaling: w = α*(nn*v) + β*w
function (nn::NullOperator)(w::AbstractVecOrMat, v::AbstractVecOrMat, u, p, t, α, β; kwargs...)
@assert size(v, 1) == nn.len
lmul!(β, w)
w
end

# operator fusion, composition
for op in (:*, :∘)
@eval function Base.$op(nn::NullOperator, A::AbstractSciMLOperator)
Expand Down Expand Up @@ -218,6 +259,26 @@ for T in SCALINGNUMBERTYPES
end
end

# Special cases for constant scalars. These simplify the structure when applicable
for T in SCALINGNUMBERTYPES[2:end]
@eval function Base.:*(α::$T, L::ScaledOperator)
isconstant(L.λ) && return ScaledOperator(α * L.λ, L.L)
return ScaledOperator(L.λ, α * L.L) # Try to propagate the rule
end
@eval function Base.:*(L::ScaledOperator, α::$T)
isconstant(L.λ) && return ScaledOperator(α * L.λ, L.L)
return ScaledOperator(L.λ, α * L.L) # Try to propagate the rule
end
@eval function Base.:*(α::$T, L::MatrixOperator)
isconstant(L) && return MatrixOperator(α * L.A)
return ScaledOperator(α, L) # Going back to the generic case
end
@eval function Base.:*(L::MatrixOperator, α::$T)
isconstant(L) && return MatrixOperator(α * L.A)
return ScaledOperator(α, L) # Going back to the generic case
end
end

Base.:-(L::AbstractSciMLOperator) = ScaledOperator(-true, L)
Base.:+(L::AbstractSciMLOperator) = L

Expand Down Expand Up @@ -316,6 +377,43 @@ function LinearAlgebra.ldiv!(L::ScaledOperator, u::AbstractVecOrMat)
ldiv!(L.L, u)
end

# Out-of-place: v is action vector, u is update vector
function (L::ScaledOperator)(v::AbstractVecOrMat, u, p, t; kwargs...)
L = update_coefficients(L, u, p, t; kwargs...)
if iszero(L.λ)
return zero(v)
else
return L.λ * (L.L * v)
end
end

# In-place: w is destination, v is action vector, u is update vector
function (L::ScaledOperator)(w::AbstractVecOrMat, v::AbstractVecOrMat, u, p, t; kwargs...)
update_coefficients!(L, u, p, t; kwargs...)
if iszero(L.λ)
lmul!(false, w)
return w
else
a = convert(Number, L.λ)
mul!(w, L.L, v, a, false)
return w
end
end

# In-place with scaling: w = α*(L*v) + β*w
function (L::ScaledOperator)(w::AbstractVecOrMat, v::AbstractVecOrMat, u, p, t, α, β; kwargs...)
update_coefficients!(L, u, p, t; kwargs...)
if iszero(L.λ)
lmul!(β, w)
return w
else
a = convert(Number, L.λ * α)
mul!(w, L.L, v, a, β)
return w
end
end


"""
Lazy operator addition

Expand Down Expand Up @@ -518,6 +616,35 @@ end
v
end
end
# Out-of-place: v is action vector, u is update vector
function (L::AddedOperator)(v::AbstractVecOrMat, u, p, t; kwargs...)
L = update_coefficients(L, u, p, t; kwargs...)
sum(op -> iszero(op) ? zero(v) : op(v, u, p, t; kwargs...), L.ops)
end

# In-place: w is destination, v is action vector, u is update vector
function (L::AddedOperator)(w::AbstractVecOrMat, v::AbstractVecOrMat, u, p, t; kwargs...)
update_coefficients!(L, u, p, t; kwargs...)
L.ops[1](w, v, u, p, t; kwargs...)
for i in 2:length(L.ops)
if !iszero(L.ops[i])
L.ops[i](w, v, u, p, t, 1.0, 1.0; kwargs...)
end
end
w
end

# In-place with scaling: w = α*(L*v) + β*w
function (L::AddedOperator)(w::AbstractVecOrMat, v::AbstractVecOrMat, u, p, t, α, β; kwargs...)
update_coefficients!(L, u, p, t; kwargs...)
lmul!(β, w)
for op in L.ops
if !iszero(op)
op(w, v, u, p, t, α, 1.0; kwargs...)
end
end
w
end

"""
Lazy operator composition
Expand Down Expand Up @@ -772,6 +899,41 @@ function LinearAlgebra.ldiv!(L::ComposedOperator, u::AbstractVecOrMat)
u
end

# Out-of-place: v is action vector, u is update vector
function (L::ComposedOperator)(v::AbstractVecOrMat, u, p, t; kwargs...)
L = update_coefficients(L, u, p, t; kwargs...)
result = v
for op in reverse(L.ops)
result = op(result, u, p, t; kwargs...)
end
result
end

# In-place: w is destination, v is action vector, u is update vector
function (L::ComposedOperator)(w::AbstractVecOrMat, v::AbstractVecOrMat, u, p, t; kwargs...)
update_coefficients!(L, u, p, t; kwargs...)
@assert iscached(L) "Cache needs to be set up for ComposedOperator. Call cache_operator(L, u) first."

vecs = (w, L.cache[1:(end-1)]..., v)
for i in reverse(1:length(L.ops))
L.ops[i](vecs[i], vecs[i+1], u, p, t; kwargs...)
end
w
end

# In-place with scaling: w = α*(L*v) + β*w
function (L::ComposedOperator)(w::AbstractVecOrMat, v::AbstractVecOrMat, u, p, t, α, β; kwargs...)
update_coefficients!(L, u, p, t; kwargs...)
@assert iscached(L) "Cache needs to be set up for ComposedOperator. Call cache_operator(L, u) first."

cache = L.cache[end]
copy!(cache, w)

L(w, v, u, p, t; kwargs...)
lmul!(α, w)
axpy!(β, cache, w)
end

"""
Lazy Operator Inverse
"""
Expand Down Expand Up @@ -889,4 +1051,29 @@ function LinearAlgebra.ldiv!(L::InvertedOperator, u::AbstractVecOrMat)
copy!(L.cache, u)
mul!(u, L.L, L.cache)
end

# Out-of-place: v is action vector, u is update vector
function (L::InvertedOperator)(v::AbstractVecOrMat, u, p, t; kwargs...)
L = update_coefficients(L, u, p, t; kwargs...)
L.L \ v
end

# In-place: w is destination, v is action vector, u is update vector
function (L::InvertedOperator)(w::AbstractVecOrMat, v::AbstractVecOrMat, u, p, t; kwargs...)
update_coefficients!(L, u, p, t; kwargs...)
ldiv!(w, L.L, v)
w
end

# In-place with scaling: w = α*(L*v) + β*w
function (L::InvertedOperator)(w::AbstractVecOrMat, v::AbstractVecOrMat, u, p, t, α, β; kwargs...)
update_coefficients!(L, u, p, t; kwargs...)
@assert iscached(L) "Cache needs to be set up for InvertedOperator. Call cache_operator(L, u) first."

copy!(L.cache, w)
ldiv!(w, L.L, v)
lmul!(α, w)
axpy!(β, L.cache, w)
w
end
#
21 changes: 21 additions & 0 deletions src/batch.jl
Original file line number Diff line number Diff line change
Expand Up @@ -153,4 +153,25 @@ function LinearAlgebra.ldiv!(L::BatchedDiagonalOperator, u::AbstractVecOrMat)

u
end

function (L::BatchedDiagonalOperator)(v::AbstractVecOrMat, u, p, t; kwargs...)
L = update_coefficients(L, u, p, t; kwargs...)
L.diag .* v
end

function (L::BatchedDiagonalOperator)(w::AbstractVecOrMat, v::AbstractVecOrMat, u, p, t; kwargs...)
update_coefficients!(L, u, p, t; kwargs...)
w .= L.diag .* v
return w
end

function (L::BatchedDiagonalOperator)(w::AbstractVecOrMat, v::AbstractVecOrMat, u, p, t, α, β; kwargs...)
update_coefficients!(L, u, p, t; kwargs...)
if β == 0
w .= α .* (L.diag .* v)
else
w .= α .* (L.diag .* v) .+ β .* w
end
return w
end
#
64 changes: 64 additions & 0 deletions src/func.jl
Original file line number Diff line number Diff line change
Expand Up @@ -782,4 +782,68 @@ end
function LinearAlgebra.ldiv!(L::FunctionOperator{false}, u::AbstractArray)
@error "LinearAlgebra.ldiv! not defined for out-of-place $L"
end

# Out-of-place: v is action vector, u is update vector
function (L::FunctionOperator)(v::AbstractArray, u, p, t; kwargs...)
L = update_coefficients(L, u, p, t; kwargs...)
_sizecheck(L, v, nothing)
V, _, vec_output = _unvec(L, v, nothing)

# Apply the operator to action vector v after updating with u
if L.traits.outofplace
result = L.op(V, L.p, L.t; L.traits.kwargs...)
return vec_output ? vec(result) : result
else
# For operators without out-of-place methods, use their in-place methods with a temporary
Co = similar(V)
L.op(Co, V, L.p, L.t; L.traits.kwargs...)
return vec_output ? vec(Co) : Co
end
end

# In-place: w is destination, v is action vector, u is update vector
function (L::FunctionOperator)(w::AbstractArray, v::AbstractArray, u, p, t; kwargs...)
update_coefficients!(L, u, p, t; kwargs...)

# Check dimensions
_sizecheck(L, v, w)
V, W, _ = _unvec(L, v, w)

# Apply the operator in-place to action vector v after updating with u
if L.traits.isinplace
L.op(W, V, L.p, L.t; L.traits.kwargs...)
else
# For operators without in-place methods, use their out-of-place methods
result = L.op(V, L.p, L.t; L.traits.kwargs...)
copyto!(W, result)
end

return w
end

# In-place with scaling: w = α*(L*v) + β*w
function (L::FunctionOperator)(w::AbstractArray, v::AbstractArray, u, p, t, α, β; kwargs...)
update_coefficients!(L, u, p, t; kwargs...)

# Check dimensions
_sizecheck(L, v, w)
V, W, _ = _unvec(L, v, w)

# Apply the operator in-place to action vector v with scaling
if L.traits.isinplace && L.traits.has_mul5
# Direct 5-arg mul! if supported
L.op(W, V, L.p, L.t, α, β; L.traits.kwargs...)
elseif L.traits.isinplace
# Use temporary for regular in-place
temp = copy(W)
L.op(W, V, L.p, L.t; L.traits.kwargs...)
axpby!(β, temp, α, W)
else
# Out-of-place with scaling
result = L.op(V, L.p, L.t; L.traits.kwargs...)
axpby!(β, W, α, result)
end

return w
end
#
Loading