-
-
Notifications
You must be signed in to change notification settings - Fork 195
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Lazy W operator support #443
Changes from 26 commits
71147ee
42ce8c5
a4addeb
197fc40
1a1f5ad
2f17963
df84f70
c57604b
9f40d55
66c62aa
2c5850e
f7e425e
4bd3617
bb185eb
d175f9b
7b7a1cd
08f460d
54b9029
fcc937f
9fe4202
e6f639c
5cedc29
bf600ea
c48a0de
7983293
21b1a5b
be258f8
66a38fb
f01872d
e3c1505
3f8cefe
c4437ea
1eba5e9
b8ae169
e0eea48
72cde28
80d072b
87e174d
90f377b
e2beccb
b520fe0
c1e53da
284fa70
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
julia 0.7-beta2 | ||
DiffEqBase 3.8.0 | ||
DiffEqOperators 3.2.0 | ||
Parameters 0.5.0 | ||
ForwardDiff 0.7.0 | ||
GenericSVD 0.0.2 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -54,10 +54,161 @@ function calc_J!(integrator, cache::OrdinaryDiffEqMutableCache, is_compos) | |
is_compos && (integrator.eigen_est = opnorm(J, Inf)) | ||
end | ||
|
||
""" | ||
WOperator(mass_matrix,gamma,J[;transform=false]) | ||
|
||
A linear operator that represents the W matrix of an ODEProblem, defined as | ||
|
||
```math | ||
W = MM - \\gamma J | ||
``` | ||
|
||
or, if `transform=true`: | ||
|
||
```math | ||
W = \\frac{1}{\\gamma}MM - J | ||
``` | ||
|
||
where `MM` is the mass matrix (a regular `AbstractMatrix` or a `UniformScaling`), | ||
`γ` is a real number proportional to the time step, and `J` is the Jacobian | ||
operator (must be a `AbstractDiffEqLinearOperator`). A `WOperator` can also be | ||
constructed using a `*DEFunction` directly as | ||
|
||
WOperator(f,gamma[;transform=false]) | ||
|
||
`f` needs to have a jacobian and `jac_prototype`, but the prototype does not need | ||
to be a diffeq operator --- it will automatically be converted to one. | ||
|
||
`WOperator` supports lazy `*` and `mul!` operations, the latter utilizing an | ||
internal cache (can be specified in the constructor; default to regular `Vector`). | ||
It supports all of `AbstractDiffEqLinearOperator`'s interface. | ||
""" | ||
mutable struct WOperator{T, | ||
MType <: Union{UniformScaling,AbstractMatrix}, | ||
GType <: Real, | ||
JType <: DiffEqBase.AbstractDiffEqLinearOperator{T} | ||
} <: DiffEqBase.AbstractDiffEqLinearOperator{T} | ||
mass_matrix::MType | ||
gamma::GType | ||
J::JType | ||
transform::Bool # true => W = mm/gamma - J; false => W = mm - gamma*J | ||
_func_cache # cache used in `mul!` | ||
_concrete_form # non-lazy form (matrix/number) of the operator | ||
WOperator(mass_matrix, gamma, J; transform=false) = new{eltype(J),typeof(mass_matrix), | ||
typeof(gamma),typeof(J)}(mass_matrix,gamma,J,transform,nothing,nothing) | ||
end | ||
function WOperator(f::DiffEqBase.AbstractODEFunction, gamma; transform=false) | ||
@assert DiffEqBase.has_jac(f) "f needs to have an associated jacobian" | ||
if isa(f, Union{SplitFunction, DynamicalODEFunction}) | ||
error("WOperator does not support $(typeof(f)) yet") | ||
end | ||
# Convert mass matrix, if needed | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's fine for now. I am hoping to support time-dependent mass matrices but we can just add an issue that it's a feature to implement in the future. |
||
mass_matrix = f.mass_matrix | ||
if !isa(mass_matrix, Union{AbstractMatrix,UniformScaling}) | ||
mass_matrix = convert(AbstractMatrix, mass_matrix) | ||
end | ||
# Convert jacobian, if needed | ||
J = deepcopy(f.jac_prototype) | ||
if !isa(J, DiffEqBase.AbstractDiffEqLinearOperator) | ||
J = DiffEqArrayOperator(J; update_func=f.jac) | ||
end | ||
return WOperator(mass_matrix, gamma, J; transform=transform) | ||
end | ||
|
||
set_gamma!(W::WOperator, gamma) = (W.gamma = gamma; W) | ||
DiffEqBase.update_coefficients!(W::WOperator,u,p,t) = (update_coefficients!(W.J,u,p,t); W) | ||
function Base.convert(::Type{AbstractMatrix}, W::WOperator) | ||
if W._concrete_form == nothing | ||
# Allocating | ||
if W.transform | ||
W._concrete_form = W.mass_matrix / W.gamma - convert(AbstractMatrix,W.J) | ||
else | ||
W._concrete_form = W.mass_matrix - W.gamma * convert(AbstractMatrix,W.J) | ||
end | ||
else | ||
# Non-allocating | ||
if W.transform | ||
rmul!(copyto!(W._concrete_form, W.mass_matrix), 1/W.gamma) | ||
axpy!(-1, convert(AbstractMatrix,W.J), W._concrete_form) | ||
else | ||
copyto!(W._concrete_form, W.mass_matrix) | ||
axpy!(-W.gamma, convert(AbstractMatrix,W.J), W._concrete_form) | ||
end | ||
end | ||
W._concrete_form | ||
end | ||
function Base.convert(::Type{Number}, W::WOperator) | ||
if W.transform | ||
W._concrete_form = W.mass_matrix / W.gamma - convert(Number,W.J) | ||
else | ||
W._concrete_form = W.mass_matrix - W.gamma * convert(Number,W.J) | ||
end | ||
W._concrete_form | ||
end | ||
Base.size(W::WOperator, args...) = size(W.J, args...) | ||
function Base.getindex(W::WOperator, i::Int) | ||
if W.transform | ||
W.mass_matrix[i] / W.gamma - W.J[i] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this shows up everywhere and it's constant given by the algorithm, it might make sense to make this a type parameter so the code can simply dispatch off of it, or at least not have large performance cuts because of this conditioning in indexing. I'm not sure this indexing is really used all that much though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is true both for factorization (it indexes the concrete matrix instead of W) and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good |
||
else | ||
W.mass_matrix[i] - W.gamma * W.J[i] | ||
end | ||
end | ||
function Base.getindex(W::WOperator, I::Vararg{Int,N}) where {N} | ||
if W.transform | ||
W.mass_matrix[I...] / W.gamma - W.J[I...] | ||
else | ||
W.mass_matrix[I...] - W.gamma * W.J[I...] | ||
end | ||
end | ||
function Base.:*(W::WOperator, x::Union{AbstractVecOrMat,Number}) | ||
if W.transform | ||
(W.mass_matrix*x) / W.gamma - W.J*x | ||
else | ||
W.mass_matrix*x - W.gamma * (W.J*x) | ||
end | ||
end | ||
function Base.:\(W::WOperator, x::Union{AbstractVecOrMat,Number}) | ||
if size(W) == () # scalar operator | ||
convert(Number,W) \ x | ||
else | ||
convert(AbstractMatrix,W) \ x | ||
end | ||
end | ||
function LinearAlgebra.mul!(Y::AbstractVecOrMat, W::WOperator, B::AbstractVecOrMat) | ||
if W._func_cache == nothing | ||
# Allocate cache only if needed | ||
W._func_cache = Vector{eltype(W)}(undef, size(Y, 1)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think algorithm caches should be able to supply this during construction when possible. That's an optimization we can add later. |
||
end | ||
if W.transform | ||
# Compute mass_matrix * B | ||
if isa(W.mass_matrix, UniformScaling) | ||
a = W.mass_matrix.λ / W.gamma | ||
@. Y = a * B | ||
else | ||
mul!(Y, W.mass_matrix, B) | ||
lmul!(1/W.gamma, Y) | ||
end | ||
# Compute J * B and subtract | ||
mul!(W._func_cache, W.J, B) | ||
Y .-= W._func_cache | ||
else | ||
# Compute mass_matrix * B | ||
if isa(W.mass_matrix, UniformScaling) | ||
@. Y = W.mass_matrix.λ * B | ||
else | ||
mul!(Y, W.mass_matrix, B) | ||
end | ||
# Compute J * B | ||
mul!(W._func_cache, W.J, B) | ||
# Subtract result | ||
axpy!(-W.gamma, W._func_cache, Y) | ||
end | ||
end | ||
|
||
function calc_W!(integrator, cache::OrdinaryDiffEqMutableCache, dtgamma, repeat_step, W_transform=false) | ||
@inbounds begin | ||
@unpack t,dt,uprev,u,f,p = integrator | ||
@unpack J,W,jac_config = cache | ||
@unpack J,W = cache | ||
mass_matrix = integrator.f.mass_matrix | ||
is_compos = typeof(integrator.alg) <: CompositeAlgorithm | ||
alg = unwrap_alg(integrator, true) | ||
|
@@ -78,21 +229,14 @@ function calc_W!(integrator, cache::OrdinaryDiffEqMutableCache, dtgamma, repeat_ | |
new_jac = false | ||
else | ||
new_jac = true | ||
calc_J!(integrator, cache, is_compos) | ||
DiffEqBase.update_coefficients!(W,uprev,p,t) | ||
end | ||
# skip calculation of W if step is repeated | ||
if !repeat_step && (!alg_can_repeat_jac(alg) || | ||
(integrator.iter < 1 || new_jac || | ||
abs(dt - (t-integrator.tprev)) > 100eps(typeof(integrator.t)))) | ||
if W_transform | ||
for j in 1:length(u), i in 1:length(u) | ||
W[i,j] = mass_matrix[i,j]/dtgamma - J[i,j] | ||
end | ||
else | ||
for j in 1:length(u), i in 1:length(u) | ||
W[i,j] = mass_matrix[i,j] - dtgamma*J[i,j] | ||
end | ||
end | ||
W.transform = W_transform | ||
set_gamma!(W, dtgamma) | ||
else | ||
new_W = false | ||
end | ||
|
@@ -102,27 +246,42 @@ function calc_W!(integrator, cache::OrdinaryDiffEqMutableCache, dtgamma, repeat_ | |
end | ||
|
||
function calc_W!(integrator, cache::OrdinaryDiffEqConstantCache, dtgamma, repeat_step, W_transform=false) | ||
@unpack t,uprev,f = integrator | ||
@unpack t,uprev,p,f = integrator | ||
@unpack uf = cache | ||
mass_matrix = integrator.f.mass_matrix | ||
# calculate W | ||
uf.t = t | ||
isarray = typeof(uprev) <: AbstractArray | ||
iscompo = typeof(integrator.alg) <: CompositeAlgorithm | ||
if !W_transform | ||
if isarray | ||
J = ForwardDiff.jacobian(uf,uprev) | ||
W = I - dtgamma*J | ||
if DiffEqBase.has_jac(f) | ||
J = f.jac(uprev, p, t) | ||
if !isa(f, DiffEqBase.AbstractDiffEqLinearOperator) | ||
J = DiffEqArrayOperator(J) | ||
end | ||
W = WOperator(mass_matrix, dtgamma, J; transform=false) | ||
else | ||
J = ForwardDiff.derivative(uf,uprev) | ||
W = 1 - dtgamma*J | ||
if isarray | ||
J = ForwardDiff.jacobian(uf,uprev) | ||
else | ||
J = ForwardDiff.derivative(uf,uprev) | ||
end | ||
W = mass_matrix - dtgamma*J | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. out of place isn't using WOperator yet? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's used in the branch where There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not all of the time? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess it's not necessary all of the time, and if we don't allow a linsolve option on it then it's pointless for now. |
||
end | ||
else | ||
if isarray | ||
J = ForwardDiff.jacobian(uf,uprev) | ||
W = I*inv(dtgamma) - J | ||
if DiffEqBase.has_jac(f) | ||
J = f.jac(uprev, p, t) | ||
if !isa(f, DiffEqBase.AbstractDiffEqLinearOperator) | ||
J = DiffEqArrayOperator(J) | ||
end | ||
W = WOperator(mass_matrix, dtgamma, J; transform=true) | ||
else | ||
J = ForwardDiff.derivative(uf,uprev) | ||
W = inv(dtgamma) - J | ||
if isarray | ||
J = ForwardDiff.jacobian(uf,uprev) | ||
else | ||
J = ForwardDiff.derivative(uf,uprev) | ||
end | ||
W = mass_matrix*inv(dtgamma) - J | ||
end | ||
end | ||
iscompo && (integrator.eigen_est = isarray ? opnorm(J, Inf) : J) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
diffeqoperators are <: AbstractMatrix?