================= Custom components ================= FONSim provides several ready-to-use components in its :doc:`standard library <../autodoc/fonsim.components>`. 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 :py:class:`.Container` component. Full script, including usage example: :download:`custom_component.py <../../../examples/custom_component.py>`. Component definition ==================== Components are defined as Python classes that inherit from the base class :py: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. .. literalinclude:: ../../../examples/custom_component.py :start-at: class Container :end-at: super().__init__ :lineno-match: Next, we need to define the possibilities for the component to interact with other components by instantiating and adding a :py:class:`.Terminal`. This is done in the following five lines: .. literalinclude:: ../../../examples/custom_component.py :start-at: set_terminals :end-at: 'massflow' :lineno-match: 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: .. literalinclude:: ../../../examples/custom_component.py :start-at: set_states :end-at: label :lineno-match: 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). .. literalinclude:: ../../../examples/custom_component.py :start-at: @self.auto_state :end-at: self.update_state :lineno-match: 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: .. literalinclude:: ../../../examples/custom_component.py :start-at: # With derivative specified :end-at: self.evaluate :lineno-match: 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, .. literalinclude:: ../../../examples/custom_component.py :start-at: system.add(Container :end-at: system.add(Container :lineno-match: 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.