.. Author: elenia@TUBS Copyright 2024 elenia This file is part of eELib, which is free software under the terms of the GNU GPL Version 3. ######################## Configuration of a Model ######################## A model, as explained in the :ref:`glossary `, represents a real process or component in form of code. It follows the general structure of *Input* - *internal calculation* - *output*, which is triggered by its corresponding :ref:`simulator ` that needs to be existent for using the model in simulations. The model is just representing the calculation steps and an entity (see the :ref:`glossary `) is the instantiation of a model. So within a scenario it is possible to have multiple entities (like battery storages) for one model type (battery storage model). This page explains the structure of a ``_model.py`` to enable you to create your own model. As there can be different implementations of a device type (like storage system), the can be more than one class within a ``_model.py`` (like a very detailled model and a generic model). .. note:: All code-blocks derive from ``charging_station_model.py`` as of (01/24) if not stated otherwise. Imports ======= .. code-block:: :lineno-start: 1 :caption: ``Import`` relevant packages for the model calculation import warnings import math from eelib.utils import cos_phi_fix -------------------------------- Class definition ================ .. code-block:: :lineno-start: 13 :caption: Short explanation, listing of all model parameters with their allowed values for initialization (+ method to ``return`` these). class ChargingStation: """Models a charging station for electric vehicles of different types.""" # Valid values and types for each parameter _VALID_PARAMETERS = { "p_rated": {"types": [float], "values": (0, math.inf)}, "output_type": {"types": [str], "values": ["AC", "DC"]}, "charge_efficiency": {"types": [float, int], "values": (0, 1)}, "discharge_efficiency": {"types": [float, int], "values": (0, 1)}, "cos_phi": {"types": [float, int], "values": (0, 1)}, } @classmethod def get_valid_parameters(cls): """Returns dictionary containing valid parameter types and values. Returns: dict: valid parameters for this model """ return cls._VALID_PARAMETERS E.g. the rated power ``p_rated`` of a charging station has to be a floating point value on should be non-negative. The ``efficiency`` should have a value between zero and one (100%). These values are used when creating a model in simulations to check for correct values. -------------------------------- Initialization method ``__init__()`` ==================================== .. code-block:: :lineno-start: 55 :caption: takes parameter values as inputs def __init__( self, ename: str, p_rated: int, output_type: str = "AC", charge_efficiency: float = 0.99, discharge_efficiency: float = 0.99, cos_phi: float = 1.0, step_size=60 * 15, # step size in seconds ): .. code-block:: :lineno-start: 78 :caption: creates entity of this model by setting the properties and initializing attributes # Set attributes of init_vals to static properties self.ename = ename self.p_rated = p_rated # rated active power (AC/DC) [W] self.output_type = output_type # source AC/DC [-] self.discharge_efficiency = discharge_efficiency # discharging efficiency [-] self.charge_efficiency = charge_efficiency # charging efficiency [-] self.cos_phi = cos_phi # initialize dynamic output properties self.p = 0 # Active Power (after control) [W] self.q = 0 # Reactive Power (after control) [W] self.p_device = {} # active power for every vehicle [W] self.p_min = 0 # Minimal active Power [W] self.p_max = 0 # Maximal active Power [W] ... # save time step length and current time step self.step_size = step_size self.time = 0 -------------------------------- Model methods ============= .. code-block:: :lineno-start: 99 :caption: Type-specific function like calculation of power limits, aging, efficiency, adaption of stored energy etc. def _calc_power_limits(self): """Calculate the power limits for the charging station with the input thats coming from the electric vehicles. Raises: ValueError: If the power limits of at least one connected ev do not work together. """ # calculate current efficiency depending on the direction of power flow self._calc_current_efficiency() # in case no ev is connected to cs - no active power flexibility self.p_min = 0 self.p_max = 0 for ev_id, ev_data in self.ev_data.items(): # check for each ev if connected - consider their limits, efficiency and nominal power if ev_data.appearance: # check if min. and max. power are correct if ev_data.p_min > ev_data.p_max: raise ValueError(f"Min. and max. power of ev {ev_id} do not comply.") # handle the power limits self.p_min = max( self.p_min + ev_data.p_min / self.efficiency, -self.p_rated, ) self.p_max = min( self.p_max + ev_data.p_max / self.efficiency, self.p_rated, ) ... -------------------------------- ``step()`` method ================= Within the ``step()`` method, the calculation processes are executed for the model entity. This example is coming from the ``storage_model.py`` (01/24). .. code-block:: :lineno-start: 226 :caption: For handling of the processes of the model (its calculation) within a time step def step(self, time): """Performs simulation step of eELib battery model. Calculates all of the Dynamic Properties based on the Input Properties. Args: time (int): Current simulation time """ .. code-block:: :lineno-start: 233 :caption: At first: Always handling of a new time step (if entity was called for first time, do some processes once, like adapting energy). This is needed for storages etc., but may not be needed for other model types. # handle current time step if not self.time == time: self.time = time # adapt energy content from last time step ( + self-discharge) if self.p >= 0: # charging self.e_bat_step_volume = ( self.p * self.charge_efficiency * (self.step_size / 3600) ) else: # discharging self.e_bat_step_volume = ( self.p / self.discharge_efficiency * (self.step_size / 3600) ) self.e_bat += self.e_bat_step_volume # Calculate battery cycles self.bat_cycles += abs(self.e_bat_step_volume / self.e_cycle) # Calculate battery state of health and aging properties if self.status_aging: self.__calculate_aging_status() .. code-block:: :lineno-start: 259 :caption: Call model-specific methods in supposed order # Set active power and energy within limits self.__set_power_within_limit() self.__set_energy_within_limit() self.soc = self.e_bat / self.e_bat_usable self.__calc_charging_efficiency() self.__calc_power_limits() Checklist for adding / enhancing a model ======================================== What changes? ------------- adapting current implementation? Try to make use of the existing properties and methods of the model adding new implementation (e.g. new method) or need for new properties? #. Add the part of code to the model #. Write proper comments and documentation (docstrings for every method!) #. Write a corresponding test function! New packages have been added? add them to the ``requirements.txt`` file Where to add? ------------- **New model attributes** need to be ... #. ... added to the ``META`` of the simulator. #. If they are also input data, add them to the ``model_data`` of the test scenarios (``examples/data/model_data_scenario``) as well as the ``VALID_PARAMETERS`` of the model. **New connections between** properties of different models ... ... need to be integrated to the ``model_connections/model_connect_config.json`` file, simply paste them in the direction of the connection. ... in case of connections in both direction, the setup of strong (first sent value) and weak (at beginning only done with default value) connection has to be set in ``model_connections/connect_directions_config.json`` file. Always use lower-case letters for the model names! **New models** need to be integrated ... #. ... into the test scenario scripts (in the ``SIM_CONFIG``). #. ... into the ``model_data`` of the test scenarios (``examples/data/model_data_scenario``). #. ... into the ``model_connections/model_connect_config.json`` file with their connections to/from other models. If the direction of the connection is of importance, there may be a need to adapt the :mod:`~eelib.utils.simulation_helper` functions :meth:`~eelib.utils.simulation_helper.connect_entities()` and :meth:`~eelib.utils.simulation_helper.connect_entities_of_two_model_types()`. #. ... into the simulator ``META`` data. #. ... with a unit test file that checks all the relevant model functionalities.