Skip to content

Commit 1ab6ccf

Browse files
authored
Move to MTK v9 and prepare v1 tag (#4)
* bump version of MTK * rename states to unknowns * make default choices more fitting for dynsys * increase test coverage * increase coverage more * more informative make new parameter * correct exponential relaxation * explcitily display errors and warnings * state value * name, not symbol * massive improvement on parameter macro * typo * don't use (t) in the wranings * add LiteralParameter * fix literal value * fix wrong test in derived * fix omitted x * restore warnonly setting * fix timescale reference * use canonical MTK t and derivative as requested * fix typo in docs * bump version to 1.0 * add addition process and test for all processes
1 parent e6c8ef1 commit 1ab6ccf

File tree

9 files changed

+243
-74
lines changed

9 files changed

+243
-74
lines changed

Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
name = "ProcessBasedModelling"
22
uuid = "ca969041-2cf3-4b10-bc21-86f4417093eb"
33
authors = ["Datseris <[email protected]>"]
4-
version = "0.1.0"
4+
version = "1.0.0"
55

66
[deps]
77
ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78"
88
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
99

1010
[compat]
11-
ModelingToolkit = "8.73"
11+
ModelingToolkit = "9.0"
1212
Reexport = "1.2"
1313
julia = "1.9.0"

docs/make.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,4 @@ pages = [
1515

1616
build_docs_with_style(pages, ProcessBasedModelling;
1717
authors = "George Datseris <[email protected]>",
18-
warnonly = true,
1918
)

docs/src/index.md

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,17 @@ ProcessBasedModelling
55
!!! note "Basic familiarity with ModelingToolkit.jl"
66
These docs assume that you have some basic familiarity with ModelingToolkit.jl. If you don't going through the introductory tutorial of [ModelingToolkit.jl](https://docs.sciml.ai/ModelingToolkit/stable/) should be enough to get you started!
77

8+
!!! note "Default `t` is unitless"
9+
Like ModelingToolkit.jl, ProcessBasedModelling.jl also exports `t` as the independent variable representing time.
10+
However, instead of the default `t` of ModelingToolkit.jl, here `t` is unitless.
11+
Do `t = ModelingToolkit.t` to obtain the unitful version of `t`.
812

913
## Usage
1014

1115
In ProcessBasedModelling.jl, each variable is governed by a "process".
1216
Conceptually this is just an equation that _defines_ the given variable.
13-
To couple the variable with the process it is governed by, a user either defines simple equations of the form "variable = expression", or creates an instance of [`Process`](@ref) if the left-hand-side of the equation needs to be anything more complex. In either case, the variable and the expression are both _symbolic expressions_ created via ModellingToolkit.jl (more specifically, via Symbolics.jl).
17+
To couple the variable with the process it is governed by, a user either defines simple equations of the form "variable = expression", or creates an instance of [`Process`](@ref) if the left-hand-side of the equation needs to be anything more complex (or, simply if you want to utilize the conveniences of predefined processes).
18+
In either case, the variable and the expression are both _symbolic expressions_ created via ModellingToolkit.jl (more specifically, via Symbolics.jl).
1419

1520
Once all the processes about the physical system are collected, they are given as a `Vector` to the [`processes_to_mtkmodel`](@ref) central function, similarly to how one gives a `Vector` of `Equation`s to e.g., `ModelingToolkit.ODESystem`. This function also defines what quantifies as a "process" in more specificity.
1621

@@ -30,13 +35,17 @@ symbolically using ModelingToolkit.jl (**MTK**). We define
3035
using ModelingToolkit
3136
using OrdinaryDiffEq: Tsit5
3237
33-
@variables t # independent variable
38+
@variables t # independent variable _without_ units
3439
@variables z(t) = 0.0
3540
@variables x(t) # no default value
3641
@variables y(t) = 0.0
3742
```
3843
ProcessBasedModelling.jl (**PBM**) strongly recommends that all defined variables have a default value at definition point. Here we didn't do this for ``x`` to illustrate what how such an "omission" will be treated by **PBM**.
3944

45+
!!! note "ModelingToolkit.jl is re-exported"
46+
ProcessBasedModelling.jl re-exports the whole `ModelingToolkit` package,
47+
so you don't need to be `using` both of them, just `using ProcessBasedModelling`.
48+
4049
To make the equations we want, we can use MTK directly, and call
4150
```@example MAIN
4251
eqs = [
@@ -52,20 +61,23 @@ equations(model)
5261

5362
All good. Now, if we missed the process for one variable (because of our own error/sloppyness/very-large-codebase), MTK will throw an error when we try to _structurally simplify_ the model (a step necessary before solving the ODE problem):
5463

55-
```@example MAIN
64+
```julia
5665
model = ODESystem(eqs[1:2], t; name = :example)
57-
try
58-
model = structural_simplify(model)
59-
catch e
60-
return e.msg
61-
end
66+
model = structural_simplify(model)
67+
```
68+
```
69+
ERROR: ExtraVariablesSystemException: The system is unbalanced.
70+
There are 3 highest order derivative variables and 2 equations.
71+
More variables than equations, here are the potential extra variable(s):
72+
z(t)
73+
x(t)
74+
y(t)
6275
```
6376

64-
As you can see, the error message is unhelpful even with such a trivial system of equations,
65-
as all variables are reported as "potentially missing".
77+
The error message is unhelpful as all variables are reported as "potentially missing".
6678
At least on the basis of our scientific reasoning however, both ``x, z`` have an equation.
6779
It is ``y`` that ``x`` introduced that does not have an equation.
68-
Moreover, in our experience these errors messages become increasingly less useful when a model has many equations and/or variables, as many variables get cited as "missing" from the variable map even when only one should be.
80+
Moreover, in our experience these error messages become increasingly less useful when a model has many equations and/or variables, as many variables get cited as "missing" from the variable map even when only one should be.
6981

7082
**PBM** resolves these problems and always gives accurate error messages when it comes to
7183
the construction of the system of equations.
@@ -95,15 +107,17 @@ Notice that the resulting **MTK** model is not `structural_simplify`-ed, to allo
95107
Now, in contrast to before, if we "forgot" a process, **PBM** will react accordingly.
96108
For example, if we forgot the 2nd process, then the construction will error informatively,
97109
telling us exactly which variable is missing, and because of which processes it is missing:
98-
```@example MAIN
99-
try
100-
model = processes_to_mtkmodel(processes[[1, 3]])
101-
catch e
102-
return e.msg
103-
end
110+
```julia
111+
model = processes_to_mtkmodel(processes[[1, 3]])
112+
```
113+
```
114+
ERROR: ArgumentError: Variable x was introduced in process of variable z(t).
115+
However, a process for x was not provided,
116+
there is no default process for x, and x doesn't have a default value.
117+
Please provide a process for variable x.
104118
```
105119

106-
If instead we "forgot" the ``y`` process, **PBM** will not error, but instead warn, and make ``y`` equal to a named parameter:
120+
If instead we "forgot" the ``y`` process, **PBM** will not error, but warn, and make ``y`` equal to a named parameter, since ``y`` has a default value:
107121
```@example MAIN
108122
model = processes_to_mtkmodel(processes[1:2])
109123
equations(model)
@@ -113,8 +127,18 @@ equations(model)
113127
parameters(model)
114128
```
115129

130+
and the warning thrown was:
131+
```julia
132+
┌ Warning: Variable y was introduced in process of variable x(t).
133+
│ However, a process for y was not provided,
134+
│ and there is no default process for it either.
135+
│ Since it has a default value, we make it a parameter by adding a process:
136+
`ParameterProcess(y)`.
137+
└ @ ProcessBasedModelling ...\ProcessBasedModelling\src\make.jl:65
138+
```
139+
116140
Lastly, [`processes_to_mtkmodel`](@ref) also allows the concept of "default" processes, that can be used for introduced "process-less" variables.
117-
Default processes are like `processes` and given as a 2nd argument to [`process_to_mtkmodel`](@ref).
141+
Default processes are like `processes` and given as a 2nd argument to [`processes_to_mtkmodel`](@ref).
118142
For example,
119143

120144
```@example MAIN
@@ -143,7 +167,8 @@ equations(model)
143167
parameters(model)
144168
```
145169

146-
This special handling is also why each process explicitly declares a timescale via the [`timescale`](@ref) function that one can optionally extend.
170+
This special handling is also why each process can declare a timescale via the [`ProcessBasedModelling.timescale`](@ref) function that one can optionally extend
171+
(although in our experience the default behaviour covers almost all cases).
147172

148173

149174
## Main API function
@@ -158,6 +183,7 @@ processes_to_mtkmodel
158183
ParameterProcess
159184
TimeDerivative
160185
ExpRelaxation
186+
AdditionProcess
161187
```
162188

163189
## `Process` API
@@ -180,4 +206,5 @@ default_value
180206
has_variable
181207
new_derived_named_parameter
182208
@convert_to_parameters
209+
LiteralParameter
183210
```

src/API.jl

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
"""
2-
A process subtype `p::Process` extends the following unexported functions:
2+
Process
3+
4+
A new process must subtype `Process` and can be used in [`processes_to_mtkmodel`](@ref).
5+
The type must extend the following functions from the module `ProcessBasedModelling`:
36
47
- `lhs_variable(p)` which returns the variable the process describes
58
(left-hand-side variable). There is a default implementation
@@ -57,12 +60,12 @@ function lhs(p::Process)
5760
τ = timescale(p)
5861
v = lhs_variable(p)
5962
if isnothing(τ) # time variability exists but timescale is nonexistent (unity)
60-
return Differential(t)(v)
63+
return D(v) # `D` is the MTK canonical variable for time derivative
6164
elseif τ isa NoTimeDerivative || iszero(τ) # no time variability
6265
return v
6366
else # τ is either Num or Real
6467
τvar = new_derived_named_parameter(v, τ, "τ", false)
65-
return τvar*Differential(t)(v)
68+
return τvar*D(v)
6669
end
6770
end
6871

src/ProcessBasedModelling.jl

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ module ProcessBasedModelling
88
end ProcessBasedModelling
99

1010
using Reexport
11+
using ModelingToolkit: t_nounits as t, D_nounits as D
1112
@reexport using ModelingToolkit
12-
13-
@variables t # independent variable (time)
13+
export t
1414

1515
include("API.jl")
1616
include("utils.jl")
@@ -22,10 +22,11 @@ include("processes_basic.jl")
2222

2323
# TODO: Perhaps not don't export `t`?
2424
export t
25-
export Process, ParameterProcess, TimeDerivative, ExpRelaxation
25+
export Process, ParameterProcess, TimeDerivative, ExpRelaxation, AdditionProcess
2626
export processes_to_mtkmodel
2727
export new_derived_named_parameter
2828
export has_variable, default_value
29-
export @convert_to_parameters
29+
export @convert_to_parameters, LiteralParameter
30+
export lhs_variable, rhs, lhs
3031

3132
end

src/make.jl

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"""
22
processes_to_mtkmodel(processes::Vector, default::Vector = []; kw...)
33
4-
Construct a ModelingToolkit.jl model using the provided `processes` and `default` processes.
4+
Construct a ModelingToolkit.jl model/system using the provided `processes` and `default` processes.
5+
The model/system is _not_ structurally simplified.
56
67
`processes` is a vector whose elements can be:
78
@@ -60,34 +61,31 @@ function processes_to_mtkmodel(_processes, _default = [];
6061
append_incomplete_variables!(incomplete, introduced, lhs_vars, def_proc)
6162
else
6263
def_val = default_value(added_var) # utilize default value (if possible)
64+
varstr = ModelingToolkit.getname(added_var)
6365
if !isnothing(def_val)
6466
@warn("""
65-
Variable $(added_var) was introduced in process of variable $(introduced[added_var]).
66-
However, a process for $(added_var) was not provided,
67+
Variable $(varstr) was introduced in process of variable $(introduced[added_var]).
68+
However, a process for $(varstr) was not provided,
6769
and there is no default process for it either.
6870
Since it has a default value, we make it a parameter by adding a process:
69-
`ParameterProcess($(added_var))`.
71+
`ParameterProcess($(varstr))`.
7072
""")
7173
parproc = ParameterProcess(added_var)
7274
push!(eqs, lhs(parproc) ~ rhs(parproc))
7375
push!(lhs_vars, added_var)
7476
else
7577
throw(ArgumentError("""
76-
Variable $(added_var) was introduced in process of variable $(introduced[added_var]).
77-
However, a process for $(added_var) was not provided,
78-
there is no default process for, and it doesn't have a default value.
79-
Please provide a process for variable $(added_var).
78+
Variable $(varstr) was introduced in process of variable $(introduced[added_var]).
79+
However, a process for $(varstr) was not provided,
80+
there is no default process for $(varstr), and $(varstr) doesn't have a default value.
81+
Please provide a process for variable $(varstr).
8082
"""))
8183
end
8284
end
8385
end
8486
sys = type(eqs, independent; name)
8587
return sys
8688
end
87-
# version without given processes
88-
function processes_to_mtkmodel(; kwargs...)
89-
return processes_to_mtkmodel(collect(values(default_processes())); kwargs...)
90-
end
9189

9290
function expand_multi_processes(procs::Vector)
9391
# Expand vectors of processes or ODESystems

src/processes_basic.jl

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,46 @@ ExpRelaxation(proc::Union{Process,Equation}, τ) = ExpRelaxation(lhs_variable(pr
7979

8080
timescale(e::ExpRelaxation) = e.timescale
8181
function rhs(e::ExpRelaxation)
82-
dt = isnothing(e.timescale) || iszero(e.timescale)
83-
dt ? e.expression : e.expression - e.variable
82+
τ = timescale(e)
83+
hasdt = if τ isa NoTimeDerivative
84+
false
85+
elseif isnothing(τ)
86+
true
87+
else
88+
!iszero(τ)
89+
end
90+
hasdt ? e.expression - e.variable : e.expression
91+
end
92+
93+
"""
94+
AdditionProcess(process, added)
95+
96+
A convenience process for adding `added` to the `rhs` of the given `process`.
97+
`added` can be a `Process` or `Equation`, in which case it is checked that
98+
the `lhs_variable` matches. Otherwise, it can be an arbitrary expression.
99+
"""
100+
struct AdditionProcess <: Process
101+
process
102+
added
103+
function AdditionProcess(process, added)
104+
if typeof(added) <: Union{Process, Equation}
105+
if ModelingToolkit.getname(lhs_variable(process)) ModelingToolkit.getname(lhs_variable(added))
106+
@show lhs_variable(process), lhs_variable(added)
107+
throw(ArgumentError("Added component does not have the same lhs variable."))
108+
end
109+
end
110+
return new(process, added)
111+
end
112+
end
113+
114+
lhs_variable(a::AdditionProcess) = lhs_variable(a.process)
115+
timescale(a::AdditionProcess) = timescale(a.process)
116+
117+
function rhs(a::AdditionProcess)
118+
if typeof(a.added) <: Union{Process, Equation}
119+
plus = rhs(a.added)
120+
else
121+
plus = a.added
122+
end
123+
return rhs(a.process) + plus
84124
end

0 commit comments

Comments
 (0)