Asides: A collection of information pages¶
Understanding units¶
Some basic units have been defined and built into libCellML, others you can define by combining the built-in ones using scaling factors and exponents, or you can define your own from scratch if need be.
There are four different kinds of units used here: irreducible units, built-in units, derived or combination units, and custom irreducible units.
Irreducible units¶
The first are called irreducible because they represent the physical base quantities which cannot be further simplified:
length (
metre)time (
second)amount of a substance (
mole)temperature (
kelvin)mass (
kilogram)electrical current (
ampere)luminous intensity (
candela)non-dimensional (
dimensionless)
These irreducible units can be used to create all other physically-based units by combining them using different exponents, multipliers, and prefixes.
Built-in units¶
Some of these combinations form our second type of units, the built-in units, these being common relationships which have been constructed from combinations of the irreducible units. The combinations can involve:
A scaling factor (the units
millisecondis equivalent tosecondand a factor of 0.001);A combination of units (a
coulombis asecondmultiplied by anampere);Powers of units (a
hertzhas a base ofsecondwith an exponent of -1); andAny combination of the above.
A list of pre-existing built-in convenience units is shown in the Built-in Units table, along with their relationships to the irreducible units.
Combination or derived units¶
The third type of units are those combinations which users can define for themselves based on the built-in units, the irreducible units, any other units already created, or (see below) their own custom irreducible units.
For example, let’s say that you want to simulate the time variable, \(t\), in units of milliseconds.
This isn’t one of the built-in units, so you’ll need to define it, but it’s easy to see that it’s based on the built-in second, but needs a scaling factor.
For convenience libCellML gives a variety of options for defining such scaling factors:
Either through the use of named prefixes which are listed on the Interpretation of Units page, eg:
millisecondissecondwithprefix="milli";By defining an integer or integer string as a prefix which represents the \(log_{10}\) of the scaling factor, eg:
millisecondissecondwithprefix=-3gives a scaling factor of \(10^{-3}=0.001\). NB: using an integer string likeprefix="-3"gives the same result; andBy defining the scaling factor directly, as a multiplier, eg:
millisecondissecondwithmultiplier=0.001.
The overloaded argument option list is shown below for each language. Please check the API Units documentation for details.
void addUnit(const std::string &reference, const std::string &prefix, double exponent = 1.0,
double multiplier = 1.0, const std::string &id = "");
void addUnit(const std::string &reference, Prefix prefix, double exponent = 1.0,
double multiplier = 1.0, const std::string &id = "");
void addUnit(const std::string &reference, int prefix, double exponent,
double multiplier = 1.0, const std::string &id = "");
void addUnit(const std::string &reference, double exponent, const std::string &id = "");
void addUnit(const std::string &reference);
addUnit(reference, prefix, exponent=1, multiplier=1)
addUnit(reference, exponent)
addUnit(reference)
Note that reference can be another unit name string or a StandardUnits enum, and prefix can be a string or an integer.
To create a Units item you need will follow the same basic steps as other entities: declare it, name it, define it, and then add it in.
For example:
// Declare, name, and define a "millisecond" unit pointer.
auto ms = libcellml::Units::create("millisecond");
// The manner of specification here is agnostic: all three definitions are identical.
ms->addUnit("second", "milli"); // reference unit and built-in prefix
// OR
ms->addUnit("second", 1.0, -3); // reference unit, multiplier, exponent
// OR
ms->addUnit("second", 1.0, 0, 0.001); // reference unit, multiplier, exponent
from libcellml import Units
# Declare, name, and define a "millisecond" unit pointer.
ms = Units("millisecond")
# The manner of specification here is agnostic: all three definitions are identical.
ms.addUnit("second", "milli") # reference unit and built-in prefix
# OR
ms.addUnit("second", -3, 0.001) # reference unit, exponent, multiplier
# OR
ms.addUnit("second", 1, 1.0, 0.01) # reference unit, prefix, exponent, multiplier
Units can be defined based on one another as well.
For example, after defining our millisecond units, we could then use this definition to define the per_millisecond units by simply including it with an exponent of -1:
// Define a per_millisecond unit based on millisecond^-1:
per_ms->addUnit(ms, -1.0);
# Defining a per_millisecond unit based on millisecond^-1.
per_ms.addUnit(ms, -1.0) # reference unit, exponent
Custom irreducible units¶
The final type of unit is a custom irreducible unit. While this is not common in purely physical models (all of the seven physical attributes are already included), for times when you’re modelling something non-physical (such as our numbers of sharks or fishes), you’re able to define your own. Here’s an example.
// Create a custom irreducible unit named "banana".
auto uBanana = libcellml::Units::create("banana");
// Note that when a UnitsPtr is defined with a name only (that is, without any
// calls to the addUnit(...) function), it is effectively irreducible.
// Create a new compound unit based on the "banana" unit above.
auto uBunchOfBananas = libcellml::Units::create("bunch_of_bananas");
u2->addUnit("banana", 5.0); // include bananas^5 in the bunch_of_bananas unit
from libcellml import Units
# Create a custom irreducible unit named "banana".
uBanana = Units("banana")
# Note that when a Units is defined with a name only, it is effectively irreducible.
# Create a new compound unit based on the "banana" unit above.
uBunchOfBananas = Units("bunch_of_bananas")
uBunchOfBananas.addUnit("banana", 5.0) # include bananas^5 in the bunch_of_bananas unit
Constants vs Variables¶
In your model, there is mathematics.
And in your mathematics, there are variables.
Some of these variables will, well, vary in value, but others might not.
This note is to help you understand the different ways in which variable elements are assigned value, and what it means to your whole model’s interpretation.
There are six ways to assign a value to a variable element:
1. Assign to a number in a math block¶
These are classed as “eternal truths”, and are statements which are held to be true throughout the entirety of the simulation.
If you use a math block statement to assign value to a variable element using a hard-coded numerical value, then that value can never be changed.
Any attempt to change it will make the model over-defined.
In the example below, A is a constant with value 1 for the entire simulation.
// Setting A = 1 using a math block:
std::string myMathString = "<math><apply><eq/><ci>A</ci><cn cellml:units=\"dimensionless\">1</ci></apply></math>";
myComponent->appendMath(myMathString);
# Setting A = 1 using a math block:
my_math_string = '<math><apply><eq/><ci>A</ci><cn cellml:units="dimensionless">1</ci></apply></math>'
my_component.appendMath(my_math_string)
Show CellML
<math>
<apply><eq/>
<ci>A</ci>
<cn cellml:units="dimensionless">1</cn>
</apply>
</math>
2. Assign to another variable or expression in a math block¶
As above, these are classed as “eternal truths”, but of course the actual value held by the variables may change as the expression is evaluated at different times.
The math statements are not assignments (as you’d expect in a programming language), but rather equations (as you’d expect in mathematics).
This means that writing \(B = C\) is equivalent to writing \(C = B\), just as it is in normal mathematics, and can be read “The value of B is always the same as the value of C”.
Compare that to the assignment statement we’re familiar with in programming, B := C (“set the value of B to be the current value of C”) which is not the same as C := B (“set the value of C to be the current value of B”).
Of course \(B = C\) is perhaps the simplest equation possible, and yours will be a lot more complicated than this!
// Setting B = C using a math block:
std::string myMathString = "<math><apply><eq/><ci>B</ci><ci>C</ci></apply></math>";
myComponent->appendMath(myMathString);
# Setting B = C using a math block:
my_math_string = '<math><apply><eq/><ci>B</ci><ci>C</ci></apply></math>'
my_component.appendMath(my_math_string)
3. Assign as an initial value attribute¶
The term “initial value” might lead you to believe that the initial_value attributes are only used for variables of integration.
In reality, any variable which will have a non-constant value during the simulation may be set using this attribute.
This includes values which are changed by resets, or by solving a differential or algebraic equation.
The only situation where you don’t need to set a variable’s value like this is where the variable is defined by evaluation of a math block statement.
In that situation, having an initial value specified as well as a maths definition will make the model over-defined.
// Setting the initial value of B:
myComponent->variable("B")->setInitialValue(1);
// Setting B = C using a math block:
std::string myMathString = "<math><apply><eq/><ci>B</ci><ci>C</ci></apply></math>";
myComponent->appendMath(myMathString);
# Setting the initial value of B:
my_component->variable('B')->setInitialValue(1)
# Setting B = C using a math block:
my_math_string = '<math><apply><eq/><ci>B</ci><ci>C</ci></apply></math>'
my_component.appendMath(my_math_string)
Show CellML
<!-- The variable B can change in value during the simulation. -->
<variable name="B" initial_value="1" units="dimensionless" />
<!-- The variable C does not use initialisation, as it is
constrained by the maths below to always have the same
value as B, which has been initialised. -->
<variable name="C" units="dimensionless" />
<math>
<apply><eq/>
<ci>B</ci>
<ci>C</ci>
</apply>
</math>
<!-- The statement above is an equation, not an assignment,
so is equivalent to: -->
<math>
<apply><eq/>
<ci>C</ci>
<ci>B</ci>
</apply>
</math>
4. Assign by solving a differential equation¶
This is similar to point 2 above, in that the variable’s definition is contained within a mathematical statement.
The difference is that when a variable element is a state variable (ie: the numerator of a differential equation), it must have a value which is specified (either locally or somewhere in its equivalent variable set) by an initial_value attribute.
// Setting the initial value of E:
myComponent->variable("E")->setInitialValue(3);
// Solving for E using a differential equation:
std::string myDifferentialEquation = "<math>\n"
" <apply><eq/>\n"
" <diff>\n"
" <ci>E</ci>\n"
" <bvar>t</bvar>\n"
" </diff>\n"
" <cn cellml:units=\"dimensionless\">1</cn>\n"
" </apply>\n"
"</math>";
myComponent->appendMath(myDifferentialEquation);
# Setting the initial value of E:
my_component->variable('E')->setInitialValue(3)
# Solving for E using a differential equation:
my_differential_equation = '<math>\n'
' <apply><eq/>\n'
' <diff>\n'
' <ci>E</ci>\n'
' <bvar>t</bvar>\n'
' </diff>\n'
' <cn cellml:units="dimensionless">1</cn>\n'
' </apply>\n'
'</math>'
my_component.appendMath(my_math_string)
Show CellML
<!-- A state variable must have an initial value specified.
Note that this could be applied to a connected variable in another component. -->
<variable name="E" units="dimensionless" initial_value="3" />
<math>
<apply><eq/>
<diff>
<ci>E</ci>
<bvar>t</bvar>
</diff>
<cn cellml:units="dimensionless">1</cn>
</apply>
</math>
5. Assign by applying a reset¶
Resets allow a variable to have prescribed discontinuity in value at some stage in the simulation process.
This means that the value of the variable must be allowed to change (that is, it must not appear in a statement like in point 1 above).
In contrast to the mathematical equations defined inside math elements, the MathML statements within a reset’s reset_value block is an assignment.
By their nature, the mathematics of resets are only temporarily true, and so anything written there is a one-off assignment when the reset item is active.
This means that even though the reset variable’s value can’t be defined as a constant (as in point 1), it could still be part of an equation (as in point 2).
It must not be a variable of integration (as in point 4) or a base variable (as in point 6).
6. Assign as a variable of integration¶
These don’t need to be given a value inside CellML as their value will be set during the simulation.