Skip to content

Commit cb8aebb

Browse files
authored
Check for wrong Equation type in RHS. (#11)
* Check for wrong `Equation` type in RHS. * oops use `isa` * use new ci file which will hopefully work? * simlper ode test dependency * change ODE to Tsit4 * only check RHS when keyword true * remove obsolete arguments from mtkeqs function * improve central docstring * wrap all tests in a testset * module outside tests
1 parent e1a9ac6 commit cb8aebb

File tree

6 files changed

+58
-20
lines changed

6 files changed

+58
-20
lines changed

.github/workflows/ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
with:
3535
version: ${{ matrix.version }}
3636
arch: ${{ matrix.arch }}
37-
- uses: actions/cache@v1
37+
- uses: actions/cache@v4
3838
env:
3939
cache-name: cache-artifacts
4040
with:

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
ProcessBasedModelling.jl follows semver 2.0.
44
Changelog is kept with respect to v1 release.
55

6+
## 1.6
7+
8+
- Added an additional step when constructing the raw equation vector to be passed into an MTK model. In this step it is also checked that the RHS for all equations is an `Expression`. Sometimes it is easy to get confused and mess up and make it be an `Equation` (i.e., assigning the LHS-variable twice). This now will give an informative error.
9+
610
## 1.5
711

812
- Add docstring to `processes_to_mtkeqs` and list it in the documentation.

Project.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "ProcessBasedModelling"
22
uuid = "ca969041-2cf3-4b10-bc21-86f4417093eb"
33
authors = ["Datseris <datseris.george@gmail.com>"]
4-
version = "1.5.0"
4+
version = "1.6.0"
55

66
[deps]
77
ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78"

src/make.jl

+25-8
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,22 @@ passed to the MTK model/system like `ODESystem`.
88
99
During construction, the following automations improve user experience:
1010
11-
- Variable(s) introduced in `processes` that does not itself have a process obtain
11+
- Variable(s) introduced in `processes` that does not themselves have a process obtain
1212
a default process from `default`.
13-
- If no default exists, but the variable(s) itself has a default numerical value,
14-
a [`ParameterProcess`](@ref) is created for said variable and a warning is thrown.
13+
- If no default exists, but the variable(s) have a default numerical value,
14+
a [`ParameterProcess`](@ref) is created for said variable(s) and a warning is thrown.
1515
- Else, an informative error is thrown.
1616
- An error is also thrown if any variable has two or more processes assigned to it.
1717
1818
`processes` is a `Vector` whose elements can be:
1919
20-
1. Any instance of a subtype of [`Process`](@ref). `Process` is a
20+
1. Any instance of a subtype of [`Process`](@ref). `Process` is like a
2121
wrapper around `Equation` that provides some conveniences, e.g., handling of timescales
2222
or not having limitations on the left-hand-side (LHS) form.
2323
1. An `Equation`. The LHS format of the equation is limited.
2424
Let `x` be a `@variable` and `p` be a `@parameter`. Then, the LHS can only be one of:
25-
`x`, `Differential(t)(x)`, `Differential(t)(x)*p`, `p*Differential(t)(x)`,
26-
however, the versions with `p` may fail unexpectedly. Anything else will error.
25+
`x`, `Differential(t)(x)`, `Differential(t)(x)*p`, or `p*Differential(t)(x)`.
26+
The versions with `p` may fail unexpectedly. Anything else will error.
2727
2. A `Vector` of the above two, which is then expanded. This allows the convenience of
2828
functions representing a physical process that may require many equations to be defined
2929
(because e.g., they may introduce more variables).
@@ -43,7 +43,7 @@ modelling libraries based on ProcessBasedModelling.jl is to define modules/submo
4343
that offer a pool of pre-defined variables and processes.
4444
Modules may register their own default processes via the function
4545
[`register_default_process!`](@ref).
46-
These registered processes are used when `default` is a `Module`.
46+
These registered default processes are used when `default` is a `Module`.
4747
4848
## Keyword arguments
4949
@@ -53,6 +53,10 @@ These registered processes are used when `default` is a `Module`.
5353
`t` is also exported by ProcessBasedModelling.jl for convenience.
5454
- `warn_default::Bool = true`: if `true`, throw a warning when a variable does not
5555
have an assigned process but it has a default value so that it becomes a parameter instead.
56+
- `check_rhs::Bool = true`: if `true`, check that the RHS of all processes is NOT an
57+
`Equation` type. Throw an informative error if there is one. This
58+
helps scenarios where the RHS is wrongly an `Equation` assigning the LHS itself
59+
(has happened to me many times!).
5660
"""
5761
function processes_to_mtkmodel(args...;
5862
type = ODESystem, name = nameof(type), independent = t, kw...,
@@ -78,9 +82,10 @@ processes_to_mtkeqs(procs, default_dict(v); kw...)
7882
# The main implementation has the defaults to be a map from variable to process
7983
# because this simplifies a bit the code
8084
function processes_to_mtkeqs(_processes::Vector, default::Dict{Num, Any};
81-
type = ODESystem, name = nameof(type), independent = t, warn_default::Bool = true,
85+
warn_default::Bool = true, check_rhs::Bool = true,
8286
)
8387
processes = expand_multi_processes(_processes)
88+
check_rhs && check_rhs_validity(processes)
8489
# Setup: obtain lhs-variables so we can track new variables that are not
8590
# in this vector. The vector has to be of type `Num`
8691
lhs_vars = Num[lhs_variable(p) for p in processes]
@@ -154,6 +159,18 @@ function expand_multi_processes(procs::Vector)
154159
return expanded
155160
end
156161

162+
function check_rhs_validity(processes)
163+
for p in processes
164+
if rhs(p) isa Equation
165+
lvar = lhs_variable(p)
166+
throw(ArgumentError("Process assigned to variable $(lvar) is ill defined. "*
167+
"The RHS is an `<: Equation` type but it shouldn't be."
168+
))
169+
end
170+
end
171+
return
172+
end
173+
157174
function default_dict(processes)
158175
default = Dict{Num, Any}()
159176
for proc in processes

test/Project.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[deps]
22
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
3-
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
3+
OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a"
44
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
55
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
66
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

test/runtests.jl

+26-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
11
using ProcessBasedModelling
22
using Test
3-
using OrdinaryDiffEq
3+
using OrdinaryDiffEqTsit5
44

5+
# This module is used in one of the tests
6+
module TestDefault
7+
using ProcessBasedModelling
8+
@variables x(t) = 0.5 y(t) = 0.2
9+
register_default_process!.([
10+
Differential(t)(x) ~ 0.2y - x,
11+
y ~ x^2,
12+
], Ref(TestDefault))
13+
end
14+
15+
@testset "ProcessBasedModelling" begin
516
@testset "construction + evolution" begin
617
# The model, as defined below, is bistable due to ice albedo feedback
718
# so two initial conditions should go to two attractors
@@ -61,7 +72,7 @@ using OrdinaryDiffEq
6172
ufs = []
6273
for u0 in u0s
6374
p = ODEProblem(sys, u0, (0.0, 1000.0*365*24*60*60.0))
64-
sol = solve(p, Tsit5())
75+
sol = solve(p, Tsit5(); abstol = 1e-9, reltol = 1e-9)
6576
push!(ufs, sol.u[end])
6677
end
6778

@@ -215,13 +226,16 @@ end
215226
@test sort(ModelingToolkit.getname.(unknowns(sys2))) == [:w, :x, :y, :z]
216227
end
217228

218-
module TestDefault
219-
using ProcessBasedModelling
220-
@variables x(t) = 0.5 y(t) = 0.2
221-
register_default_process!.([
222-
Differential(t)(x) ~ 0.2y - x,
223-
y ~ x^2,
224-
], Ref(TestDefault))
229+
@testset "equation in RHS" begin
230+
@variables z(t) = 0.0
231+
@variables x(t) = 0.0
232+
@variables y(t) = 0.0
233+
procs = [
234+
ExpRelaxation(z, x^2, 1.0), # introduces x and y variables
235+
y ~ z-x, # is an equation, not a process!
236+
z ~ (z ~ x^2),
237+
]
238+
@test_throws ["an `<: Equation` type"] processes_to_mtkeqs(procs)
225239
end
226240

227241
@testset "registering default" begin
@@ -233,3 +247,6 @@ end
233247
@test has_symbolic_var(mtk, z)
234248
@test has_symbolic_var(mtk, TestDefault.x)
235249
end
250+
251+
252+
end # @testset

0 commit comments

Comments
 (0)