Skip to content

Commit f41091a

Browse files
authored
ALlow XDESystems, expand possibilities for LHS of Equation (#7)
* allow DESystems in processes_to_mtkmodel * allow Differential and differential*p in LHS of equations as processes * add changelog entry * add warning comment * fix type of vector expansion container * show that differential can be used in the docs * add new info to docs * correct version parse
1 parent 3580c5f commit f41091a

File tree

6 files changed

+94
-35
lines changed

6 files changed

+94
-35
lines changed

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
# Changelog
22

33
ProcessBasedModelling.jl follows semver 2.0.
4-
Changelog is kept with respect to v1 release.
4+
Changelog is kept with respect to v1 release.
5+
6+
## 1.1
7+
8+
- New keyword `warn_default` in `processes_to_mtkmodel`.
9+
- Now `processes_to_mtkmodel` allows for `XDESystems` as elements of the input vector.
10+
- Detection of LHS-variable in `Equation` has been improved significantly.
11+
Now, LHS can be any of: `x`, `Differential(t)(x)`, `Differential(t)(x)*p`, `p*Differential(t)(x)`.

Project.toml

Lines changed: 1 addition & 1 deletion
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 <[email protected]>"]
4-
version = "1.0.5"
4+
version = "1.1.0"
55

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

docs/src/index.md

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,22 +76,25 @@ More variables than equations, here are the potential extra variable(s):
7676
The error message is unhelpful as all variables are reported as "potentially missing".
7777
At least on the basis of our scientific reasoning however, both ``x, z`` have an equation.
7878
It is ``y`` that ``x`` introduced that does not have an equation.
79-
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.
79+
Moreover, in our experience these error messages become increasingly less accurate or helpful when a model has many equations and/or variables.
8080
This makes it difficult to quickly find out where the "mistake" happened in the equations.
8181

8282
**PBM** resolves these problems and always gives accurate error messages when
8383
it comes to the construction of the system of equations.
8484
This is because on top of the variable map that MTK constructs automatically, **PBM** requires the user to implicitly provide a map of variables to processes that govern said variables. **PBM** creates the map automatically, the only thing the user has to do is to define the equations in terms of what [`processes_to_mtkmodel`](@ref) wants (which are either [`Process`](@ref)es or `Equation`s as above).
8585

86+
For the majority of cases, **PBM** can infer the LHS variable a process "defines" automatically, just by passing in a vector of `Equation`s, like in **MTK**.
87+
For cases where this is not possible a dedicated `Process` type is provided, whose subtypes act as wrappers around equations providing some additional conveniences.
88+
8689
Here is what the user defines to make the same system of equations via **PBM**:
8790

8891
```@example MAIN
8992
using ProcessBasedModelling
9093
9194
processes = [
92-
ExpRelaxation(z, x^2), # introduces x variable
93-
TimeDerivative(x, 0.1*y), # introduces y variable
94-
y ~ z - x, # can be an equation because LHS is single variable
95+
ExpRelaxation(z, x^2), # defines z, introduces x; `Process` subtype (optional)
96+
Differential(t)(x) ~ 0.1*y, # defines x, introduces y; normal `Equation`
97+
y ~ z - x, # defines y; normal `Equation`
9598
]
9699
```
97100

@@ -117,17 +120,20 @@ there is no default process for x(t), and (t)x doesn't have a default value.
117120
Please provide a process for variable x(t).
118121
```
119122

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:
123+
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.
124+
So, running:
121125
```@example MAIN
122126
model = processes_to_mtkmodel(processes[1:2])
123127
equations(model)
124128
```
125129

130+
Makes the named parameter:
131+
126132
```@example MAIN
127133
parameters(model)
128134
```
129135

130-
and the warning thrown was:
136+
and throws the warning:
131137
```julia
132138
┌ Warning: Variable y(t) was introduced in process of variable x(t).
133139
│ However, a process for y(t) was not provided,

src/API.jl

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,9 @@ timescale(::Process) = NoTimeDerivative()
5151

5252
"""
5353
ProcessBasedModelling.lhs(p::Process)
54+
ProcessBasedModelling.lhs(eq::Equation)
5455
55-
Return the left-hand-side of the equation that `p` represents as an `Expression`.
56+
Return the right-hand-side of the equation governing the process.
5657
If `timescale` is implemented for `p`, typically `lhs` does not need to be as well.
5758
See [`Process`](@ref) for more.
5859
"""
@@ -70,24 +71,39 @@ function lhs(p::Process)
7071
end
7172

7273
"""
73-
ProcessBasedModelling.rhs(p::Process)
74+
ProcessBasedModelling.rhs(process)
7475
75-
Return the right-hand-side of the equation that `p` represents as an `Expression`.
76-
See [`Process`](@ref) for more.
76+
Return the right-hand-side of the equation governing the process.
7777
"""
7878
function rhs(p::Process)
7979
error("Right-hand side (`rhs`) is not defined for process $(nameof(typeof(p))).")
8080
end
8181

8282
# Extensions for `Equation`:
8383
rhs(e::Equation) = e.rhs
84-
lhs(e::Equation) = lhs_variable(e)
85-
function lhs_variable(e::Equation)
86-
x = e.lhs
87-
# we first check whether `x` is a variable
88-
if !is_variable(x)
89-
throw(ArgumentError("In given equation $(e), the left-hand-side does "*
90-
"not represent a single variable."))
84+
lhs(e::Equation) = e.lhs
85+
lhs_variable(e::Equation) = lhs_variable(lhs(e))
86+
lhs_variable(x::Num) = Num(lhs_variable(x.val))
87+
function lhs_variable(x) # basically x is SymbolicUtils.BasicSymbolic{Real}
88+
# check whether `x` is a single variable already
89+
if is_variable(x)
90+
return x
91+
end
92+
# check Differential(t)(x)
93+
if hasproperty(x, :f)
94+
if x.f isa Differential
95+
return x.arguments[1]
96+
end
97+
end
98+
# check Differential(t)(x)*parameter
99+
if hasproperty(x, :arguments)
100+
args = x.arguments
101+
di = findfirst(a -> hasproperty(a, :f) && a.f isa Differential, args)
102+
if !isnothing(di)
103+
return args[di].arguments[1]
104+
end
91105
end
92-
return x
106+
# error if all failed
107+
throw(ArgumentError("We analyzed the LHS `$(x)` "*
108+
"but could not extract a single variable it represents."))
93109
end

src/make.jl

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,22 @@ The model/system is _not_ structurally simplified.
66
77
`processes` is a vector whose elements can be:
88
9-
- Any instance of a subtype of [`Process`](@ref).
10-
- An `Equation` which is of the form `variable ~ expression` with `variable` a single
11-
variable resulting from an `@variables` call.
12-
- A vector of the above two, which is then expanded. This allows the convenience of
13-
functions representing a physical process that may require many equations to be defined.
9+
1. Any instance of a subtype of [`Process`](@ref). `Process` is a
10+
wrapper around `Equation` that provides some conveniences, e.g., handling of timescales
11+
or not having limitations on the left-hand-side (LHS) form.
12+
1. An `Equation`. The LHS format of the equation is limited.
13+
Let `x` be a `@variable` and `p` be a `@parameter`. Then, the LHS can only be one of:
14+
`x`, `Differential(t)(x)`, `Differential(t)(x)*p`, `p*Differential(t)(x)`.
15+
Anything else will either error or fail unexpectedly.
16+
2. A vector of the above two, which is then expanded. This allows the convenience of
17+
functions representing a physical process that may require many equations to be defined.
18+
3. A ModelingToolkit.jl `XDESystem`, in which case the `equations` of the system are expanded
19+
as if they were given as a vector of equations like above. This allows the convenience
20+
of straightforwardly coupling already existing systems.
1421
1522
`default` is a vector that can contain the first two possibilities only
16-
as it contains default processes that may be assigned to variables introduced in `processes`
17-
but they don't themselves have an assigned process.
23+
as it contains default processes that may be assigned to individual variables introduced in
24+
`processes` but they don't themselves have an assigned process.
1825
1926
It is expected that downstream packages that use ProcessBasedModelling.jl to make a
2027
field-specific library implement a 1-argument version of `processes_to_mtkmodel`,
@@ -26,10 +33,10 @@ or provide a wrapper function for it, and add a default value for `default`.
2633
- `name = nameof(type)`: the name of the model
2734
- `independent = t`: the independent variable (default: `@variables t`).
2835
`t` is also exported by ProcessBasedModelling.jl for convenience.
29-
- `warn_defaut::Bool = true`: if `true`, throw a warning when a variable does not
36+
- `warn_default::Bool = true`: if `true`, throw a warning when a variable does not
3037
have an assigned process but it has a default value so that it becomes a parameter instead.
3138
"""
32-
function processes_to_mtkmodel(_processes, _default = [];
39+
function processes_to_mtkmodel(_processes::Vector, _default = [];
3340
type = ODESystem, name = nameof(type), independent = t, warn_default::Bool = true,
3441
)
3542
processes = expand_multi_processes(_processes)
@@ -86,19 +93,25 @@ function processes_to_mtkmodel(_processes, _default = [];
8693
end
8794
end
8895
end
96+
# return eqs
8997
sys = type(eqs, independent; name)
9098
return sys
9199
end
92100

93101
function expand_multi_processes(procs::Vector)
102+
etypes = Union{Vector, ODESystem, SDESystem, PDESystem}
103+
!any(p -> p isa etypes, procs) && return procs
94104
# Expand vectors of processes or ODESystems
95-
!any(p -> p isa Vector, procs) && return procs
96-
expanded = deepcopy(procs)
97-
idxs = findall(p -> p isa Vector, procs)
105+
expanded = Any[procs...]
106+
idxs = findall(p -> p isa etypes, procs)
98107
multiprocs = expanded[idxs]
99108
deleteat!(expanded, idxs)
100109
for mp in multiprocs
101-
append!(expanded, mp)
110+
if mp isa Vector
111+
append!(expanded, mp)
112+
else # then it is XDE system
113+
append!(expanded, equations(mp))
114+
end
102115
end
103116
return expanded
104117
end
@@ -144,8 +157,8 @@ function nonunique(x::AbstractArray{T}) where T
144157
duplicatedset = Set{T}()
145158
duplicatedvector = Vector{T}()
146159
for i in x
147-
if(i in uniqueset)
148-
if !(i in duplicatedset)
160+
if i uniqueset
161+
if !(i duplicatedset)
149162
push!(duplicatedset, i)
150163
push!(duplicatedvector, i)
151164
end

test/runtests.jl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,4 +191,21 @@ end
191191
@testset "addition process error" begin
192192
@variables x(t) y(t) z(t)
193193
@test_throws ArgumentError AdditionProcess(x ~ 0.1z, y ~ x^2)
194+
end
195+
196+
@testset "ODESystem as process" begin
197+
@variables z(t) = 0.0
198+
@variables x(t) = 0.0
199+
@variables y(t) = 0.0
200+
@variables w(t) = 0.0
201+
procs = [
202+
ExpRelaxation(z, x^2, 1.0), # introduces x and y variables
203+
TimeDerivative(x, 0.1*y), # introduces y variable!
204+
y ~ z-x, # is an equation, not a process!
205+
]
206+
207+
sys = processes_to_mtkmodel(procs)
208+
sys2 = processes_to_mtkmodel([sys, w ~ x*y])
209+
@test length(equations(sys2)) == 4
210+
@test sort(ModelingToolkit.getname.(unknowns(sys2))) == [:w, :x, :y, :z]
194211
end

0 commit comments

Comments
 (0)