Skip to content

Commit 1e80944

Browse files
authored
Store linear invariants matrix in (Conservative)PDSProblem (#163)
* added lin invariants in PDSFunction and ConservativePDSFunction * added linear Invariants for linmod test problem, typo * add lin invariants to problem library * renamed lin_invariants to linear_invariants, corrected minmapk linear_invariants matrix * add tests for linear invariant matrices * typo * add @smatrix, typo * format * fix stefan * requested changes hendrik 'runtests' * described 'linear invariants' in docstring * revised docstring
1 parent afbdc41 commit 1e80944

3 files changed

Lines changed: 149 additions & 42 deletions

File tree

src/PDSProblemLibrary.jl

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ There is one independent linear invariant, e.g. ``u_1+u_2 = 1``.
3232
[DOI: 10.1016/S0168-9274(03)00101-6](https://doi.org/10.1016/S0168-9274(03)00101-6)
3333
"""
3434
prob_pds_linmod = ConservativePDSProblem(P_linmod, u0_linmod, (0.0, 2.0),
35-
analytic = f_linmod_analytic, std_rhs = f_linmod)
35+
analytic = f_linmod_analytic, std_rhs = f_linmod,
36+
linear_invariants = @SMatrix[1.0 1.0])
3637

3738
function P_linmod!(P, u, p, t)
3839
P .= P_linmod(u, p, t)
@@ -51,7 +52,8 @@ Same as [`prob_pds_linmod`](@ref) but with in-place computation.
5152
prob_pds_linmod_inplace = ConservativePDSProblem(P_linmod!, Array(u0_linmod),
5253
(0.0, 2.0),
5354
analytic = f_linmod_analytic,
54-
std_rhs = f_linmod!)
55+
std_rhs = f_linmod!,
56+
linear_invariants = @SMatrix[1.0 1.0])
5557

5658
# nonlinear model problem
5759
function P_nonlinmod(u, p, t)
@@ -86,7 +88,8 @@ There is one independent linear invariant, e.g. ``u_1+u_2+u_3 = 10.0``.
8688
[DOI: 10.1016/S0168-9274(03)00101-6](https://doi.org/10.1016/S0168-9274(03)00101-6)
8789
"""
8890
prob_pds_nonlinmod = ConservativePDSProblem(P_nonlinmod, u0_nonlinmod, (0.0, 30.0),
89-
std_rhs = f_nonlinmod)
91+
std_rhs = f_nonlinmod,
92+
linear_invariants = @SMatrix[1.0 1.0 1.0])
9093

9194
# robertson problem
9295
function P_robertson(u, p, t)
@@ -119,7 +122,8 @@ There is one independent linear invariant, e.g. ``u_1+u_2+u_3 = 1.0``.
119122
2nd Edition, Springer (2002): Section IV.1.
120123
"""
121124
prob_pds_robertson = ConservativePDSProblem(P_robertson, u0_robertson, (0.0, 1.0e11),
122-
std_rhs = f_robertson)
125+
std_rhs = f_robertson,
126+
linear_invariants = @SMatrix[1.0 1.0 1.0])
123127

124128
# brusselator problem
125129
function P_brusselator(u, p, t)
@@ -166,7 +170,9 @@ There are two independent linear invariants, e.g. ``u_1+u_4+u_5+u_6 = 10.2`` and
166170
[DOI: 10.1007/s10915-016-0267-9](https://doi.org/10.1007/s10915-016-0267-9)
167171
"""
168172
prob_pds_brusselator = ConservativePDSProblem(P_brusselator, u0_brusselator, (0.0, 10.0),
169-
std_rhs = f_brusselator)
173+
std_rhs = f_brusselator,
174+
linear_invariants = @SMatrix[1.0 0.0 0.0 1.0 1.0 1.0;
175+
0.0 1.0 1.0 0.0 0.0 0.0])
170176

171177
# SIR problem
172178
P_sir(u, p, t) = @SMatrix [0.0 0.0 0.0; 2*u[1]*u[2] 0.0 0.0; 0.0 u[2] 0.0]
@@ -195,7 +201,8 @@ There is one independent linear invariant, e.g. ``u_1+u_2+u_3 = 1.0``.
195201
Computers and Mathematics with Applications 66 (2013): 2307-2316.
196202
[DOI: 10.1016/j.camwa.2013.06.011](https://doi.org/10.1016/j.camwa.2013.06.011)
197203
"""
198-
prob_pds_sir = ConservativePDSProblem(P_sir, u0_sir, (0.0, 20.0), std_rhs = f_sir)
204+
prob_pds_sir = ConservativePDSProblem(P_sir, u0_sir, (0.0, 20.0), std_rhs = f_sir,
205+
linear_invariants = @SMatrix[1.0 1.0 1.0])
199206

200207
# bertolazzi problem
201208
function P_bertolazzi(u, p, t)
@@ -237,7 +244,8 @@ There is one independent linear invariant, e.g. ``u_1+u_2+u_3 = 3.0``.
237244
[DOI: 10.1016/0898-1221(96)00142-3](https://doi.org/10.1016/0898-1221(96)00142-3)
238245
"""
239246
prob_pds_bertolazzi = ConservativePDSProblem(P_bertolazzi, u0_bertolazzi, (0.0, 1.0),
240-
std_rhs = f_bertolazzi)
247+
std_rhs = f_bertolazzi,
248+
linear_invariants = @SMatrix[1.0 1.0 1.0])
241249

242250
# npzd problem
243251
function P_npzd(u, p, t)
@@ -288,7 +296,8 @@ There is one independent linear invariant, e.g. ``u_1+u_2+u_3+u_4 = 15.0``.
288296
Ocean Dynamics 55 (2005): 326-337.
289297
[DOI: 10.1007/s10236-005-0001-x](https://doi.org/10.1007/s10236-005-0001-x)
290298
"""
291-
prob_pds_npzd = ConservativePDSProblem(P_npzd, u0_npzd, (0.0, 10.0), std_rhs = f_npzd)
299+
prob_pds_npzd = ConservativePDSProblem(P_npzd, u0_npzd, (0.0, 10.0), std_rhs = f_npzd,
300+
linear_invariants = @SMatrix[1.0 1.0 1.0 1.0])
292301

293302
# stratospheric reaction problem
294303
function P_stratreac(u, p, t)
@@ -437,7 +446,9 @@ There are two independent linear invariants, e.g. ``u_1+u_2+3u_3+2u_4+u_5+2u_6=(
437446
[DOI: 10.2140/camcos.2021.16.155](https://doi.org/10.2140/camcos.2021.16.155)
438447
"""
439448
prob_pds_stratreac = PDSProblem(P_stratreac, d_stratreac, u0_stratreac, (4.32e4, 3.024e5),
440-
std_rhs = f_stratreac)
449+
std_rhs = f_stratreac,
450+
linear_invariants = @SMatrix[1.0 1.0 3.0 2.0 1.0 2.0;
451+
0.0 0.0 0.0 0.0 1.0 1.0])
441452

442453
# mapk problem
443454
function f_minmapk(u, p, t)
@@ -518,4 +529,6 @@ There are two independent linear invariants, e.g. ``u_1+u_4+u_6=1.75`` and ``u_2
518529
PLoS ONE 12 (2017): e0178457.
519530
[DOI: 10.1371/journal.pone.0178457](https://doi.org/10.1371/journal.pone.0178457)
520531
"""
521-
prob_pds_minmapk = PDSProblem(P_minmapk, D_minmapk, u0, tspan; std_rhs = f_minmapk)
532+
prob_pds_minmapk = PDSProblem(P_minmapk, D_minmapk, u0, tspan; std_rhs = f_minmapk,
533+
linear_invariants = @SMatrix[1.0 0.0 0.0 1.0 0.0 1.0;
534+
0.0 1.0 1.0 1.0 1.0 0.0])

src/proddest.jl

Lines changed: 59 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ abstract type AbstractPDSProblem end
55
PDSProblem(P, D, u0, tspan, p = NullParameters();
66
p_prototype = nothing,
77
analytic = nothing,
8-
std_rhs = nothing)
8+
std_rhs = nothing,
9+
linear_invariants = nothing)
910
1011
A structure describing a system of ordinary differential equations in form of a production-destruction system (PDS).
1112
`P` denotes the function defining the production matrix ``P``.
@@ -31,6 +32,9 @@ The functions `P` and `D` can be used either in the out-of-place form with signa
3132
the production-destruction representation of the ODE, will use this function
3233
instead to compute the solution. If not specified,
3334
a default implementation calling `P` and `D` is used.
35+
-`linear_invariants`: The rows of this matrix contain the linear invariants of the ODE.
36+
Certain solvers or callbacks require this matrix.
37+
Note that this feature is experimental and its API may change in future releases.
3438
3539
## References
3640
@@ -43,14 +47,15 @@ The functions `P` and `D` can be used either in the out-of-place form with signa
4347
struct PDSProblem{iip} <: AbstractPDSProblem end
4448

4549
# New ODE function PDSFunction
46-
struct PDSFunction{iip, specialize, P, D, PrototypeP, PrototypeD, StdRHS, Ta} <:
50+
struct PDSFunction{iip, specialize, P, D, PrototypeP, PrototypeD, StdRHS, Ta, LI} <:
4751
AbstractODEFunction{iip}
4852
p::P
4953
d::D
5054
p_prototype::PrototypeP
5155
d_prototype::PrototypeD
5256
std_rhs::StdRHS
5357
analytic::Ta
58+
linear_invariants::LI
5459
end
5560

5661
# define behavior of PDSFunctions for non-existing fields
@@ -73,8 +78,7 @@ function Base.getproperty(obj::PDSFunction, sym::Symbol)
7378
end
7479

7580
# Most general constructor for PDSProblems
76-
function PDSProblem(P, D, u0, tspan, p = NullParameters();
77-
kwargs...)
81+
function PDSProblem(P, D, u0, tspan, p = NullParameters(); kwargs...)
7882
Piip = isinplace(P, 4)
7983
Diip = isinplace(D, 4)
8084
if Piip == Diip
@@ -87,11 +91,16 @@ end
8791

8892
# Specialized constructor for PDSProblems setting `iip` manually
8993
# (arbitrary functions)
90-
function PDSProblem{iip}(P, D, u0, tspan, p = NullParameters();
94+
function PDSProblem{iip}(P,
95+
D,
96+
u0,
97+
tspan,
98+
p = NullParameters();
9199
p_prototype = nothing,
92100
analytic = nothing,
93101
std_rhs = nothing,
94-
kwargs...) where {iip}
102+
linear_invariants = nothing,
103+
kwargs...,) where {iip}
95104

96105
# p_prototype is used to store evaluations of P, if P is in-place.
97106
if isnothing(p_prototype) && iip
@@ -101,8 +110,8 @@ function PDSProblem{iip}(P, D, u0, tspan, p = NullParameters();
101110
# evaluations of D.
102111
d_prototype = similar(u0 ./ oneunit(first(tspan)))
103112

104-
PD = PDSFunction{iip}(P, D; p_prototype, d_prototype,
105-
analytic, std_rhs)
113+
PD = PDSFunction{iip}(P, D; p_prototype, d_prototype, analytic, std_rhs,
114+
linear_invariants)
106115
PDSProblem{iip}(PD, u0, tspan, p; kwargs...)
107116
end
108117

@@ -119,18 +128,26 @@ function PDSFunction{iip}(P, D; kwargs...) where {iip}
119128
end
120129

121130
# Most specific constructor for PDSFunction
122-
function PDSFunction{iip, FullSpecialize}(P, D;
131+
function PDSFunction{iip, FullSpecialize}(P,
132+
D;
123133
p_prototype = nothing,
124134
d_prototype = nothing,
125135
analytic = nothing,
126-
std_rhs = nothing) where {iip}
136+
std_rhs = nothing,
137+
linear_invariants = nothing,) where {iip}
127138
if std_rhs === nothing
128139
std_rhs = PDSStdRHS(P, D, p_prototype, d_prototype)
129140
end
130-
PDSFunction{iip, FullSpecialize, typeof(P), typeof(D), typeof(p_prototype),
141+
PDSFunction{iip,
142+
FullSpecialize,
143+
typeof(P),
144+
typeof(D),
145+
typeof(p_prototype),
131146
typeof(d_prototype),
132-
typeof(std_rhs), typeof(analytic)}(P, D, p_prototype, d_prototype, std_rhs,
133-
analytic)
147+
typeof(std_rhs),
148+
typeof(analytic),
149+
typeof(linear_invariants)}(P, D, p_prototype, d_prototype, std_rhs,
150+
analytic, linear_invariants)
134151
end
135152

136153
# Evaluation of a PDSFunction
@@ -165,8 +182,7 @@ end
165182
function (PD::PDSStdRHS)(u, p, t)
166183
P = PD.p(u, p, t)
167184
D = PD.d(u, p, t)
168-
diag(P) + vec(sum(P, dims = 2)) -
169-
vec(sum(P, dims = 1)) - vec(D)
185+
diag(P) + vec(sum(P; dims = 2)) - vec(sum(P; dims = 1)) - vec(D)
170186
end
171187

172188
# Evaluation of a PDSStdRHS (in-place)
@@ -203,7 +219,8 @@ end
203219
ConservativePDSProblem(P, u0, tspan, p = NullParameters();
204220
p_prototype = nothing,
205221
analytic = nothing,
206-
std_rhs = nothing)
222+
std_rhs = nothing,
223+
linear_invariants = nothing)
207224
208225
A structure describing a conservative system of ordinary differential equation in form of a production-destruction system (PDS).
209226
`P` denotes the function defining the production matrix ``P``.
@@ -227,7 +244,10 @@ The function `P` can be given either in the out-of-place form with signature
227244
as `std_rhs(du, u, p, t)` for the in-place form. Solvers that do not rely on
228245
the production-destruction representation of the ODE, will use this function
229246
instead to compute the solution. If not specified,
230-
a default implementation calling `P` is used.
247+
a default implementation calling `P` is used
248+
-`linear_invariants`: The rows of this matrix contain the linear invariants of the ODE.
249+
Certain solvers or callbacks require this matrix.
250+
Note that this feature is experimental and its API may change in future releases.
231251
232252
## References
233253
@@ -240,12 +260,13 @@ The function `P` can be given either in the out-of-place form with signature
240260
struct ConservativePDSProblem{iip} <: AbstractPDSProblem end
241261

242262
# New ODE function ConservativePDSFunction
243-
struct ConservativePDSFunction{iip, specialize, P, PrototypeP, StdRHS, Ta} <:
263+
struct ConservativePDSFunction{iip, specialize, P, PrototypeP, StdRHS, Ta, LI} <:
244264
AbstractODEFunction{iip}
245265
p::P # production terms
246266
p_prototype::PrototypeP # prototype for production terms
247267
std_rhs::StdRHS # standard right-hand side evaluation function
248268
analytic::Ta # analytic solution (or nothing)
269+
linear_invariants::LI
249270
end
250271

251272
# define behavior of ConservativePDSFunction for non-existing fields
@@ -268,26 +289,29 @@ function Base.getproperty(obj::ConservativePDSFunction, sym::Symbol)
268289
end
269290

270291
# Most general constructor for ConservativePDSProblems
271-
function ConservativePDSProblem(P, u0, tspan, p = NullParameters();
272-
kwargs...)
292+
function ConservativePDSProblem(P, u0, tspan, p = NullParameters(); kwargs...)
273293
iip = isinplace(P, 4)
274294
return ConservativePDSProblem{iip}(P, u0, tspan, p; kwargs...)
275295
end
276296

277297
# Specialized constructor for ConservativePDSProblems setting `iip` manually
278-
# (arbitrary function)
279-
function ConservativePDSProblem{iip}(P, u0, tspan, p = NullParameters();
298+
# (arbitrary function)
299+
function ConservativePDSProblem{iip}(P,
300+
u0,
301+
tspan,
302+
p = NullParameters();
280303
p_prototype = nothing,
281304
analytic = nothing,
282305
std_rhs = nothing,
283-
kwargs...) where {iip}
306+
linear_invariants = nothing,
307+
kwargs...,) where {iip}
284308

285309
# p_prototype is used to store evaluations of P, if P is in-place.
286310
if isnothing(p_prototype) && iip
287311
p_prototype = zeros(eltype(u0), (length(u0), length(u0))) / oneunit(first(tspan))
288312
end
289313

290-
PD = ConservativePDSFunction{iip}(P; p_prototype, analytic, std_rhs)
314+
PD = ConservativePDSFunction{iip}(P; p_prototype, analytic, std_rhs, linear_invariants)
291315
ConservativePDSProblem{iip}(PD, u0, tspan, p; kwargs...)
292316
end
293317

@@ -304,16 +328,20 @@ function ConservativePDSFunction{iip}(P; kwargs...) where {iip}
304328
end
305329

306330
# Most specific constructor for ConservativePDSFunction
307-
function ConservativePDSFunction{iip, FullSpecialize}(P;
308-
p_prototype = nothing,
309-
analytic = nothing,
310-
std_rhs = nothing) where {iip}
331+
function ConservativePDSFunction{iip, FullSpecialize}(P; p_prototype = nothing,
332+
analytic = nothing, std_rhs = nothing,
333+
linear_invariants = nothing) where {iip}
311334
if std_rhs === nothing
312335
std_rhs = ConservativePDSStdRHS(P, p_prototype)
313336
end
314-
ConservativePDSFunction{iip, FullSpecialize, typeof(P), typeof(p_prototype),
315-
typeof(std_rhs), typeof(analytic)}(P, p_prototype, std_rhs,
316-
analytic)
337+
ConservativePDSFunction{iip,
338+
FullSpecialize,
339+
typeof(P),
340+
typeof(p_prototype),
341+
typeof(std_rhs),
342+
typeof(analytic),
343+
typeof(linear_invariants)}(P, p_prototype, std_rhs, analytic,
344+
linear_invariants)
317345
end
318346

319347
# Evaluation of a ConservativePDSFunction

test/runtests.jl

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ using SparseArrays
44
using Statistics: mean, median
55

66
using DoubleFloats: Double64
7-
using StaticArrays: SMatrix, MVector, @SVector, SVector, SA
7+
using StaticArrays: @SMatrix, SMatrix, MVector, @SVector, SVector, SA
88

99
using Unitful: @u_str, ustrip
1010

@@ -2680,4 +2680,70 @@ end
26802680
end
26812681
end
26822682
end
2683+
@testset "linear invariant matrices" begin
2684+
@testset "Check that the structure of the linear invariant field is well implemented" begin
2685+
u0 = [0.9, 0.1]
2686+
tspan = (0.0, 2.0)
2687+
AT = @SMatrix[1 1]
2688+
2689+
linmodP(u, p, t) = [0 u[2]; 5*u[1] 0]
2690+
linmodD(u, p, t) = [0.0; 0.0]
2691+
linmod_PDS_op = PDSProblem(linmodP, linmodD, u0, tspan, linear_invariants = AT)
2692+
2693+
linmod_PDS_op_cons = ConservativePDSProblem(linmodP, linmodD, u0, tspan,
2694+
linear_invariants = AT)
2695+
2696+
@test linmod_PDS_op.f.linear_invariants == AT
2697+
@test linmod_PDS_op_cons.f.linear_invariants == AT
2698+
end
2699+
2700+
@testset "Check that the predefined linear invariant matrices fit in predefined problems" begin
2701+
# non-stiff conservative problems (out-of-place)
2702+
probs = (prob_pds_linmod, prob_pds_nonlinmod, prob_pds_brusselator,
2703+
prob_pds_sir, prob_pds_npzd)
2704+
@testset "$prob" for prob in probs
2705+
dt = (last(prob.tspan) - first(prob.tspan)) / 1e4
2706+
sol = solve(prob, Tsit5(); dt, isoutofdomain = isnegative)
2707+
@test prob.f.linear_invariants * prob.u0
2708+
prob.f.linear_invariants * sol.u[end]
2709+
end
2710+
2711+
# non-stiff conservative problems (in-place)
2712+
probs = (prob_pds_linmod_inplace,)
2713+
@testset "$prob" for prob in probs
2714+
dt = (last(prob.tspan) - first(prob.tspan)) / 1e4
2715+
sol = solve(prob, Tsit5(); dt, isoutofdomain = isnegative)
2716+
@test prob.f.linear_invariants * prob.u0
2717+
prob.f.linear_invariants * sol.u[end]
2718+
end
2719+
2720+
# non-stiff non-conservative problems (out-of-place)
2721+
probs = (prob_pds_minmapk,)
2722+
@testset "$prob" for prob in probs
2723+
dt = (last(prob.tspan) - first(prob.tspan)) / 1e4
2724+
sol = solve(prob, Tsit5(); dt, isoutofdomain = isnegative)
2725+
@test prob.f.linear_invariants * prob.u0
2726+
prob.f.linear_invariants * sol.u[end]
2727+
end
2728+
2729+
# Robertson problem
2730+
prob = prob_pds_robertson
2731+
dt = 1e-6
2732+
sol = solve(prob, Rosenbrock23(); dt)
2733+
@test prob.f.linear_invariants * prob.u0 prob.f.linear_invariants * sol.u[end]
2734+
2735+
# Bertolazzi problem
2736+
prob = prob_pds_bertolazzi
2737+
alg = ImplicitEuler()
2738+
dt = 1e-3
2739+
sol = solve(prob, alg; dt)
2740+
@test prob.f.linear_invariants * prob.u0 prob.f.linear_invariants * sol.u[end]
2741+
2742+
# Stratospheric reaction problem
2743+
prob = prob_pds_stratreac
2744+
dt = 1.0
2745+
sol = solve(prob, Rosenbrock23(); dt)
2746+
@test prob.f.linear_invariants * prob.u0 prob.f.linear_invariants * sol.u[end]
2747+
end
2748+
end
26832749
end;

0 commit comments

Comments
 (0)