From 008734dc4afc1ac3c92df881700d21308501dae7 Mon Sep 17 00:00:00 2001 From: "Documenter.jl" Date: Tue, 27 Feb 2024 09:44:54 +0000 Subject: [PATCH] build based on d625fff --- dev/.documenter-siteinfo.json | 2 +- dev/index.html | 14 +++++++------- dev/search_index.js | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/dev/.documenter-siteinfo.json b/dev/.documenter-siteinfo.json index 4eeb283..7a57ec8 100644 --- a/dev/.documenter-siteinfo.json +++ b/dev/.documenter-siteinfo.json @@ -1 +1 @@ -{"documenter":{"julia_version":"1.10.1","generation_timestamp":"2024-02-26T15:29:39","documenter_version":"1.2.1"}} \ No newline at end of file +{"documenter":{"julia_version":"1.10.1","generation_timestamp":"2024-02-27T09:44:52","documenter_version":"1.2.1"}} \ No newline at end of file diff --git a/dev/index.html b/dev/index.html index 7ddb4f0..8586a10 100644 --- a/dev/index.html +++ b/dev/index.html @@ -1,5 +1,5 @@ -Documentation · ProcessBasedModelling.jl
ProcessBasedModellingModule

ProcessBasedModelling.jl

docsdev docsstable CI codecov Package Downloads

ProcessBasedModelling.jl is an extension to ModelingToolkit.jl (MTK) for building a model of equations using symbolic expressions. It is an alternative framework to MTK's native component-based modelling, 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. 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. This allows ProcessBasedModelling.jl to:

  1. Iterate over the processes and collect new variables that have been introduced by a provided process but do not themselves have a process assigned to them.
  2. For these collected "process-less" variables:
    • If there is a default process defined, incorporate this one into the model
    • If there is no default process but the variable has a default value, equate the variable to a parameter that has the same default value and throw an informative warning.
    • 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.

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. Hence, the target application of ProcessBasedModelling.jl is to be a framework to develop field-specific libraries that offer predefined processes without themselves relying on the existence of context-specific predefined components. An example usage is in EnergyBalanceModels.jl.

Besides the informative errors, ProcessBasedModelling.jl also

  1. Provides a couple of common process subtypes out of the box to accelerate development of field-specific libraries.
  2. Makes named MTK variables and parameters automatically, corresponding to parameters introduced by the by-default provided processes. This typically leads to intuitive names without being explicitly coded, while being possible to opt-out.
  3. Provides some utility functions for further building field-specific libraries.

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

source
Basic familiarity with ModelingToolkit.jl

These docs assume that you have some basic familiarity with ModelingToolkit.jl. If you don't going through the introductory tutorial of ModelingToolkit.jl should be enough to get you started!

Default `t` is unitless

Like ModelingToolkit.jl, ProcessBasedModelling.jl also exports t as the independent variable representing time. However, instead of the default t of ModelingToolkit.jl, here t is unitless. Do t = ModelingToolkit.t to obtain the unitful version of t.

Usage

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

Once all the processes about the physical system are collected, they are given as a Vector to the processes_to_mtkmodel central function, similarly to how one gives a Vector of Equations to e.g., ModelingToolkit.ODESystem. This function also defines what quantifies as a "process" in more specificity.

Example

Let's say we want to build the system of equations

\[\dot{z} = x^2 - z \\ +Documentation · ProcessBasedModelling.jl

ProcessBasedModellingModule

ProcessBasedModelling.jl

docsdev docsstable CI codecov Package Downloads

ProcessBasedModelling.jl is an extension to ModelingToolkit.jl (MTK) for building a model of equations using symbolic expressions. It is an alternative framework to MTK's native component-based modelling, 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. 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. This allows ProcessBasedModelling.jl to:

  1. Iterate over the processes and collect new variables that have been introduced by a provided process but do not themselves have a process assigned to them.
  2. For these collected "process-less" variables:
    • If there is a default process defined, incorporate this one into the model
    • If there is no default process but the variable has a default value, equate the variable to a parameter that has the same default value and throw an informative warning.
    • 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.

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. Hence, the target application of ProcessBasedModelling.jl is to be a framework to develop field-specific libraries that offer predefined processes without themselves relying on the existence of context-specific predefined components. An example usage is in EnergyBalanceModels.jl.

Besides the informative errors, ProcessBasedModelling.jl also

  1. Provides a couple of common process subtypes out of the box to accelerate development of field-specific libraries.
  2. Makes named MTK variables and parameters automatically, corresponding to parameters introduced by the by-default provided processes. This typically leads to intuitive names without being explicitly coded, while being possible to opt-out.
  3. Provides some utility functions for further building field-specific libraries.

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

source
Basic familiarity with ModelingToolkit.jl

These docs assume that you have some basic familiarity with ModelingToolkit.jl. If you don't going through the introductory tutorial of ModelingToolkit.jl should be enough to get you started!

Default `t` is unitless

Like ModelingToolkit.jl, ProcessBasedModelling.jl also exports t as the independent variable representing time. However, instead of the default t of ModelingToolkit.jl, here t is unitless. Do t = ModelingToolkit.t to obtain the unitful version of t.

Usage

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

Once all the processes about the physical system are collected, they are given as a Vector to the processes_to_mtkmodel central function, similarly to how one gives a Vector of Equations to e.g., ModelingToolkit.ODESystem. This function also defines what quantifies as a "process" in more specificity.

Example

Let's say we want to build the system of equations

\[\dot{z} = x^2 - z \\ \dot{x} = 0.1y \\ y = z - x\]

symbolically using ModelingToolkit.jl (MTK). We define

using ModelingToolkit
 using OrdinaryDiffEq: Tsit5
@@ -77,12 +77,12 @@
 
 model = processes_to_mtkmodel(processes)
 equations(model)

\[ \begin{align} -z_{\tau} \frac{\mathrm{d} z\left( t \right)}{\mathrm{d}t} =& - z\left( t \right) + \left( x\left( t \right) \right)^{2} \\ -x_{\tau} \frac{\mathrm{d} x\left( t \right)}{\mathrm{d}t} =& 0.1 y\left( t \right) \\ +\frac{\mathrm{d} z\left( t \right)}{\mathrm{d}t} \tau_{z} =& - z\left( t \right) + \left( x\left( t \right) \right)^{2} \\ +\frac{\mathrm{d} x\left( t \right)}{\mathrm{d}t} \tau_{x} =& 0.1 y\left( t \right) \\ y\left( t \right) =& - x\left( t \right) + z\left( t \right) \end{align} \]

parameters(model)
2-element Vector{SymbolicUtils.BasicSymbolic{Real}}:
- z_τ
- x_τ

This special handling is also why each process can declare a timescale via the ProcessBasedModelling.timescale function that one can optionally extend (although in our experience the default behaviour covers almost all cases).

Main API function

ProcessBasedModelling.processes_to_mtkmodelFunction
processes_to_mtkmodel(processes::Vector, default::Vector = []; kw...)

Construct a ModelingToolkit.jl model/system using the provided processes and default processes. The model/system is not structurally simplified.

processes is a vector whose elements can be:

  • Any instance of a subtype of Process.
  • An Equation which is of the form variable ~ expression with variable a single variable resulting from an @variables call.
  • 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.

default is a vector that can contain the first two possibilities only as it contains default processes that may be assigned to variables introduced in processes but they don't themselves have an assigned process.

It is expected that downstream packages that use ProcessBasedModelling.jl to make a field-specific library implement a 1-argument version of processes_to_mtkmodel, or provide a wrapper function for it, and add a default value for default.

Keyword arguments

  • 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.
source

Predefined Process subtypes

ProcessBasedModelling.ParameterProcessType
ParameterProcess(variable, value = default_value(variable)) <: Process

The simplest process which equates a given variable to a constant value that is encapsulated in a parameter. If value isa Real, then a named parameter with the name of variable and _0 appended is created. Else, if valua isa Num then it is taken as the paremeter directly.

Example:

@variables T(t) = 0.5
-proc = ParameterProcess(T)

will create the equation T ~ T_0, where T_0 is a @parameter with default value 0.5.

source
ProcessBasedModelling.TimeDerivativeType
TimeDerivative(variable, expression [, τ])

The second simplest process that equates the time derivative of the variable to the given expression while providing some conveniences over manually constructing an Equation.

It creates the equation τ_$(variable) Differential(t)(variable) ~ expression by constructing a new @parameter with default value τ (if τ is already a @parameter, it is used as-is). If τ is not given, then 1 is used at its place and no parameter is created.

Note that if iszero(τ), then the process variable ~ expression is created.

source
ProcessBasedModelling.ExpRelaxationType
ExpRelaxation(variable, expression [, τ]) <: Process

A common process for creating an exponential relaxation of variable towards the given expression, with timescale τ. It creates the equation:

τn*Differential(t)(variable) ~ expression - variable

Where τn is a new named @parameter with the value of τ and name τ_($(variable)). If instead τ is nothing, then 1 is used in its place (this is the default behavior). If iszero(τ), then the equation variable ~ expression is created instead.

The convenience function

ExpRelaxation(process, τ)

allows converting an existing process (or equation) into an exponential relaxation by using the rhs(process) as the expression in the equation above.

source
ProcessBasedModelling.AdditionProcessType
AdditionProcess(process, added)

A convenience process for adding added to the rhs of the given process. added can be a Process or Equation, in which case it is checked that the lhs_variable matches. Otherwise, it can be an arbitrary expression.

source

Process API

This API describes how you can implement your own Process subtype, if the existing predefined subtypes don't fit your bill!

ProcessBasedModelling.ProcessType
Process

A new process must subtype Process and can be used in processes_to_mtkmodel. The type must extend the following functions from the module ProcessBasedModelling:

  • lhs_variable(p) which returns the variable the process describes (left-hand-side variable). There is a default implementation lhs_variable(p) = p.variable if the field exists.
  • rhs(p) which is the right-hand-side expression, i.e., the "actual" process.
  • (optional) timescale(p), which defaults to NoTimeDerivative.
  • (optional) lhs(p) which returns the left-hand-side. Let τ = timescale(p). Then default lhs(p) behaviour depends on τ as follows:
    • Just lhs_variable(p) if τ == NoTimeDerivative().
    • Differential(t)(p) if τ == nothing.
    • τ_var*Differential(t)(p) if τ isa Union{Real, Num}. If real, a new named parameter τ_var is created that has the prefix :τ_ and then the lhs-variable name and has default value τ. Else if Num, τ_var = τ as given.
    • Explicitly extend lhs_variable if the above do not suit you.
source
ProcessBasedModelling.NoTimeDerivativeType
ProcessBasedModelling.NoTimeDerivative()

Singleton value that is the default output of the timescale function for variables that do not vary in time autonomously, i.e., they have no d/dt derivative and hence the concept of a "timescale" does not apply to them.

source
ProcessBasedModelling.lhsFunction
ProcessBasedModelling.lhs(p::Process)

Return the left-hand-side of the equation that p represents as an Expression. If timescale is implemented for p, typically lhs does not need to be as well. See Process for more.

source

Utility functions

ProcessBasedModelling.has_variableFunction
has_variable(eq, var)

Return true if variable var exists in the equation(s) eq, false otherwise. Function works irrespectively if var is an @variable or @parameter.

source
ProcessBasedModelling.new_derived_named_parameterFunction
new_derived_named_parameter(variable, value, extra::String, prefix = true)

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 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.

For example,

@variables x(t)
-p = new_derived_named_parameter(x, 0.5, "τ")

Now p will be a parameter with name :τ_x and default value 0.5.

source
ProcessBasedModelling.@convert_to_parametersMacro
@convert_to_parameters vars...

Convert all variables vars into @parameters with name the same as vars and default value the same as the value of vars. The macro leaves unaltered inputs that are of type Num, assumming they are already parameters. It also replaces LiteralParameter inputs with its literal values. This macro is extremely useful to convert e.g., keyword arguments into named parameters, while also allowing the user to give custom parameter names.

Example:

``` julia> A, B = 0.5, 0.5 (0.5, 0.5)

julia> C = first(@parameters X = 0.5)

julia> @converttoparameters A B C 3-element Vector{Num}: A B X

julia> typeof(A) # A is not a number anymore! Num

julia> default_value(A) 0.5

julia> C # the binding C still corresponds to parameter named :X! X

source
+ τ_z + τ_x

This special handling is also why each process can declare a timescale via the ProcessBasedModelling.timescale function that one can optionally extend (although in our experience the default behaviour covers almost all cases).

Main API function

ProcessBasedModelling.processes_to_mtkmodelFunction
processes_to_mtkmodel(processes::Vector, default::Vector = []; kw...)

Construct a ModelingToolkit.jl model/system using the provided processes and default processes. The model/system is not structurally simplified.

processes is a vector whose elements can be:

  • Any instance of a subtype of Process.
  • An Equation which is of the form variable ~ expression with variable a single variable resulting from an @variables call.
  • 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.

default is a vector that can contain the first two possibilities only as it contains default processes that may be assigned to variables introduced in processes but they don't themselves have an assigned process.

It is expected that downstream packages that use ProcessBasedModelling.jl to make a field-specific library implement a 1-argument version of processes_to_mtkmodel, or provide a wrapper function for it, and add a default value for default.

Keyword arguments

  • 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.
source

Predefined Process subtypes

ProcessBasedModelling.ParameterProcessType
ParameterProcess(variable, value = default_value(variable)) <: Process

The simplest process which equates a given variable to a constant value that is encapsulated in a parameter. If value isa Real, then a named parameter with the name of variable and _0 appended is created. Else, if valua isa Num then it is taken as the paremeter directly.

Example:

@variables T(t) = 0.5
+proc = ParameterProcess(T)

will create the equation T ~ T_0, where T_0 is a @parameter with default value 0.5.

source
ProcessBasedModelling.TimeDerivativeType
TimeDerivative(variable, expression [, τ])

The second simplest process that equates the time derivative of the variable to the given expression while providing some conveniences over manually constructing an Equation.

It creates the equation τ_$(variable) Differential(t)(variable) ~ expression by constructing a new @parameter with default value τ (if τ is already a @parameter, it is used as-is). If τ is not given, then 1 is used at its place and no parameter is created.

Note that if iszero(τ), then the process variable ~ expression is created.

source
ProcessBasedModelling.ExpRelaxationType
ExpRelaxation(variable, expression [, τ]) <: Process

A common process for creating an exponential relaxation of variable towards the given expression, with timescale τ. It creates the equation:

τn*Differential(t)(variable) ~ expression - variable

Where τn is a new named @parameter with the value of τ and name τ_($(variable)). If instead τ is nothing, then 1 is used in its place (this is the default behavior). If iszero(τ), then the equation variable ~ expression is created instead.

The convenience function

ExpRelaxation(process, τ)

allows converting an existing process (or equation) into an exponential relaxation by using the rhs(process) as the expression in the equation above.

source
ProcessBasedModelling.AdditionProcessType
AdditionProcess(process, added)

A convenience process for adding added to the rhs of the given process. added can be a Process or Equation, in which case it is checked that the lhs_variable matches. Otherwise, it can be an arbitrary expression.

source

Process API

This API describes how you can implement your own Process subtype, if the existing predefined subtypes don't fit your bill!

ProcessBasedModelling.ProcessType
Process

A new process must subtype Process and can be used in processes_to_mtkmodel. The type must extend the following functions from the module ProcessBasedModelling:

  • lhs_variable(p) which returns the variable the process describes (left-hand-side variable). There is a default implementation lhs_variable(p) = p.variable if the field exists.
  • rhs(p) which is the right-hand-side expression, i.e., the "actual" process.
  • (optional) timescale(p), which defaults to NoTimeDerivative.
  • (optional) lhs(p) which returns the left-hand-side. Let τ = timescale(p). Then default lhs(p) behaviour depends on τ as follows:
    • Just lhs_variable(p) if τ == NoTimeDerivative().
    • Differential(t)(p) if τ == nothing.
    • τ_var*Differential(t)(p) if τ isa Union{Real, Num}. If real, a new named parameter τ_var is created that has the prefix :τ_ and then the lhs-variable name and has default value τ. Else if Num, τ_var = τ as given.
    • Explicitly extend lhs_variable if the above do not suit you.
source
ProcessBasedModelling.NoTimeDerivativeType
ProcessBasedModelling.NoTimeDerivative()

Singleton value that is the default output of the timescale function for variables that do not vary in time autonomously, i.e., they have no d/dt derivative and hence the concept of a "timescale" does not apply to them.

source
ProcessBasedModelling.lhsFunction
ProcessBasedModelling.lhs(p::Process)

Return the left-hand-side of the equation that p represents as an Expression. If timescale is implemented for p, typically lhs does not need to be as well. See Process for more.

source

Utility functions

ProcessBasedModelling.has_variableFunction
has_variable(eq, var)

Return true if variable var exists in the equation(s) eq, false otherwise. Function works irrespectively if var is an @variable or @parameter.

source
ProcessBasedModelling.new_derived_named_parameterFunction
new_derived_named_parameter(variable, value, extra::String; kw...)

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.

For example,

@variables x(t)
+p = new_derived_named_parameter(x, 0.5, "τ")

Now p will be a parameter with name :τ_x and default value 0.5.

source
ProcessBasedModelling.@convert_to_parametersMacro
@convert_to_parameters vars...

Convert all variables vars into @parameters with name the same as vars and default value the same as the value of vars. The macro leaves unaltered inputs that are of type Num, assumming they are already parameters. It also replaces LiteralParameter inputs with its literal values. This macro is extremely useful to convert e.g., keyword arguments into named parameters, while also allowing the user to give custom parameter names.

Example:

``` julia> A, B = 0.5, 0.5 (0.5, 0.5)

julia> C = first(@parameters X = 0.5)

julia> @converttoparameters A B C 3-element Vector{Num}: A B X

julia> typeof(A) # A is not a number anymore! Num

julia> default_value(A) 0.5

julia> C # the binding C still corresponds to parameter named :X! X

source
diff --git a/dev/search_index.js b/dev/search_index.js index eb81de9..cd5d8b9 100644 --- a/dev/search_index.js +++ b/dev/search_index.js @@ -1,3 +1,3 @@ var documenterSearchIndex = {"docs": -[{"location":"","page":"Documentation","title":"Documentation","text":"ProcessBasedModelling","category":"page"},{"location":"#ProcessBasedModelling","page":"Documentation","title":"ProcessBasedModelling","text":"ProcessBasedModelling.jl\n\n(Image: docsdev) (Image: docsstable) (Image: CI) (Image: codecov) (Image: Package Downloads)\n\nProcessBasedModelling.jl is an extension to ModelingToolkit.jl (MTK) for building a model of equations using symbolic expressions. It is an alternative framework to MTK's native component-based modelling, 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. In many scientific fields this approach parallels the modelling reasoning of the researcher more closely than the \"components\" approach.\n\nBeyond 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. This allows ProcessBasedModelling.jl to:\n\nIterate over the processes and collect new variables that have been introduced by a provided process but do not themselves have a process assigned to them.\nFor these collected \"process-less\" variables:\nIf there is a default process defined, incorporate this one into the model\nIf there is no default process but the variable has a default value, equate the variable to a parameter that has the same default value and throw an informative warning.\nElse, throw an informative error saying exactly which originally provided variable introduced this new \"process-less\" variable.\nThrow an informative error if a variable has two processes assigned to it (by mistake).\n\nIn 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.\n\nProcessBasedModelling.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. Hence, the target application of ProcessBasedModelling.jl is to be a framework to develop field-specific libraries that offer predefined processes without themselves relying on the existence of context-specific predefined components. An example usage is in EnergyBalanceModels.jl.\n\nBesides the informative errors, ProcessBasedModelling.jl also\n\nProvides a couple of common process subtypes out of the box to accelerate development of field-specific libraries.\nMakes named MTK variables and parameters automatically, corresponding to parameters introduced by the by-default provided processes. This typically leads to intuitive names without being explicitly coded, while being possible to opt-out.\nProvides some utility functions for further building field-specific libraries.\n\nSee the documentation online for details on how to use this package as well as examples highlighting its usefulness.\n\n\n\n\n\n","category":"module"},{"location":"","page":"Documentation","title":"Documentation","text":"note: Basic familiarity with ModelingToolkit.jl\nThese docs assume that you have some basic familiarity with ModelingToolkit.jl. If you don't going through the introductory tutorial of ModelingToolkit.jl should be enough to get you started!","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"note: Default `t` is unitless\nLike ModelingToolkit.jl, ProcessBasedModelling.jl also exports t as the independent variable representing time. However, instead of the default t of ModelingToolkit.jl, here t is unitless. Do t = ModelingToolkit.t to obtain the unitful version of t.","category":"page"},{"location":"#Usage","page":"Documentation","title":"Usage","text":"","category":"section"},{"location":"","page":"Documentation","title":"Documentation","text":"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 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).","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"Once all the processes about the physical system are collected, they are given as a Vector to the processes_to_mtkmodel central function, similarly to how one gives a Vector of Equations to e.g., ModelingToolkit.ODESystem. This function also defines what quantifies as a \"process\" in more specificity.","category":"page"},{"location":"#Example","page":"Documentation","title":"Example","text":"","category":"section"},{"location":"","page":"Documentation","title":"Documentation","text":"Let's say we want to build the system of equations","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"dotz = x^2 - z \ndotx = 01y \ny = z - x","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"symbolically using ModelingToolkit.jl (MTK). We define","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"using ModelingToolkit\nusing OrdinaryDiffEq: Tsit5\n\n@variables t # independent variable _without_ units\n@variables z(t) = 0.0\n@variables x(t) # no default value\n@variables y(t) = 0.0","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"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.","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"note: ModelingToolkit.jl is re-exported\nProcessBasedModelling.jl re-exports the whole ModelingToolkit package, so you don't need to be using both of them, just using ProcessBasedModelling.","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"To make the equations we want, we can use MTK directly, and call","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"eqs = [\n Differential(t)(z) ~ x^2 - z\n Differential(t)(x) ~ 0.1y\n y ~ z - x\n]\n\nmodel = ODESystem(eqs, t; name = :example)\n\nequations(model)","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"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):","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"model = ODESystem(eqs[1:2], t; name = :example)\nmodel = structural_simplify(model)","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"ERROR: ExtraVariablesSystemException: The system is unbalanced.\nThere are 3 highest order derivative variables and 2 equations.\nMore variables than equations, here are the potential extra variable(s):\n z(t)\n x(t)\n y(t)","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"The error message is unhelpful as all variables are reported as \"potentially missing\". 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.","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"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 wants (which are either Processes or Equations as above).","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"Here is what the user defines to make the same system of equations via PBM:","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"using ProcessBasedModelling\n\nprocesses = [\n ExpRelaxation(z, x^2), # introduces x variable\n TimeDerivative(x, 0.1*y), # introduces y variable\n y ~ z - x, # can be an equation because LHS is single variable\n]","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"which is then given to","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"model = processes_to_mtkmodel(processes; name = :example)\nequations(model)","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"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.","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"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, telling us exactly which variable is missing, and because of which processes it is missing:","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"model = processes_to_mtkmodel(processes[[1, 3]])","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"ERROR: ArgumentError: Variable x was introduced in process of variable z(t).\nHowever, a process for x was not provided,\nthere is no default process for x, and x doesn't have a default value.\nPlease provide a process for variable x.","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"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:","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"model = processes_to_mtkmodel(processes[1:2])\nequations(model)","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"parameters(model)","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"and the warning thrown was:","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"┌ Warning: Variable y was introduced in process of variable x(t).\n│ However, a process for y was not provided,\n│ and there is no default process for it either.\n│ Since it has a default value, we make it a parameter by adding a process:\n│ `ParameterProcess(y)`.\n└ @ ProcessBasedModelling ...\\ProcessBasedModelling\\src\\make.jl:65","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"Lastly, processes_to_mtkmodel also allows the concept of \"default\" processes, that can be used for introduced \"process-less\" variables. Default processes are like processes and given as a 2nd argument to processes_to_mtkmodel. For example,","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"model = processes_to_mtkmodel(processes[1:2], processes[3:3])\nequations(model)","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"does not throw any warnings as it obtained a process for y from the given default processes.","category":"page"},{"location":"#Special-handling-of-timescales","page":"Documentation","title":"Special handling of timescales","text":"","category":"section"},{"location":"","page":"Documentation","title":"Documentation","text":"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 or ExpRelaxation processes, it is converted to a named @parameter by default:","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"processes = [\n ExpRelaxation(z, x^2, 2.0), # third argument is the timescale\n TimeDerivative(x, 0.1*y, 0.5), # third argument is the timescale\n y ~ z-x,\n]\n\nmodel = processes_to_mtkmodel(processes)\nequations(model)","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"parameters(model)","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"This special handling is also why each process can declare a timescale via the ProcessBasedModelling.timescale function that one can optionally extend (although in our experience the default behaviour covers almost all cases).","category":"page"},{"location":"#Main-API-function","page":"Documentation","title":"Main API function","text":"","category":"section"},{"location":"","page":"Documentation","title":"Documentation","text":"processes_to_mtkmodel","category":"page"},{"location":"#ProcessBasedModelling.processes_to_mtkmodel","page":"Documentation","title":"ProcessBasedModelling.processes_to_mtkmodel","text":"processes_to_mtkmodel(processes::Vector, default::Vector = []; kw...)\n\nConstruct a ModelingToolkit.jl model/system using the provided processes and default processes. The model/system is not structurally simplified.\n\nprocesses is a vector whose elements can be:\n\nAny instance of a subtype of Process.\nAn Equation which is of the form variable ~ expression with variable a single variable resulting from an @variables call.\nA 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.\n\ndefault is a vector that can contain the first two possibilities only as it contains default processes that may be assigned to variables introduced in processes but they don't themselves have an assigned process.\n\nIt is expected that downstream packages that use ProcessBasedModelling.jl to make a field-specific library implement a 1-argument version of processes_to_mtkmodel, or provide a wrapper function for it, and add a default value for default.\n\nKeyword arguments\n\ntype = ODESystem: the model type to make\nname = nameof(type): the name of the model\nindependent = t: the independent variable (default: @variables t). t is also exported by ProcessBasedModelling.jl for convenience.\n\n\n\n\n\n","category":"function"},{"location":"#predefined_processes","page":"Documentation","title":"Predefined Process subtypes","text":"","category":"section"},{"location":"","page":"Documentation","title":"Documentation","text":"ParameterProcess\nTimeDerivative\nExpRelaxation\nAdditionProcess","category":"page"},{"location":"#ProcessBasedModelling.ParameterProcess","page":"Documentation","title":"ProcessBasedModelling.ParameterProcess","text":"ParameterProcess(variable, value = default_value(variable)) <: Process\n\nThe simplest process which equates a given variable to a constant value that is encapsulated in a parameter. If value isa Real, then a named parameter with the name of variable and _0 appended is created. Else, if valua isa Num then it is taken as the paremeter directly.\n\nExample:\n\n@variables T(t) = 0.5\nproc = ParameterProcess(T)\n\nwill create the equation T ~ T_0, where T_0 is a @parameter with default value 0.5.\n\n\n\n\n\n","category":"type"},{"location":"#ProcessBasedModelling.TimeDerivative","page":"Documentation","title":"ProcessBasedModelling.TimeDerivative","text":"TimeDerivative(variable, expression [, τ])\n\nThe second simplest process that equates the time derivative of the variable to the given expression while providing some conveniences over manually constructing an Equation.\n\nIt creates the equation τ_$(variable) Differential(t)(variable) ~ expression by constructing a new @parameter with default value τ (if τ is already a @parameter, it is used as-is). If τ is not given, then 1 is used at its place and no parameter is created.\n\nNote that if iszero(τ), then the process variable ~ expression is created.\n\n\n\n\n\n","category":"type"},{"location":"#ProcessBasedModelling.ExpRelaxation","page":"Documentation","title":"ProcessBasedModelling.ExpRelaxation","text":"ExpRelaxation(variable, expression [, τ]) <: Process\n\nA common process for creating an exponential relaxation of variable towards the given expression, with timescale τ. It creates the equation:\n\nτn*Differential(t)(variable) ~ expression - variable\n\nWhere τn is a new named @parameter with the value of τ and name τ_($(variable)). If instead τ is nothing, then 1 is used in its place (this is the default behavior). If iszero(τ), then the equation variable ~ expression is created instead.\n\nThe convenience function\n\nExpRelaxation(process, τ)\n\nallows converting an existing process (or equation) into an exponential relaxation by using the rhs(process) as the expression in the equation above.\n\n\n\n\n\n","category":"type"},{"location":"#ProcessBasedModelling.AdditionProcess","page":"Documentation","title":"ProcessBasedModelling.AdditionProcess","text":"AdditionProcess(process, added)\n\nA convenience process for adding added to the rhs of the given process. added can be a Process or Equation, in which case it is checked that the lhs_variable matches. Otherwise, it can be an arbitrary expression.\n\n\n\n\n\n","category":"type"},{"location":"#Process-API","page":"Documentation","title":"Process API","text":"","category":"section"},{"location":"","page":"Documentation","title":"Documentation","text":"This API describes how you can implement your own Process subtype, if the existing predefined subtypes don't fit your bill!","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"Process\nProcessBasedModelling.lhs_variable\nProcessBasedModelling.rhs\nProcessBasedModelling.timescale\nProcessBasedModelling.NoTimeDerivative\nProcessBasedModelling.lhs","category":"page"},{"location":"#ProcessBasedModelling.Process","page":"Documentation","title":"ProcessBasedModelling.Process","text":"Process\n\nA new process must subtype Process and can be used in processes_to_mtkmodel. The type must extend the following functions from the module ProcessBasedModelling:\n\nlhs_variable(p) which returns the variable the process describes (left-hand-side variable). There is a default implementation lhs_variable(p) = p.variable if the field exists.\nrhs(p) which is the right-hand-side expression, i.e., the \"actual\" process.\n(optional) timescale(p), which defaults to NoTimeDerivative.\n(optional) lhs(p) which returns the left-hand-side. Let τ = timescale(p). Then default lhs(p) behaviour depends on τ as follows:\nJust lhs_variable(p) if τ == NoTimeDerivative().\nDifferential(t)(p) if τ == nothing.\nτ_var*Differential(t)(p) if τ isa Union{Real, Num}. If real, a new named parameter τ_var is created that has the prefix :τ_ and then the lhs-variable name and has default value τ. Else if Num, τ_var = τ as given.\nExplicitly extend lhs_variable if the above do not suit you.\n\n\n\n\n\n","category":"type"},{"location":"#ProcessBasedModelling.lhs_variable","page":"Documentation","title":"ProcessBasedModelling.lhs_variable","text":"ProcessBasedModelling.lhs_variable(p::Process)\n\nReturn the variable (a single symbolic variable) corresponding to p.\n\n\n\n\n\n","category":"function"},{"location":"#ProcessBasedModelling.rhs","page":"Documentation","title":"ProcessBasedModelling.rhs","text":"ProcessBasedModelling.rhs(p::Process)\n\nReturn the right-hand-side of the equation that p represents as an Expression. See Process for more.\n\n\n\n\n\n","category":"function"},{"location":"#ProcessBasedModelling.timescale","page":"Documentation","title":"ProcessBasedModelling.timescale","text":"ProcessBasedModelling.timescale(p::Process)\n\nReturn the timescale associated with p. See Process for more.\n\n\n\n\n\n","category":"function"},{"location":"#ProcessBasedModelling.NoTimeDerivative","page":"Documentation","title":"ProcessBasedModelling.NoTimeDerivative","text":"ProcessBasedModelling.NoTimeDerivative()\n\nSingleton value that is the default output of the timescale function for variables that do not vary in time autonomously, i.e., they have no d/dt derivative and hence the concept of a \"timescale\" does not apply to them.\n\n\n\n\n\n","category":"type"},{"location":"#ProcessBasedModelling.lhs","page":"Documentation","title":"ProcessBasedModelling.lhs","text":"ProcessBasedModelling.lhs(p::Process)\n\nReturn the left-hand-side of the equation that p represents as an Expression. If timescale is implemented for p, typically lhs does not need to be as well. See Process for more.\n\n\n\n\n\n","category":"function"},{"location":"#Utility-functions","page":"Documentation","title":"Utility functions","text":"","category":"section"},{"location":"","page":"Documentation","title":"Documentation","text":"default_value\nhas_variable\nnew_derived_named_parameter\n@convert_to_parameters\nLiteralParameter","category":"page"},{"location":"#ProcessBasedModelling.default_value","page":"Documentation","title":"ProcessBasedModelling.default_value","text":"default_value(x)\n\nReturn 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.\n\n\n\n\n\n","category":"function"},{"location":"#ProcessBasedModelling.has_variable","page":"Documentation","title":"ProcessBasedModelling.has_variable","text":"has_variable(eq, var)\n\nReturn true if variable var exists in the equation(s) eq, false otherwise. Function works irrespectively if var is an @variable or @parameter.\n\n\n\n\n\n","category":"function"},{"location":"#ProcessBasedModelling.new_derived_named_parameter","page":"Documentation","title":"ProcessBasedModelling.new_derived_named_parameter","text":"new_derived_named_parameter(variable, value, extra::String, prefix = true)\n\nIf 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 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.\n\nFor example,\n\n@variables x(t)\np = new_derived_named_parameter(x, 0.5, \"τ\")\n\nNow p will be a parameter with name :τ_x and default value 0.5.\n\n\n\n\n\n","category":"function"},{"location":"#ProcessBasedModelling.@convert_to_parameters","page":"Documentation","title":"ProcessBasedModelling.@convert_to_parameters","text":"@convert_to_parameters vars...\n\nConvert all variables vars into @parameters with name the same as vars and default value the same as the value of vars. The macro leaves unaltered inputs that are of type Num, assumming they are already parameters. It also replaces LiteralParameter inputs with its literal values. This macro is extremely useful to convert e.g., keyword arguments into named parameters, while also allowing the user to give custom parameter names.\n\nExample:\n\n``` julia> A, B = 0.5, 0.5 (0.5, 0.5)\n\njulia> C = first(@parameters X = 0.5)\n\njulia> @converttoparameters A B C 3-element Vector{Num}: A B X\n\njulia> typeof(A) # A is not a number anymore! Num\n\njulia> default_value(A) 0.5\n\njulia> C # the binding C still corresponds to parameter named :X! X\n\n\n\n\n\n","category":"macro"},{"location":"#ProcessBasedModelling.LiteralParameter","page":"Documentation","title":"ProcessBasedModelling.LiteralParameter","text":"LiteralParameter(p)\n\nA wrapper around a value p to indicate to new_derived_named_parameter or @convert_to_parameters to not convert the given parameter p into a named @parameters instance, but rather keep it as a numeric literal in the generated equations.\n\n\n\n\n\n","category":"type"}] +[{"location":"","page":"Documentation","title":"Documentation","text":"ProcessBasedModelling","category":"page"},{"location":"#ProcessBasedModelling","page":"Documentation","title":"ProcessBasedModelling","text":"ProcessBasedModelling.jl\n\n(Image: docsdev) (Image: docsstable) (Image: CI) (Image: codecov) (Image: Package Downloads)\n\nProcessBasedModelling.jl is an extension to ModelingToolkit.jl (MTK) for building a model of equations using symbolic expressions. It is an alternative framework to MTK's native component-based modelling, 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. In many scientific fields this approach parallels the modelling reasoning of the researcher more closely than the \"components\" approach.\n\nBeyond 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. This allows ProcessBasedModelling.jl to:\n\nIterate over the processes and collect new variables that have been introduced by a provided process but do not themselves have a process assigned to them.\nFor these collected \"process-less\" variables:\nIf there is a default process defined, incorporate this one into the model\nIf there is no default process but the variable has a default value, equate the variable to a parameter that has the same default value and throw an informative warning.\nElse, throw an informative error saying exactly which originally provided variable introduced this new \"process-less\" variable.\nThrow an informative error if a variable has two processes assigned to it (by mistake).\n\nIn 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.\n\nProcessBasedModelling.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. Hence, the target application of ProcessBasedModelling.jl is to be a framework to develop field-specific libraries that offer predefined processes without themselves relying on the existence of context-specific predefined components. An example usage is in EnergyBalanceModels.jl.\n\nBesides the informative errors, ProcessBasedModelling.jl also\n\nProvides a couple of common process subtypes out of the box to accelerate development of field-specific libraries.\nMakes named MTK variables and parameters automatically, corresponding to parameters introduced by the by-default provided processes. This typically leads to intuitive names without being explicitly coded, while being possible to opt-out.\nProvides some utility functions for further building field-specific libraries.\n\nSee the documentation online for details on how to use this package as well as examples highlighting its usefulness.\n\n\n\n\n\n","category":"module"},{"location":"","page":"Documentation","title":"Documentation","text":"note: Basic familiarity with ModelingToolkit.jl\nThese docs assume that you have some basic familiarity with ModelingToolkit.jl. If you don't going through the introductory tutorial of ModelingToolkit.jl should be enough to get you started!","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"note: Default `t` is unitless\nLike ModelingToolkit.jl, ProcessBasedModelling.jl also exports t as the independent variable representing time. However, instead of the default t of ModelingToolkit.jl, here t is unitless. Do t = ModelingToolkit.t to obtain the unitful version of t.","category":"page"},{"location":"#Usage","page":"Documentation","title":"Usage","text":"","category":"section"},{"location":"","page":"Documentation","title":"Documentation","text":"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 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).","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"Once all the processes about the physical system are collected, they are given as a Vector to the processes_to_mtkmodel central function, similarly to how one gives a Vector of Equations to e.g., ModelingToolkit.ODESystem. This function also defines what quantifies as a \"process\" in more specificity.","category":"page"},{"location":"#Example","page":"Documentation","title":"Example","text":"","category":"section"},{"location":"","page":"Documentation","title":"Documentation","text":"Let's say we want to build the system of equations","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"dotz = x^2 - z \ndotx = 01y \ny = z - x","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"symbolically using ModelingToolkit.jl (MTK). We define","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"using ModelingToolkit\nusing OrdinaryDiffEq: Tsit5\n\n@variables t # independent variable _without_ units\n@variables z(t) = 0.0\n@variables x(t) # no default value\n@variables y(t) = 0.0","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"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.","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"note: ModelingToolkit.jl is re-exported\nProcessBasedModelling.jl re-exports the whole ModelingToolkit package, so you don't need to be using both of them, just using ProcessBasedModelling.","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"To make the equations we want, we can use MTK directly, and call","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"eqs = [\n Differential(t)(z) ~ x^2 - z\n Differential(t)(x) ~ 0.1y\n y ~ z - x\n]\n\nmodel = ODESystem(eqs, t; name = :example)\n\nequations(model)","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"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):","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"model = ODESystem(eqs[1:2], t; name = :example)\nmodel = structural_simplify(model)","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"ERROR: ExtraVariablesSystemException: The system is unbalanced.\nThere are 3 highest order derivative variables and 2 equations.\nMore variables than equations, here are the potential extra variable(s):\n z(t)\n x(t)\n y(t)","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"The error message is unhelpful as all variables are reported as \"potentially missing\". 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.","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"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 wants (which are either Processes or Equations as above).","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"Here is what the user defines to make the same system of equations via PBM:","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"using ProcessBasedModelling\n\nprocesses = [\n ExpRelaxation(z, x^2), # introduces x variable\n TimeDerivative(x, 0.1*y), # introduces y variable\n y ~ z - x, # can be an equation because LHS is single variable\n]","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"which is then given to","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"model = processes_to_mtkmodel(processes; name = :example)\nequations(model)","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"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.","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"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, telling us exactly which variable is missing, and because of which processes it is missing:","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"model = processes_to_mtkmodel(processes[[1, 3]])","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"ERROR: ArgumentError: Variable x was introduced in process of variable z(t).\nHowever, a process for x was not provided,\nthere is no default process for x, and x doesn't have a default value.\nPlease provide a process for variable x.","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"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:","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"model = processes_to_mtkmodel(processes[1:2])\nequations(model)","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"parameters(model)","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"and the warning thrown was:","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"┌ Warning: Variable y was introduced in process of variable x(t).\n│ However, a process for y was not provided,\n│ and there is no default process for it either.\n│ Since it has a default value, we make it a parameter by adding a process:\n│ `ParameterProcess(y)`.\n└ @ ProcessBasedModelling ...\\ProcessBasedModelling\\src\\make.jl:65","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"Lastly, processes_to_mtkmodel also allows the concept of \"default\" processes, that can be used for introduced \"process-less\" variables. Default processes are like processes and given as a 2nd argument to processes_to_mtkmodel. For example,","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"model = processes_to_mtkmodel(processes[1:2], processes[3:3])\nequations(model)","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"does not throw any warnings as it obtained a process for y from the given default processes.","category":"page"},{"location":"#Special-handling-of-timescales","page":"Documentation","title":"Special handling of timescales","text":"","category":"section"},{"location":"","page":"Documentation","title":"Documentation","text":"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 or ExpRelaxation processes, it is converted to a named @parameter by default:","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"processes = [\n ExpRelaxation(z, x^2, 2.0), # third argument is the timescale\n TimeDerivative(x, 0.1*y, 0.5), # third argument is the timescale\n y ~ z-x,\n]\n\nmodel = processes_to_mtkmodel(processes)\nequations(model)","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"parameters(model)","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"This special handling is also why each process can declare a timescale via the ProcessBasedModelling.timescale function that one can optionally extend (although in our experience the default behaviour covers almost all cases).","category":"page"},{"location":"#Main-API-function","page":"Documentation","title":"Main API function","text":"","category":"section"},{"location":"","page":"Documentation","title":"Documentation","text":"processes_to_mtkmodel","category":"page"},{"location":"#ProcessBasedModelling.processes_to_mtkmodel","page":"Documentation","title":"ProcessBasedModelling.processes_to_mtkmodel","text":"processes_to_mtkmodel(processes::Vector, default::Vector = []; kw...)\n\nConstruct a ModelingToolkit.jl model/system using the provided processes and default processes. The model/system is not structurally simplified.\n\nprocesses is a vector whose elements can be:\n\nAny instance of a subtype of Process.\nAn Equation which is of the form variable ~ expression with variable a single variable resulting from an @variables call.\nA 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.\n\ndefault is a vector that can contain the first two possibilities only as it contains default processes that may be assigned to variables introduced in processes but they don't themselves have an assigned process.\n\nIt is expected that downstream packages that use ProcessBasedModelling.jl to make a field-specific library implement a 1-argument version of processes_to_mtkmodel, or provide a wrapper function for it, and add a default value for default.\n\nKeyword arguments\n\ntype = ODESystem: the model type to make\nname = nameof(type): the name of the model\nindependent = t: the independent variable (default: @variables t). t is also exported by ProcessBasedModelling.jl for convenience.\n\n\n\n\n\n","category":"function"},{"location":"#predefined_processes","page":"Documentation","title":"Predefined Process subtypes","text":"","category":"section"},{"location":"","page":"Documentation","title":"Documentation","text":"ParameterProcess\nTimeDerivative\nExpRelaxation\nAdditionProcess","category":"page"},{"location":"#ProcessBasedModelling.ParameterProcess","page":"Documentation","title":"ProcessBasedModelling.ParameterProcess","text":"ParameterProcess(variable, value = default_value(variable)) <: Process\n\nThe simplest process which equates a given variable to a constant value that is encapsulated in a parameter. If value isa Real, then a named parameter with the name of variable and _0 appended is created. Else, if valua isa Num then it is taken as the paremeter directly.\n\nExample:\n\n@variables T(t) = 0.5\nproc = ParameterProcess(T)\n\nwill create the equation T ~ T_0, where T_0 is a @parameter with default value 0.5.\n\n\n\n\n\n","category":"type"},{"location":"#ProcessBasedModelling.TimeDerivative","page":"Documentation","title":"ProcessBasedModelling.TimeDerivative","text":"TimeDerivative(variable, expression [, τ])\n\nThe second simplest process that equates the time derivative of the variable to the given expression while providing some conveniences over manually constructing an Equation.\n\nIt creates the equation τ_$(variable) Differential(t)(variable) ~ expression by constructing a new @parameter with default value τ (if τ is already a @parameter, it is used as-is). If τ is not given, then 1 is used at its place and no parameter is created.\n\nNote that if iszero(τ), then the process variable ~ expression is created.\n\n\n\n\n\n","category":"type"},{"location":"#ProcessBasedModelling.ExpRelaxation","page":"Documentation","title":"ProcessBasedModelling.ExpRelaxation","text":"ExpRelaxation(variable, expression [, τ]) <: Process\n\nA common process for creating an exponential relaxation of variable towards the given expression, with timescale τ. It creates the equation:\n\nτn*Differential(t)(variable) ~ expression - variable\n\nWhere τn is a new named @parameter with the value of τ and name τ_($(variable)). If instead τ is nothing, then 1 is used in its place (this is the default behavior). If iszero(τ), then the equation variable ~ expression is created instead.\n\nThe convenience function\n\nExpRelaxation(process, τ)\n\nallows converting an existing process (or equation) into an exponential relaxation by using the rhs(process) as the expression in the equation above.\n\n\n\n\n\n","category":"type"},{"location":"#ProcessBasedModelling.AdditionProcess","page":"Documentation","title":"ProcessBasedModelling.AdditionProcess","text":"AdditionProcess(process, added)\n\nA convenience process for adding added to the rhs of the given process. added can be a Process or Equation, in which case it is checked that the lhs_variable matches. Otherwise, it can be an arbitrary expression.\n\n\n\n\n\n","category":"type"},{"location":"#Process-API","page":"Documentation","title":"Process API","text":"","category":"section"},{"location":"","page":"Documentation","title":"Documentation","text":"This API describes how you can implement your own Process subtype, if the existing predefined subtypes don't fit your bill!","category":"page"},{"location":"","page":"Documentation","title":"Documentation","text":"Process\nProcessBasedModelling.lhs_variable\nProcessBasedModelling.rhs\nProcessBasedModelling.timescale\nProcessBasedModelling.NoTimeDerivative\nProcessBasedModelling.lhs","category":"page"},{"location":"#ProcessBasedModelling.Process","page":"Documentation","title":"ProcessBasedModelling.Process","text":"Process\n\nA new process must subtype Process and can be used in processes_to_mtkmodel. The type must extend the following functions from the module ProcessBasedModelling:\n\nlhs_variable(p) which returns the variable the process describes (left-hand-side variable). There is a default implementation lhs_variable(p) = p.variable if the field exists.\nrhs(p) which is the right-hand-side expression, i.e., the \"actual\" process.\n(optional) timescale(p), which defaults to NoTimeDerivative.\n(optional) lhs(p) which returns the left-hand-side. Let τ = timescale(p). Then default lhs(p) behaviour depends on τ as follows:\nJust lhs_variable(p) if τ == NoTimeDerivative().\nDifferential(t)(p) if τ == nothing.\nτ_var*Differential(t)(p) if τ isa Union{Real, Num}. If real, a new named parameter τ_var is created that has the prefix :τ_ and then the lhs-variable name and has default value τ. Else if Num, τ_var = τ as given.\nExplicitly extend lhs_variable if the above do not suit you.\n\n\n\n\n\n","category":"type"},{"location":"#ProcessBasedModelling.lhs_variable","page":"Documentation","title":"ProcessBasedModelling.lhs_variable","text":"ProcessBasedModelling.lhs_variable(p::Process)\n\nReturn the variable (a single symbolic variable) corresponding to p.\n\n\n\n\n\n","category":"function"},{"location":"#ProcessBasedModelling.rhs","page":"Documentation","title":"ProcessBasedModelling.rhs","text":"ProcessBasedModelling.rhs(p::Process)\n\nReturn the right-hand-side of the equation that p represents as an Expression. See Process for more.\n\n\n\n\n\n","category":"function"},{"location":"#ProcessBasedModelling.timescale","page":"Documentation","title":"ProcessBasedModelling.timescale","text":"ProcessBasedModelling.timescale(p::Process)\n\nReturn the timescale associated with p. See Process for more.\n\n\n\n\n\n","category":"function"},{"location":"#ProcessBasedModelling.NoTimeDerivative","page":"Documentation","title":"ProcessBasedModelling.NoTimeDerivative","text":"ProcessBasedModelling.NoTimeDerivative()\n\nSingleton value that is the default output of the timescale function for variables that do not vary in time autonomously, i.e., they have no d/dt derivative and hence the concept of a \"timescale\" does not apply to them.\n\n\n\n\n\n","category":"type"},{"location":"#ProcessBasedModelling.lhs","page":"Documentation","title":"ProcessBasedModelling.lhs","text":"ProcessBasedModelling.lhs(p::Process)\n\nReturn the left-hand-side of the equation that p represents as an Expression. If timescale is implemented for p, typically lhs does not need to be as well. See Process for more.\n\n\n\n\n\n","category":"function"},{"location":"#Utility-functions","page":"Documentation","title":"Utility functions","text":"","category":"section"},{"location":"","page":"Documentation","title":"Documentation","text":"default_value\nhas_variable\nnew_derived_named_parameter\n@convert_to_parameters\nLiteralParameter","category":"page"},{"location":"#ProcessBasedModelling.default_value","page":"Documentation","title":"ProcessBasedModelling.default_value","text":"default_value(x)\n\nReturn 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.\n\n\n\n\n\n","category":"function"},{"location":"#ProcessBasedModelling.has_variable","page":"Documentation","title":"ProcessBasedModelling.has_variable","text":"has_variable(eq, var)\n\nReturn true if variable var exists in the equation(s) eq, false otherwise. Function works irrespectively if var is an @variable or @parameter.\n\n\n\n\n\n","category":"function"},{"location":"#ProcessBasedModelling.new_derived_named_parameter","page":"Documentation","title":"ProcessBasedModelling.new_derived_named_parameter","text":"new_derived_named_parameter(variable, value, extra::String; kw...)\n\nIf 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.\n\nFor example,\n\n@variables x(t)\np = new_derived_named_parameter(x, 0.5, \"τ\")\n\nNow p will be a parameter with name :τ_x and default value 0.5.\n\n\n\n\n\n","category":"function"},{"location":"#ProcessBasedModelling.@convert_to_parameters","page":"Documentation","title":"ProcessBasedModelling.@convert_to_parameters","text":"@convert_to_parameters vars...\n\nConvert all variables vars into @parameters with name the same as vars and default value the same as the value of vars. The macro leaves unaltered inputs that are of type Num, assumming they are already parameters. It also replaces LiteralParameter inputs with its literal values. This macro is extremely useful to convert e.g., keyword arguments into named parameters, while also allowing the user to give custom parameter names.\n\nExample:\n\n``` julia> A, B = 0.5, 0.5 (0.5, 0.5)\n\njulia> C = first(@parameters X = 0.5)\n\njulia> @converttoparameters A B C 3-element Vector{Num}: A B X\n\njulia> typeof(A) # A is not a number anymore! Num\n\njulia> default_value(A) 0.5\n\njulia> C # the binding C still corresponds to parameter named :X! X\n\n\n\n\n\n","category":"macro"},{"location":"#ProcessBasedModelling.LiteralParameter","page":"Documentation","title":"ProcessBasedModelling.LiteralParameter","text":"LiteralParameter(p)\n\nA wrapper around a value p to indicate to new_derived_named_parameter or @convert_to_parameters to not convert the given parameter p into a named @parameters instance, but rather keep it as a numeric literal in the generated equations.\n\n\n\n\n\n","category":"type"}] }