Skip to content

Commit

Permalink
Improve docs a bit more (#9)
Browse files Browse the repository at this point in the history
* improve docs significantly

* bump version
  • Loading branch information
Datseris committed May 7, 2024
1 parent e17ebb0 commit 92fef85
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 25 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "ProcessBasedModelling"
uuid = "ca969041-2cf3-4b10-bc21-86f4417093eb"
authors = ["Datseris <datseris.george@gmail.com>"]
version = "1.2.1"
version = "1.2.2"

[deps]
ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78"
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
ProcessBasedModelling.jl is an extension to [ModelingToolkit.jl](https://docs.sciml.ai/ModelingToolkit/stable/) (MTK) for building a model of equations using symbolic expressions.
It is an alternative framework to MTK's [native component-based modelling](https://docs.sciml.ai/ModelingToolkit/stable/tutorials/acausal_components/), but, instead of components, there are "processes".
This approach is useful in the modelling of physical/biological/whatever systems, where each variable corresponds to a particular physical concept or observable and there are few (or none) duplicate variables to make the definition of MTK "factories" worthwhile.
On the other hand, there plenty of different physical representations, or _processes_ to represent a given physical concept.
On the other hand, there plenty of different physical representations, or _processes_ to represent a given physical concept in equation form.
In many scientific fields this approach parallels the modelling reasoning of the researcher more closely than the "components" approach.

Beyond this reasoning style, the biggest strength of ProcessBasedModelling.jl is the **informative errors and automation** it provides regarding incorrect/incomplete equations. When building the MTK model via ProcessBasedModelling.jl the user provides a vector of "processes": equations or custom types that have a well defined and single left-hand-side variable.
Expand All @@ -22,7 +22,7 @@ This allows ProcessBasedModelling.jl to:
- Else, throw an informative error saying exactly which originally provided variable introduced this new "process-less" variable.
3. Throw an informative error if a variable has two processes assigned to it (by mistake).

In our experience, and as we also highlight explicitly in the online documentation, this approach typically yields simpler, less ambiguous and more targeted warning or error messages than the native MTK one's, leading to faster identification and resolution of the problems with the composed equations.
In our experience, and as we also highlight explicitly in the online documentation, this approach typically yields simpler, less ambiguous, and more targeted warning or error messages than the native MTK one's. This leads to faster identification and resolution of the problems with the composed equations.

ProcessBasedModelling.jl is particularly suited for developing a model about a physical/biological/whatever system and being able to try various physical "rules" (couplings, feedbacks, mechanisms, ...) for a given physical observable efficiently.
This means switching arbitrarily between different processes that correspond to the same variable.
Expand All @@ -36,4 +36,4 @@ Besides the informative errors, ProcessBasedModelling.jl also

See the documentation online for details on how to use this package as well as examples highlighting its usefulness.

ProcessBasedModelling.jl development is funded by UKRI's Engineering and Physical Sciences Research Council, grant no. EP/Y01653X/1 (grant agreement for a EU Marie Sklodowska-Curie Postdoctoral Fellowship).
ProcessBasedModelling.jl development is funded by UKRI's Engineering and Physical Sciences Research Council, grant no. EP/Y01653X/1 (grant agreement for a EU Marie Sklodowska-Curie Postdoctoral Fellowship for George Datseris).
36 changes: 28 additions & 8 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ ProcessBasedModelling

In ProcessBasedModelling.jl, each variable is governed by a "process".
Conceptually this is just an equation that _defines_ the given variable.
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).
In either case, the variable and the expression are both _symbolic expressions_ created via ModellingToolkit.jl (more specifically, via Symbolics.jl).
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).
In either case, the `variable` and the `expression` are both _symbolic expressions_ created via ModellingToolkit.jl.

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.
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`. `processes_to_mtkmodel` also defines what quantifies as a "process" in more specificity.
Then `processes_to_mtkmodel` ensures that all variables in the relational graph of your equations have a defining equation, or throws informative errors/warnings otherwise.
It also provides some useful automation, see the example below.

## Example

Expand Down Expand Up @@ -60,8 +62,13 @@ equations(model)

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):

```julia
```@example MAIN
# no errors:
model = ODESystem(eqs[1:2], t; name = :example)
```

```julia
# here is the error
model = structural_simplify(model)
```
```
Expand Down Expand Up @@ -92,7 +99,7 @@ Here is what the user defines to make the same system of equations via **PBM**:
using ProcessBasedModelling
processes = [
ExpRelaxation(z, x^2), # defines z, introduces x; `Process` subtype (optional)
ExpRelaxation(z, x^2), # defines z, introduces x; `Process` subtype
Differential(t)(x) ~ 0.1*y, # defines x, introduces y; normal `Equation`
y ~ z - x, # defines y; normal `Equation`
]
Expand All @@ -108,15 +115,15 @@ equations(model)
Notice that the resulting **MTK** model is not `structural_simplify`-ed, to allow composing it with other models. By default `t` is taken as the independent variable.

Now, in contrast to before, if we "forgot" a process, **PBM** will react accordingly.
For example, if we forgot the 2nd process, then the construction will error informatively,
For example, if we forgot the process for ``x``, then the construction will error informatively,
telling us exactly which variable is missing, and because of which processes it is missing:
```julia
model = processes_to_mtkmodel(processes[[1, 3]])
```
```
ERROR: ArgumentError: Variable x(t) was introduced in process of variable z(t).
However, a process for x(t) was not provided,
there is no default process for x(t), and (t)x doesn't have a default value.
there is no default process for x(t), and x(t) doesn't have a default value.
Please provide a process for variable x(t).
```

Expand Down Expand Up @@ -154,6 +161,10 @@ equations(model)

does not throw any warnings as it obtained a process for ``y`` from the given default processes.

!!! note "Default processes example"
The default process infrastructure of **PBM** is arguably its most powerful quality when it comes to building field-specific libraries. Its usefulness is illustrated in the derivative package [ConceptualClimateModels.jl](https://github.com/JuliaDynamics/ConceptualClimateModels.jl).


## Special handling of timescales

In dynamical systems modelling the timescale associated with a process is a special parameter. That is why, if a timescale is given for either the [`TimeDerivative`](@ref) or [`ExpRelaxation`](@ref) processes, it is converted to a named `@parameter` by default:
Expand All @@ -173,9 +184,18 @@ equations(model)
parameters(model)
```

Note the automatically created parameters ``\tau_x, \tau_z``.
This special handling is also why each process can declare a timescale via the [`ProcessBasedModelling.timescale`](@ref) function that one can optionally extend
(although in our experience the default behaviour covers almost all cases).

If you do not want this automation, you can opt out in two ways:

- Provide your own created parameter as the third argument in e.g., `ExpRelaxation`
- Wrap the numeric value into [`LiteralParameter`](@ref). This will insert the numeric literal into the equation.

See the section on [automatic parameters](@ref auto_params) for more related automation,
such as the macro [`@convert_to_parameters`](@ref) which can be particularly useful
when developing a field-specific library.

## Main API function

Expand Down Expand Up @@ -205,7 +225,7 @@ ProcessBasedModelling.NoTimeDerivative
ProcessBasedModelling.lhs
```

## Automatic named parameters
## [Automatic named parameters](@id auto_params)

```@docs
new_derived_named_parameter
Expand Down
2 changes: 2 additions & 0 deletions src/default.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Register a `process` (`Equation` or `Process`) as a default process for its LHS
in the list of default processes tracked by the given module.
If `warn`, throw a warning if a default process with same LHS variable already
exists and will be overwritten.
You can use [`default_processes`](@ref) to obtain the list of tracked default processes.
"""
function register_default_process!(process::Union{Process, Equation}, m::Module; warn = true)
mdict = default_processes(m)
Expand Down
39 changes: 26 additions & 13 deletions src/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,50 @@
processes_to_mtkmodel(processes::Vector [, default]; kw...)
Construct a ModelingToolkit.jl model/system using the provided `processes` and `default` processes.
The model/system is _not_ structurally simplified.
The model/system is _not_ structurally simplified. During construction, the following automations
improve user experience:
`processes` is a vector whose elements can be:
- Variable(s) introduced in `processes` that does not itself have a process obtain
a default process from `default`.
- If no default exists, but the variable(s) itself has a default numerical value,
a [`ParameterProcess`](@ref) is created for said variable and a warning is thrown.
- Else, an informative error is thrown.
- An error is also thrown if any variable has two or more processes assigned to it.
`processes` is a `Vector` whose elements can be:
1. Any instance of a subtype of [`Process`](@ref). `Process` is a
wrapper around `Equation` that provides some conveniences, e.g., handling of timescales
or not having limitations on the left-hand-side (LHS) form.
1. An `Equation`. The LHS format of the equation is limited.
Let `x` be a `@variable` and `p` be a `@parameter`. Then, the LHS can only be one of:
`x`, `Differential(t)(x)`, `Differential(t)(x)*p`, `p*Differential(t)(x)`,
however, the versions with `p` may fail unexpectedly.
Anything else will either error or fail unexpectedly.
2. A vector of the above two, which is then expanded. This allows the convenience of
functions representing a physical process that may require many equations to be defined.
however, the versions with `p` may fail unexpectedly. Anything else will error.
2. A `Vector` of the above two, which is then expanded. This allows the convenience of
functions representing a physical process that may require many equations to be defined
(because e.g., they may introduce more variables).
3. A ModelingToolkit.jl `XDESystem`, in which case the `equations` of the system are expanded
as if they were given as a vector of equations like above. This allows the convenience
of straightforwardly coupling already existing systems.
of straightforwardly coupling with already existing `XDESystem`s.
## Default processes
`processes_to_mtkmodel` allows for specifying default processes by giving `default`.
These default processes are assigned to variables introduced in the main input `processes`
without themselves having an assigned process in the main input.
These default processes are assigned to variables introduced in the main input `processes`,
but themselves do not have an assigned process in the main input.
`default` can be a `Vector` of individual processes (`Equation` or `Process`).
Alternatively, `default` can be a `Module`. ProcessBasedModelling.jl allows modules to
register their own default processes via the function [`register_default_process!`](@ref).
Alternatively, `default` can be a `Module`. The recommended way to build field-specific
modelling libraries based on ProcessBasedModelling.jl is to define modules/submodules
that offer a pool of pre-defined variables and processes.
Modules may register their own default processes via the function
[`register_default_process!`](@ref).
These registered processes are used when `default` is a `Module`.
## Keyword arguments
- `type = ODESystem`: the model type to make
- `name = nameof(type)`: the name of the model
- `type = ODESystem`: the model type to make.
- `name = nameof(type)`: the name of the model.
- `independent = t`: the independent variable (default: `@variables t`).
`t` is also exported by ProcessBasedModelling.jl for convenience.
- `warn_default::Bool = true`: if `true`, throw a warning when a variable does not
Expand All @@ -47,6 +58,8 @@ processes_to_mtkmodel(procs, default_processes(m); kw...)
processes_to_mtkmodel(procs::Vector, v::Vector; kw...) =
processes_to_mtkmodel(procs, default_dict(v); kw...)

# The main implementation has the defaults to be a map from variable to process
# because this simplifies a bit the code
function processes_to_mtkmodel(_processes::Vector, default::Dict{Num, Any};
type = ODESystem, name = nameof(type), independent = t, warn_default::Bool = true,
)
Expand Down

0 comments on commit 92fef85

Please sign in to comment.