Defining functions
Functions in psymple
are captured by functional ported objects. A system of multivariate functions of the form
is captured by a functional ported object with a set of parameter assignments modelling \(y_i = f_i(t,\underline{d}_i)\) for each \(i\), where \(\underline{d}_i \subseteq \underline{d}\).
Example
Example: gravitational attraction
The magnitude \(F\) of the gravitational attraction force between two bodies with spherically-symmetric densities is given by
where \(G\) is the gravitational constant, \(m_1\) and \(m_2\) are the masses of the two bodies, and \(r\) is the scalar distance between their centres of mass.
In psymple
, this equation can be captured as follows.
from psymple.build import FunctionalPortedObject
f_gravity = FunctionalPortedObject(
name="f_gravity", # (1)!
input_ports=["G", "m_1", "m_2", "r"],
assignments=[("F", "G*m_1*m_2 / (r**2)")],
)
name
is used to identify the ports of a system, so should be descriptive and unique.
There are multiple syntaxes for specifying assignments. The following are all equivalent in this example:
When psymple
builds the f_gravity
functional ported object, it:
- Builds a
ParameterAssignment
instance for each piece of data in the listassignments
. In this case it builds the parameter assignmentParameterAssignment(symbol="F", expression="G*m_1*m_2 / (r**2)")
, - Creates the parameter symbol
F
, - Creates the set of free symbols
G
,m_1
,m_2
andr
.
Next, it creates input ports as specified by the user. In this case, it matches each element of the specified input ports ["G","m_1","m_2","r"]
to its respective free symbol. It then creates output ports for each assignment: in this case a single output port for the parameter symbol F
, which can be used to expose the expression "G*m_1*m_2 / (r**2)"
.
Next steps
Once functional ported objects are defined, they can be used to define composite models, or define a simulable system
Notes on best practice
Automatic port creation
Functional ported objects are able to automatically create input ports. As in the example, psymple
collects the free symbols from all symbols on the right-hand side of assignments. If the argument input_ports
is not provided, then every free symbol is exposed as an input port.
Therefore in the example above, it is equivalent to call:
from psymple.build import FunctionalPortedObject
f_gravity = FunctionalPortedObject(
name="f_gravity",
assignments=[("F", "G*m_1*m_2 / (r**2)")],
)
The automatic creation of input ports can be overridden: see the documentation of FunctionalPortedObject
for full details.
When to specify ports
In practice, the only reason to specify an input port is in the case where a port is to be given a default value, when this should be specified in the input_ports
argument. In the above example, the gravitational constant \(G = 6.67 \times 10^{-11}\) might be assigned. This can still be overridden later in model construction or at simulation. This can be done as follows:
from psymple.build import FunctionalPortedObject
f_gravity = FunctionalPortedObject(
name="f_gravity",
input_ports=[("G", 6.67e-11)],
assignments=[("F", "G*m_1*m_2 / (r**2)")],
)
System parameters
It is also possible to assign meaning to the symbol G
itself without having to define it through an input port. See defining a system.
There are multiple syntaxes for specifying default values at input ports. The following are all equivalent: