Skip to content

Commit 3893ddc

Browse files
authored
Merge pull request #955 from SciML/conservation_laws_warning
[v14] Conservation law warnings
2 parents a8a2404 + 68e72b3 commit 3893ddc

File tree

6 files changed

+98
-35
lines changed

6 files changed

+98
-35
lines changed

ext/CatalystBifurcationKitExtension/bifurcation_kit_extension.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ function BK.BifurcationProblem(rs::ReactionSystem, u0_bif, ps, bif_par, args...;
2222

2323
# Creates NonlinearSystem.
2424
Catalyst.conservationlaw_errorcheck(rs, vcat(ps, u0))
25-
nsys = convert(NonlinearSystem, rs; remove_conserved = true, defaults = Dict(u0))
25+
nsys = convert(NonlinearSystem, rs; defaults = Dict(u0),
26+
remove_conserved = true, remove_conserved_warn = false)
2627
nsys = complete(nsys)
2728

2829
# Makes BifurcationProblem (this call goes through the ModelingToolkit-based BifurcationKit extension).

ext/CatalystHomotopyContinuationExtension/homotopy_continuation_extension.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ end
4949
# For a given reaction system, parameter values, and initial conditions, find the polynomial that HC solves to find steady states.
5050
function steady_state_polynomial(rs::ReactionSystem, ps, u0)
5151
rs = Catalyst.expand_registered_functions(rs)
52-
ns = complete(convert(NonlinearSystem, rs; remove_conserved = true))
52+
ns = complete(convert(NonlinearSystem, rs;
53+
remove_conserved = true, remove_conserved_warn = false))
5354
pre_varmap = [symmap_to_varmap(rs, u0)..., symmap_to_varmap(rs, ps)...]
5455
Catalyst.conservationlaw_errorcheck(rs, pre_varmap)
5556
p_vals = ModelingToolkit.varmap_to_vars(pre_varmap, parameters(ns);

ext/CatalystStructuralIdentifiabilityExtension/structural_identifiability_extension.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ function make_osys(rs::ReactionSystem; remove_conserved = true)
167167
error("Identifiability should only be computed for complete systems. A ReactionSystem can be marked as complete using the `complete` function.")
168168
end
169169
rs = complete(Catalyst.expand_registered_functions(flatten(rs)))
170-
osys = complete(convert(ODESystem, rs; remove_conserved))
170+
osys = complete(convert(ODESystem, rs; remove_conserved, remove_conserved_warn = false))
171171
vars = [unknowns(rs); parameters(rs)]
172172

173173
# Computes equations for system conservation laws.

src/reactionsystem_conversions.jl

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,22 @@ diff_2_zero(expr) = (Symbolics.is_derivative(expr) ? 0 : expr)
450450

451451
COMPLETENESS_ERROR = "A ReactionSystem must be complete before it can be converted to other system types. A ReactionSystem can be marked as complete using the `complete` function."
452452

453+
# Used to, when required, display a warning about conservation law removeal and remake.
454+
function check_cons_warning(remove_conserved, remove_conserved_warn)
455+
(remove_conserved && remove_conserved_warn) || return
456+
@warn "You are creating a system or problem while eliminating conserved quantities. Please note,
457+
due to limitations / design choices in ModelingToolkit if you use the created system to
458+
create a problem (e.g. an `ODEProblem`), or are directly creating a problem, you *should not*
459+
modify that problem's initial conditions for species (e.g. using `remake`). Changing initial
460+
conditions must be done by creating a new Problem from your reaction system or the
461+
ModelingToolkit system you converted it into with the new initial condition map.
462+
Modification of parameter values is still possible, *except* for the modification of any
463+
conservation law constants ($CONSERVED_CONSTANT_SYMBOL), which is not possible. You might
464+
get this warning when creating a problem directly.
465+
466+
You can remove this warning by setting `remove_conserved_warn = false`."
467+
end
468+
453469
### System Conversions ###
454470

455471
"""
@@ -467,15 +483,20 @@ Keyword args and default values:
467483
- `remove_conserved=false`, if set to `true` will calculate conservation laws of the
468484
underlying set of reactions (ignoring constraint equations), and then apply them to reduce
469485
the number of equations.
486+
- `remove_conserved_warn = true`: If `true`, if also `remove_conserved = true`, there will be
487+
a warning regarding limitations of modifying problems generated from the created system.
470488
"""
471489
function Base.convert(::Type{<:ODESystem}, rs::ReactionSystem; name = nameof(rs),
472490
combinatoric_ratelaws = get_combinatoric_ratelaws(rs),
473-
include_zero_odes = true, remove_conserved = false, checks = false,
474-
default_u0 = Dict(), default_p = Dict(),
491+
include_zero_odes = true, remove_conserved = false, remove_conserved_warn = true,
492+
checks = false, default_u0 = Dict(), default_p = Dict(),
475493
defaults = _merge(Dict(default_u0), Dict(default_p)),
476494
kwargs...)
495+
# Error checks.
477496
iscomplete(rs) || error(COMPLETENESS_ERROR)
478497
spatial_convert_err(rs::ReactionSystem, ODESystem)
498+
check_cons_warning(remove_conserved, remove_conserved_warn)
499+
479500
fullrs = Catalyst.flatten(rs)
480501
remove_conserved && conservationlaws(fullrs)
481502
ists, ispcs = get_indep_sts(fullrs, remove_conserved)
@@ -509,16 +530,19 @@ Keyword args and default values:
509530
- `remove_conserved=false`, if set to `true` will calculate conservation laws of the
510531
underlying set of reactions (ignoring constraint equations), and then apply them to reduce
511532
the number of equations.
533+
- `remove_conserved_warn = true`: If `true`, if also `remove_conserved = true`, there will be
534+
a warning regarding limitations of modifying problems generated from the created system.
512535
"""
513536
function Base.convert(::Type{<:NonlinearSystem}, rs::ReactionSystem; name = nameof(rs),
514537
combinatoric_ratelaws = get_combinatoric_ratelaws(rs),
515538
include_zero_odes = true, remove_conserved = false, checks = false,
516-
default_u0 = Dict(), default_p = Dict(),
539+
remove_conserved_warn = true, default_u0 = Dict(), default_p = Dict(),
517540
defaults = _merge(Dict(default_u0), Dict(default_p)),
518541
all_differentials_permitted = false, kwargs...)
519542
# Error checks.
520543
iscomplete(rs) || error(COMPLETENESS_ERROR)
521544
spatial_convert_err(rs::ReactionSystem, NonlinearSystem)
545+
check_cons_warning(remove_conserved, remove_conserved_warn)
522546
if !isautonomous(rs)
523547
error("Attempting to convert a non-autonomous `ReactionSystem` (e.g. where some rate depend on $(get_iv(rs))) to a `NonlinearSystem`. This is not possible. if you are intending to compute system steady states, consider creating and solving a `SteadyStateProblem.")
524548
end
@@ -589,17 +613,19 @@ Notes:
589613
- `remove_conserved=false`, if set to `true` will calculate conservation laws of the
590614
underlying set of reactions (ignoring constraint equations), and then apply them to reduce
591615
the number of equations.
592-
- Does not currently support `ReactionSystem`s that include coupled algebraic or
593-
differential equations.
616+
- `remove_conserved_warn = true`: If `true`, if also `remove_conserved = true`, there will be
617+
a warning regarding limitations of modifying problems generated from the created system.
594618
"""
595619
function Base.convert(::Type{<:SDESystem}, rs::ReactionSystem;
596620
name = nameof(rs), combinatoric_ratelaws = get_combinatoric_ratelaws(rs),
597621
include_zero_odes = true, checks = false, remove_conserved = false,
598-
default_u0 = Dict(), default_p = Dict(),
622+
remove_conserved_warn = true, default_u0 = Dict(), default_p = Dict(),
599623
defaults = _merge(Dict(default_u0), Dict(default_p)),
600624
kwargs...)
625+
# Error checks.
601626
iscomplete(rs) || error(COMPLETENESS_ERROR)
602627
spatial_convert_err(rs::ReactionSystem, SDESystem)
628+
check_cons_warning(remove_conserved, remove_conserved_warn)
603629

604630
flatrs = Catalyst.flatten(rs)
605631

@@ -651,7 +677,6 @@ function Base.convert(::Type{<:JumpSystem}, rs::ReactionSystem; name = nameof(rs
651677
kwargs...)
652678
iscomplete(rs) || error(COMPLETENESS_ERROR)
653679
spatial_convert_err(rs::ReactionSystem, JumpSystem)
654-
655680
(remove_conserved !== nothing) &&
656681
throw(ArgumentError("Catalyst does not support removing conserved species when converting to JumpSystems."))
657682

@@ -684,12 +709,12 @@ function DiffEqBase.ODEProblem(rs::ReactionSystem, u0, tspan,
684709
p = DiffEqBase.NullParameters(), args...;
685710
check_length = false, name = nameof(rs),
686711
combinatoric_ratelaws = get_combinatoric_ratelaws(rs),
687-
include_zero_odes = true, remove_conserved = false,
712+
include_zero_odes = true, remove_conserved = false, remove_conserved_warn = true,
688713
checks = false, structural_simplify = false, kwargs...)
689714
u0map = symmap_to_varmap(rs, u0)
690715
pmap = symmap_to_varmap(rs, p)
691716
osys = convert(ODESystem, rs; name, combinatoric_ratelaws, include_zero_odes, checks,
692-
remove_conserved)
717+
remove_conserved, remove_conserved_warn)
693718

694719
# Handles potential differential algebraic equations (which requires `structural_simplify`).
695720
if structural_simplify
@@ -708,12 +733,12 @@ function DiffEqBase.NonlinearProblem(rs::ReactionSystem, u0,
708733
p = DiffEqBase.NullParameters(), args...;
709734
name = nameof(rs), include_zero_odes = true,
710735
combinatoric_ratelaws = get_combinatoric_ratelaws(rs),
711-
remove_conserved = false, checks = false,
736+
remove_conserved = false, remove_conserved_warn = true, checks = false,
712737
check_length = false, all_differentials_permitted = false, kwargs...)
713738
u0map = symmap_to_varmap(rs, u0)
714739
pmap = symmap_to_varmap(rs, p)
715740
nlsys = convert(NonlinearSystem, rs; name, combinatoric_ratelaws, include_zero_odes,
716-
checks, all_differentials_permitted, remove_conserved)
741+
checks, all_differentials_permitted, remove_conserved, remove_conserved_warn)
717742
nlsys = complete(nlsys)
718743
return NonlinearProblem(nlsys, u0map, pmap, args...; check_length,
719744
kwargs...)
@@ -723,12 +748,12 @@ end
723748
function DiffEqBase.SDEProblem(rs::ReactionSystem, u0, tspan,
724749
p = DiffEqBase.NullParameters(), args...;
725750
name = nameof(rs), combinatoric_ratelaws = get_combinatoric_ratelaws(rs),
726-
include_zero_odes = true, checks = false, check_length = false,
727-
remove_conserved = false, structural_simplify = false, kwargs...)
751+
include_zero_odes = true, checks = false, check_length = false, remove_conserved = false,
752+
remove_conserved_warn = true, structural_simplify = false, kwargs...)
728753
u0map = symmap_to_varmap(rs, u0)
729754
pmap = symmap_to_varmap(rs, p)
730755
sde_sys = convert(SDESystem, rs; name, combinatoric_ratelaws,
731-
include_zero_odes, checks, remove_conserved)
756+
include_zero_odes, checks, remove_conserved, remove_conserved_warn)
732757

733758
# Handles potential differential algebraic equations (which requires `structural_simplify`).
734759
if structural_simplify
@@ -772,12 +797,12 @@ function DiffEqBase.SteadyStateProblem(rs::ReactionSystem, u0,
772797
p = DiffEqBase.NullParameters(), args...;
773798
check_length = false, name = nameof(rs),
774799
combinatoric_ratelaws = get_combinatoric_ratelaws(rs),
775-
remove_conserved = false, include_zero_odes = true,
800+
remove_conserved = false, remove_conserved_warn = true, include_zero_odes = true,
776801
checks = false, structural_simplify = false, kwargs...)
777802
u0map = symmap_to_varmap(rs, u0)
778803
pmap = symmap_to_varmap(rs, p)
779804
osys = convert(ODESystem, rs; name, combinatoric_ratelaws, include_zero_odes, checks,
780-
remove_conserved)
805+
remove_conserved, remove_conserved_warn)
781806

782807
# Handles potential differential algebraic equations (which requires `structural_simplify`).
783808
if structural_simplify

src/steady_state_stability.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ function steady_state_jac(rs::ReactionSystem; u0 = [sp => 0.0 for sp in unknowns
117117

118118
# Creates an `ODEProblem` with a Jacobian. Dummy values for `u0` and `ps` must be provided.
119119
ps = [p => 0.0 for p in parameters(rs)]
120-
return ODEProblem(rs, u0, 0, ps; jac = true, remove_conserved = true,
121-
combinatoric_ratelaws = combinatoric_ratelaws)
120+
return ODEProblem(rs, u0, 0, ps; jac = true, combinatoric_ratelaws,
121+
remove_conserved = true, remove_conserved_warn = false)
122122
end
123123

124124
# Converts a `u` vector from a vector of values to a map.

test/network_analysis/conservation_laws.jl

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ seed = rand(rng, 1:100)
1111
# Fetch test networks.
1212
include("../test_networks.jl")
1313

14+
# Except where we test the warnings, we do not want to print this warning.
15+
remove_conserved_warn = false
16+
1417
### Basic Tests ###
1518

1619
# Tests basic functionality on system with known conservation laws.
@@ -114,23 +117,23 @@ let
114117
tspan = (0.0, 20.0)
115118

116119
# Simulates model using ODEs and checks that simulations are identical.
117-
osys = complete(convert(ODESystem, rn; remove_conserved = true))
120+
osys = complete(convert(ODESystem, rn; remove_conserved = true, remove_conserved_warn))
118121
oprob1 = ODEProblem(osys, u0, tspan, p)
119122
oprob2 = ODEProblem(rn, u0, tspan, p)
120-
oprob3 = ODEProblem(rn, u0, tspan, p; remove_conserved = true)
123+
oprob3 = ODEProblem(rn, u0, tspan, p; remove_conserved = true, remove_conserved_warn)
121124
osol1 = solve(oprob1, Tsit5(); abstol = 1e-8, reltol = 1e-8, saveat= 0.2)
122125
osol2 = solve(oprob2, Tsit5(); abstol = 1e-8, reltol = 1e-8, saveat= 0.2)
123126
osol3 = solve(oprob3, Tsit5(); abstol = 1e-8, reltol = 1e-8, saveat= 0.2)
124127
@test osol1[sps] osol2[sps] osol3[sps]
125128

126129
# Checks that steady states found using nonlinear solving and steady state simulations are identical.
127-
nsys = complete(convert(NonlinearSystem, rn; remove_conserved = true))
130+
nsys = complete(convert(NonlinearSystem, rn; remove_conserved = true, remove_conserved_warn))
128131
nprob1 = NonlinearProblem{true}(nsys, u0, p)
129132
nprob2 = NonlinearProblem(rn, u0, p)
130-
nprob3 = NonlinearProblem(rn, u0, p; remove_conserved = true)
133+
nprob3 = NonlinearProblem(rn, u0, p; remove_conserved = true, remove_conserved_warn)
131134
ssprob1 = SteadyStateProblem{true}(osys, u0, p)
132135
ssprob2 = SteadyStateProblem(rn, u0, p)
133-
ssprob3 = SteadyStateProblem(rn, u0, p; remove_conserved = true)
136+
ssprob3 = SteadyStateProblem(rn, u0, p; remove_conserved = true, remove_conserved_warn)
134137
nsol1 = solve(nprob1, NewtonRaphson(); abstol = 1e-8)
135138
# Nonlinear problems cannot find steady states properly without removing conserved species.
136139
nsol3 = solve(nprob3, NewtonRaphson(); abstol = 1e-8)
@@ -142,10 +145,10 @@ let
142145
# Creates SDEProblems using various approaches.
143146
u0_sde = [A => 100.0, B => 20.0, C => 5.0, D => 10.0, E => 3.0, F1 => 8.0, F2 => 2.0,
144147
F3 => 20.0]
145-
ssys = complete(convert(SDESystem, rn; remove_conserved = true))
148+
ssys = complete(convert(SDESystem, rn; remove_conserved = true, remove_conserved_warn))
146149
sprob1 = SDEProblem(ssys, u0_sde, tspan, p)
147150
sprob2 = SDEProblem(rn, u0_sde, tspan, p)
148-
sprob3 = SDEProblem(rn, u0_sde, tspan, p; remove_conserved = true)
151+
sprob3 = SDEProblem(rn, u0_sde, tspan, p; remove_conserved = true, remove_conserved_warn)
149152

150153
# Checks that the SDEs f and g function evaluates to the same thing.
151154
ind_us = ModelingToolkit.get_unknowns(ssys)
@@ -186,9 +189,9 @@ let
186189
u0_2 = [rn.X => 2.0, rn.Y => 3.0, rn.XY => 4.0]
187190
u0_3 = [:X => 2.0, :Y => 3.0, :XY => 4.0]
188191
ps = (kB => 2, kD => 1.5)
189-
oprob1 = ODEProblem(rn, u0_1, 10.0, ps; remove_conserved = true)
190-
oprob2 = ODEProblem(rn, u0_2, 10.0, ps; remove_conserved = true)
191-
oprob3 = ODEProblem(rn, u0_3, 10.0, ps; remove_conserved = true)
192+
oprob1 = ODEProblem(rn, u0_1, 10.0, ps; remove_conserved = true, remove_conserved_warn)
193+
oprob2 = ODEProblem(rn, u0_2, 10.0, ps; remove_conserved = true, remove_conserved_warn)
194+
oprob3 = ODEProblem(rn, u0_3, 10.0, ps; remove_conserved = true, remove_conserved_warn)
192195
@test solve(oprob1)[sps] solve(oprob2)[sps] solve(oprob3)[sps]
193196
end
194197

@@ -200,7 +203,7 @@ let
200203
end
201204
u0 = Dict([:X1 => 100.0, :X2 => 120.0])
202205
ps = [:k1 => 0.2, :k2 => 0.15]
203-
sprob = SDEProblem(rn, u0, 10.0, ps; remove_conserved = true)
206+
sprob = SDEProblem(rn, u0, 10.0, ps; remove_conserved = true, remove_conserved_warn)
204207

205208
# Checks that conservation laws hold in all simulations.
206209
sol = solve(sprob, ImplicitEM(); seed)
@@ -213,7 +216,7 @@ let
213216
rn = @reaction_network begin
214217
(k1,k2), X1 <--> X2
215218
end
216-
osys = complete(convert(ODESystem, rn; remove_conserved = true))
219+
osys = complete(convert(ODESystem, rn; remove_conserved = true, remove_conserved_warn))
217220
u0 = [osys.X1 => 1.0, osys.X2 => 1.0]
218221
ps_1 = [osys.k1 => 2.0, osys.k2 => 3.0]
219222
ps_2 = [osys.k1 => 2.0, osys.k2 => 3.0, osys.Γ[1] => 4.0]
@@ -234,7 +237,7 @@ let
234237
rn = @reaction_network begin
235238
(k1,k2), X1 <--> X2
236239
end
237-
@test_throws ArgumentError convert(JumpSystem, rn; remove_conserved = true)
240+
@test_throws ArgumentError convert(JumpSystem, rn; remove_conserved = true, remove_conserved_warn)
238241
end
239242

240243
# Checks that `conserved` metadata is added correctly to parameters.
@@ -245,11 +248,44 @@ let
245248
(k1,k2), X1 <--> X2
246249
(k1,k2), Y1 <--> Y2
247250
end
248-
osys = convert(ODESystem, rs; remove_conserved = true)
251+
osys = convert(ODESystem, rs; remove_conserved = true, remove_conserved_warn)
249252

250253
# Checks that the correct parameters have the `conserved` metadata.
251254
@test Catalyst.isconserved(osys.Γ[1])
252255
@test Catalyst.isconserved(osys.Γ[2])
253256
@test !Catalyst.isconserved(osys.k1)
254257
@test !Catalyst.isconserved(osys.k2)
255258
end
259+
260+
# Checks that conservation law elimination warnings are generated in the correct cases.
261+
let
262+
# Prepare model.
263+
rn = @reaction_network begin
264+
(k1,k2), X1 <--> X2
265+
end
266+
u0 = [:X1 => 1.0, :X2 => 2.0]
267+
tspan = (0.0, 1.0)
268+
ps = [:k1 => 3.0, :k2 => 4.0]
269+
270+
# Check warnings in system conversion.
271+
for XSystem in [ODESystem, SDESystem, NonlinearSystem]
272+
@test_nowarn convert(XSystem, rn)
273+
@test_logs (:warn, r"You are creating a system or problem while eliminating conserved quantities. Please *") convert(XSystem, rn; remove_conserved = true)
274+
@test_nowarn convert(XSystem, rn; remove_conserved_warn = false)
275+
@test_nowarn convert(XSystem, rn; remove_conserved = true, remove_conserved_warn = false)
276+
end
277+
278+
# Checks during problem creation (separate depending on whether they have a time span or not).
279+
for XProblem in [ODEProblem, SDEProblem]
280+
@test_nowarn XProblem(rn, u0, tspan, ps)
281+
@test_logs (:warn, r"You are creating a system or problem while eliminating conserved quantities. Please *") XProblem(rn, u0, tspan, ps; remove_conserved = true)
282+
@test_nowarn XProblem(rn, u0, tspan, ps; remove_conserved_warn = false)
283+
@test_nowarn XProblem(rn, u0, tspan, ps; remove_conserved = true, remove_conserved_warn = false)
284+
end
285+
for XProblem in [NonlinearProblem, SteadyStateProblem]
286+
@test_nowarn XProblem(rn, u0, ps)
287+
@test_logs (:warn, r"You are creating a system or problem while eliminating conserved quantities. Please *") XProblem(rn, u0, ps; remove_conserved = true)
288+
@test_nowarn XProblem(rn, u0, ps; remove_conserved_warn = false)
289+
@test_nowarn XProblem(rn, u0, ps; remove_conserved = true, remove_conserved_warn = false)
290+
end
291+
end

0 commit comments

Comments
 (0)