Skip to content

Commit

Permalink
Improve utils for v1 (#6)
Browse files Browse the repository at this point in the history
* add new has_parameter

* allow get var to be used with symbols as well

* complete in the tests

* add changelog

* clarify src for new derived

* allow has directly to models

* add all_equations

* ensure tracked variables are of type Num

* we don't need ordinary diff eq in docs

* better blocks

* don't export core api
  • Loading branch information
Datseris authored Feb 28, 2024
1 parent d625fff commit 35117a9
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 38 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Changelog

ProcessBasedModelling.jl follows semver 2.0.
Changelog is kept with respect to v1 release.
1 change: 0 additions & 1 deletion docs/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,3 @@ DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244"
DocumenterTools = "35a29f4d-8980-5a13-9543-d66fff28ecb8"
DynamicalSystems = "61744808-ddfa-5f27-97ff-6e42cc95d634"
ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78"
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
17 changes: 11 additions & 6 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ symbolically using ModelingToolkit.jl (**MTK**). We define

```@example MAIN
using ModelingToolkit
using OrdinaryDiffEq: Tsit5
@variables t # independent variable _without_ units
@variables z(t) = 0.0
Expand Down Expand Up @@ -78,9 +77,10 @@ The error message is unhelpful as all variables are reported as "potentially mis
At least on the basis of our scientific reasoning however, both ``x, z`` have an equation.
It is ``y`` that ``x`` introduced that does not have an equation.
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.
This makes it difficult to quickly find out where the "mistake" happened in the equations.

**PBM** resolves these problems and always gives accurate error messages when it comes to
the construction of the system of equations.
**PBM** resolves these problems and always gives accurate error messages when
it comes to the construction of the system of equations.
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).

Here is what the user defines to make the same system of equations via **PBM**:
Expand Down Expand Up @@ -199,12 +199,17 @@ ProcessBasedModelling.NoTimeDerivative
ProcessBasedModelling.lhs
```

## Utility functions
## Automatic named parameters

```@docs
default_value
has_variable
new_derived_named_parameter
@convert_to_parameters
LiteralParameter
```

## Utility functions

```@docs
default_value
has_symbolic_var
```
5 changes: 3 additions & 2 deletions src/ProcessBasedModelling.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ export t
export Process, ParameterProcess, TimeDerivative, ExpRelaxation, AdditionProcess
export processes_to_mtkmodel
export new_derived_named_parameter
export has_variable, default_value
export has_symbolic_var, default_value
export @convert_to_parameters, LiteralParameter
export lhs_variable, rhs, lhs
# export lhs_variable, rhs, lhs # I am not sure whether these should be exported.
export all_equations

end
4 changes: 2 additions & 2 deletions src/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ function processes_to_mtkmodel(_processes, _default = [];
processes = expand_multi_processes(_processes)
default = default_dict(_default)
# Setup: obtain lhs-variables so we can track new variables that are not
# in this vector
lhs_vars = map(lhs_variable, processes)
# in this vector. The vector has to be of type `Num`
lhs_vars = Num[lhs_variable(p) for p in processes]
ensure_unique_vars(lhs_vars)
# First pass: generate equations from given processes
# and collect variables without equations
Expand Down
2 changes: 1 addition & 1 deletion src/processes_basic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ struct ParameterProcess <: Process
variable
value
function ParameterProcess(variable, value)
var = new_derived_named_parameter(variable, value, "0")
var = new_derived_named_parameter(variable, value, "0"; prefix = false)
return new(variable, var)
end
end
Expand Down
55 changes: 42 additions & 13 deletions src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,48 @@ _literalvalue(x) = x
_literalvalue(p::LiteralParameter) = p.p

"""
has_variable(eq, var)
has_symbolic_var(eqs, var)
Return `true` if variable `var` exists in the equation(s) `eq`, `false` otherwise.
Function works irrespectively if `var` is an `@variable` or `@parameter`.
Return `true` if symbolic variable `var` exists in the equation(s) `eq`, `false` otherwise.
This works for either `@parameters` or `@variables`.
If `var` is a `Symbol` isntead of a `Num`, all variables are converted to their names
and equality is checked on the basis of the name only.
has_symbolic_var(model, var)
When given a MTK model (such as `ODESystem`) search in _all_ the equations of the system,
including observed variables.
"""
function has_variable(eq::Equation, var)
function has_symbolic_var(eq::Equation, var)
vars = get_variables(eq)
return _has_thing(var, vars)
end
has_symbolic_var(eqs::Vector{Equation}, var) = any(eq -> has_symbolic_var(eq, var), eqs)
has_symbolic_var(mtk, var) = has_symbolic_var(all_equations(mtk), var)

function _has_thing(var::Num, vars)
return any(isequal(var), vars)
end
has_variable(eqs, var) = any(eq -> has_variable(eq, var), eqs)
function _has_thing(var::Symbol, vars)
vars = ModelingToolkit.getname.(vars)
var = ModelingToolkit.getname(var)
return any(isequal(var), vars)
end

"""
all_equations(model)
Equivalent with `vcat(equations(model), observed(model))`.
"""
all_equations(model) = vcat(equations(model), observed(model))

"""
default_value(x)
Return the default value of a symbolic variable `x` or `nothing`
if it doesn't have any. Return `x` if `x` is not a symbolic variable.
The difference with `ModelingToolkit.getdefault` is that this function will
not error on the absence of a default value.
"""
default_value(x) = x
default_value(x::Num) = default_value(x.val)
Expand Down Expand Up @@ -64,11 +90,14 @@ end
If `value isa Num` return `value`.
If `value isa LiteralParameter`, replace it with its literal value.
Otherwise, create a new MTK `@parameter`
whose name is created from `variable` (which could also be just a `Symbol`) by adding the `extra` string.
If the keyword `prefix == false` the `extra` is added at the end after a `_`. Otherwise
it is added at the start, then a `_` and then the variable name.
The keyword `connector = "_"` is what connects the `extra` with the name.
Otherwise, create a new MTK `@parameter` whose name is created from `variable`
(which could also be just a `Symbol`) by adding the `extra` string.
**Keywords**:
- `prefix = true`: whether the `extra` is added at the start or the end, connecting
with the with the `connector`.
- `connector = "_"`: what to use to connect `extra` with the name.
For example,
Expand All @@ -83,10 +112,10 @@ new_derived_named_parameter(v, value::Num, extra::String; kw...) = value
new_derived_named_parameter(v, value::LiteralParameter, extra::String; kw...) = value.p
function new_derived_named_parameter(v, value::Real, extra; connector = "_", prefix = true)
n = string(ModelingToolkit.getname(v))
newstring = if !(prefix)
n*connector*extra
else
newstring = if prefix
extra*connector*n
else
n*connector*extra
end
new_derived_named_parameter(newstring, value)
end
Expand Down
27 changes: 14 additions & 13 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ using OrdinaryDiffEq

sys = structural_simplify(sys)
@test length(unknowns(sys)) == 1
@test has_variable(equations(sys), T)
@test has_symbolic_var(equations(sys), T)

u0s = [[300.0], [100.0]]
ufs = []
Expand All @@ -81,7 +81,7 @@ using OrdinaryDiffEq

sys = structural_simplify(sys)
@test length(unknowns(sys)) == 1
@test has_variable(equations(sys), T)
@test has_symbolic_var(equations(sys), T)

end

Expand Down Expand Up @@ -160,12 +160,9 @@ end
@test p == 0.5
end


@testset "new named var"

end

@testset "default processes" begin
@testset "default processes, has_thing" begin
@variables x(t) = 0.5
@variables y(t) = 0.5
@variables z(t) = 0.5
Expand All @@ -174,15 +171,19 @@ end
processes = [
TimeDerivative(x, x^2, 1.2),
ParameterProcess(y),
ExpRelaxation(z, x^2),
ExpRelaxation(z, x^2, 0.5),
AdditionProcess(ParameterProcess(w), x^2),
AdditionProcess(TimeDerivative(q, x^2, 1.2), ExpRelaxation(q, x^2))
]
mtk = processes_to_mtkmodel(processes)
eqs = equations(mtk)
@test has_variable(eqs, x)
@test has_variable(eqs, y)
@test has_variable(eqs, z)
@test has_variable(eqs, w)
@test has_variable(eqs, q)
mtk = structural_simplify(mtk)
eqs = all_equations(mtk)
@test has_symbolic_var(eqs, x)
@test has_symbolic_var(eqs, y)
@test has_symbolic_var(eqs, :z)
@test has_symbolic_var(eqs, mtk.w)
@test has_symbolic_var(eqs, q)
@test has_symbolic_var(mtk, q)
@test has_symbolic_var(eqs, mtk.τ_z)
@test has_symbolic_var(eqs, :w_0)
end

0 comments on commit 35117a9

Please sign in to comment.