.. 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 :term:`Model` represents a real process or component in form of the code. It follows the general structure of *Input* - *internal calculation* - *output*, triggered by its corresponding :ref:`simulator ` that needs to exist for using the model in simulations. The model just represents 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 that is 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, eid: 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.eid = eid 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.timestep = 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 timestep def step(self, timestep): """Performs simulation step of eELib battery model. Calculates all of the Dynamic Properties based on the Input Properties. Args: timestep (int): Current simulation time """ .. code-block:: :lineno-start: 233 :caption: First: Handling of a new timestep (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 timestep if not self.timestep == timestep: self.timestep = timestep # adapt energy content from last timestep ( + self-discharge) if self.p >= 0: # charging self.e_bss_step_volume = ( self.p * self.charge_efficiency * (self.step_size / 3600) ) else: # discharging self.e_bss_step_volume = ( self.p / self.discharge_efficiency * (self.step_size / 3600) ) self.e_bat += self.e_bss_step_volume # Calculate battery cycles self.bss_cycles += abs(self.e_bss_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_bss_usable self.__calc_charging_efficiency() self.__calc_power_limits() Checklist for adding / enhancing a model ======================================== **General Guidelines** When adding or enhancing a model, try to **make use of the existing properties and methods** from this or other models **Model File Structure** A model includes the following files in a model folder which can be a subfolder inside ``eelib/core/***``: #. an init file, e.g., ``eelib/core/devices/example/__init__.py`` (can be empty or with info about the model itself) #. a model file, e.g., ``eelib/core/devices/example/example_model.py`` (see beginning of the article) #. a simulator file, e.g., ``eelib/core/devices/example/example_simulator.py`` (see :doc:`Configuration of a Simulator <../tutorials/config_simulator>`) **Required changes to** ``eelib/data`` #. if a model dataclass is needed or helpful to communicate information to other models, add or adjust the specific dataclass in ``eelib/data/dataclass.py`` #. update ``eelib/data/__init__.py``, to collect all dataclasses at that point **Tests & Documentation** #. Writer **proper comments and documentation** (e.g. docstrings for every method, but also comments to illustrate the behaviour of the code) #. create **unit tests** for all relevant functionalities ``eelib/testing/unit/devices/test_example_model.py`` #. add the model to the **documentation** overview, ``docs/source/reference_manual/model_overview.rst`` (you can use ``eelib/utils/simulation_setup/create_sim_script.ipynb`` and create this automatically by executing the first two cells) **Other considerations** #. Add new model attributes to the ``META`` of the simulator (see :doc:`Configuration of a Simulator <../tutorials/config_simulator>`) #. If you add new packages, update ``requirements.txt`` Integrating the model into a Simulation ======================================= **Strategy** - If fitting, implement the new model or functionalities into the HEMS strategies to be used in a simulation (see :doc:`Implementing an operating Strategy <../tutorials/implement_strategy>`). Here you should also add properties for recieving the correct values from the new model to integrate these into the strategies, e.g. by adding the properties to the META listing. - If it is a *different* model, you should integrate it in a different way into the test scenarios, e.g. a market model should just be added and connected to other models In the case these models are so-called *architecture models* and have to be connected to all other models, you need to add them to the list in ``eelib/model_connections/architecture_models.json`` file **Model Connections** #. define new connections between properties of different models in ``eelib/model_connections/model_connect_config.json`` file (simply paste them in the direction of the connection) #. for connections in both direction, setup strong (first sent value) and weak (at beginning only done with default value) directions, see :doc:`FAQ <../reference_manual/faq_glossary>` . Use lower-case letters for the model names here! #. 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()`. **Integration into Test Scenarios** Add the model to... #. ... the test scenario scripts (in the ``SIM_CONFIG``) #. ... the model data of the test scenarios (``examples/data_input``) #. ... the model inputs of the test scenarios (``examples/input``) Then execute the test scenarios and see whether the models works and is correctly integrated into the simulation process.