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)")],
)
nameis 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
ParameterAssignmentinstance 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_2andr.
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: