Custom components

FONSim provides several ready-to-use components in its standard library. However, there’s a good chance you will now and then want to define and use your own components. This tutorial discusses how to do this by redefining the Container component.

Full script, including usage example: custom_component.py.

Component definition

Components are defined as Python classes that inherit from the base class Component. The following snippet defines our new Container class and initializes the base class, and also provides some documentation. n addition to the mandatory label argument, we have added two custom arguments: the fluid and the container volume, as both of these influence the behavior of the component.

11class Container(fons.Component):
12    """
13    A Container object is a (by default, empty) container or tank.
14    It has one terminal named 'a'.
15    It has one state named 'mass',
16    which represents the mass of the fluid inside the container.
17
18    :param label: label
19    :param fluid: fluid, must be compressible
20    :param volume: volume of the container in m^3.
21    """
22    def __init__(self, label=None, fluid=None, volume=None):
23        super().__init__(label)

Next, we need to define the possibilities for the component to interact with other components by instantiating and adding a Terminal. This is done in the following five lines:

26        self.set_terminals(
27            fons.Terminal('a',
28                          [fons.Variable('pressure', 'across',  label='p',
29                                         initial_value=cnorm.pressure_atmospheric,
30                                         range=(0, np.Inf)),
31                           fons.Variable('massflow', 'through', label='mf')]))

The second line gives the terminal its label ‘a’, and the next three lines add the variables that constitute the terminal. Two terminals from two different components that are connected together should have the same variable _keys_. In line with other components in the FONSim standard library, we are working in a simple fluidical domain with the keys ‘pressure’ and ‘massflow’ (we assume the fluid temperature is constant throughout the system). Following classical system modelling theory, the former is an ‘across’ variable while the latter is an ‘through’ variable. Both variables receive labels, respectively ‘p’ and ‘mf’, to refer to them in the further component definition.

Note

At each internal node, which internally is a group of terminals that are connected together, FONSim enforces that at any simulation time step all across variables are equal in value and that all through variables sum to zero. Internal nodes are normally not of relevance to the user and they do not represent physical elements (e.g. T-piece).

The container has a single state: the fluid mass ‘m’ inside. Its initial value is set to the fluid mass that corresponds to standard temperature and pressure (STP) (following the ideal gas law). This is easily added with the following two lines:

32        self.set_states(fons.Variable('mass', 'local', label='m',

After the terminals and states have been defined, we need to describe the behaviour of the component. This is done by adding the two class methods update_state and evaluate.

The update_state method, shown in the excerpt below, defines how the state should change over time given particular values at the terminals. It is formulated as an explicit finite difference equation (hence the timestep dt is provided as argument). The two arguments ‘mf’ and ‘m’ should match with the variable labels given earlier. In the case of our container, this is a simple finite integral (the mass inside changes over time as there is massflow in- and out).

36        @self.auto_state
37        def update_state(dt, mf, m):
38            m_new = m + mf*dt
39            return {'m': m_new}
40        self.update_state = update_state

The latter (evaluate) is shown in the following snippet and defines how the terminal and state variable values relate. It is formulated as an implicit equation. In the case of our container, it is the ideal gas law for a fluid with a constant temperature. One can add the argument t which will receive the value of the current simulation time. Here it is left out because the behaviour does not depend on time.

This is the end of the component definition. FONSim takes care of estimating the derivatives. Alternatively, we can specify them ourselves, which is discussed in the next section.

Manual derivative definition

Sometimes it is necessary for stability to manually specify the derivative. It also increases the simulation speed. The following example shows the two methods with the derivative definitions:

53        # With derivative specified
54        @self.auto_state
55        def update_state(dt, mf, m):
56            jacobian = {}
57            m_new = m + dt * mf
58            jacobian['m/p'] = 0
59            jacobian['m/mf'] = dt
60            return {'m': m_new}, jacobian
61        self.update_state = update_state
62
63        @self.auto
64        def evaluate(p, m):
65            mass_stp = volume*fluid.rho_stp
66            values, jacobian = np.zeros([1], dtype=float), [{}]
67            values[0] = m*cnorm.pressure_atmospheric - mass_stp*p
68            jacobian[0]['m'] = cnorm.pressure_atmospheric
69            jacobian[0]['p'] = -mass_stp
70            return values, jacobian
71        self.evaluate = evaluate

For the state update, the derivatives are specified in the form of a dictionary jacobian with as keys strings of the partial derivatives. For the evaluate method, they are formulated as a list of dictionaries (also named jacobian).

Usage

To finish, the component can be used like any other component. For example,

86system.add(Container('container', fluid=fluid, volume=250e-6))

creates and adds a container to the system object with a volume of 250 ml. If desired, you can run the simulation coded in the example file and view the resulting plot.

Conclusion

In this tutorial, we have discussed how to create a custom component in FONSim by redefining the Container component. We have explained the component definition process, including how to define the terminals, states, and behavior of the component. We have also demonstrated how to use the custom component by creating an instance of it, and using it in a system. With this information, you should now be able to create your own custom components and use them in your FONSim simulations.