.. 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. ####################### Forecasts and Schedules ####################### Implemented into the eELib is a possibility to calculate a :term:`forecast` and :term:`schedule` for devices, EMSs, and the grid. For this, a forecast model is given to calculate requested forecasts. Additionally, the calculation of schedules can be implemented in each (control) model. .. figure:: ../_static/forecast_procedure.* :width: 100% :align: center :alt: Scheme of an exemplary use of the forecasts within the eELib | Integration into scenario ========================= Forecasts and schedules can be integrated into simulations like the ``examples/test_scenario_***.py`` files examplarily show. In there, one has to declare the forecast simulator to mosaik via the ``SIM_CONFIG``. Additionally, one has to set the parameter ``USE_FORECAST`` in the scenario configuration to true. .. code-block:: :lineno-start: 43 :caption: simulator configuration [test_scenario_building.py 01/24] # Sim config.: Simulators and their used model types with the properties to store into DB SIM_CONFIG = { # used database, will be left out for model creation and connections "DBSim": {"python": "eelib.data.database.hdf5:Hdf5Database"}, # forecast, will be left out for model creation and connections "ForecastSim": { "python": "eelib.core.control.forecast.forecast_simulator:Sim", "models": {"Forecast": []}, }, .. code-block:: :lineno-start: 93 :caption: scenario configuration [test_scenario_building.py 01/24] # Configuration of scenario: time and granularity START = "2020-01-01 00:00:00" END = "2020-01-04 00:00:00" STEP_SIZE_IN_SECONDS = 900 # 1=sec-steps, 3600=hour-steps, 900=15min-steps, 600=10min-steps USE_FORECAST = True After this, the forecast simulator and model will be created and the connections to the models will be instantiated. Forecast Model ============== The forecast model and its simulator are implemented in the folder ``eelib/core/control/forecast``. The implemented behaviour is quite simple and straight-forward, as all model entities of the simulation are stored within the forecast entity (as deep-copies) via the ``add_forecasted_entity()`` method. This allows to create forecasts by simply iterating over all requested forecasts for all entities, stepping the model for the requested timesteps and collecting the values (of the model entity) for the requested time steps. After that, the forecast model simply returns those calculated forecasts. .. code-block:: :lineno-start: 64 :caption: forecast calculation [forecast_model.py 01/24] # check if request for a forecast was sent if self.forecast_request == {}: self.forecast = {} # no forecast requested, simply return else: # clear earlier forecasts self.forecast = {} # go by all entities to create a forecast for for forecast_getter_id, forecast_req_eid_dict in self.forecast_request.items(): # create empty dict for forecasts connected to this request entity self.forecast[forecast_getter_id] = {} # check if no forecast requested if forecast_req_eid_dict == {}: continue # go by all forecasted model entities for forecast_eid, forecast_info in forecast_req_eid_dict.items(): # check if forecast can be done for this model type if forecast_eid in self.forecast_eid.keys(): # create structure for forecast of each attribute for this entity forecast_save = {} for attr in forecast_info["attr"]: forecast_save[attr] = [] # store the copy of this model entity to execute the stepping entity = self.forecast_eid[forecast_eid] # run the model for each time step and collect the calculated attr values for t in forecast_info["t"]: entity.step(t) for attr in forecast_info["attr"]: forecast_save[attr].append(getattr(entity, attr)) Integration into (Control) Models ================================= For the forecast model to take effect, the forecasts have to be requested by other models, e.g. the energy management system. Additionally, the calculated forecasts should afterwards be used for strategic behaviour (in operating strategies). First of all, the mosaik needs to create a connection between the model and the forecast model. This is done in the ``connect_to_forecast()`` function of the simulation helpers. Here, there is a connection added from EMS to the forecast model for the ``forecast_request`` attribute, while a ``forecast`` attribute is send back the other way around. All other models are simply added to the forecast entity such that forecasts can be calculated. .. code-block:: :lineno-start: 470 :caption: forecast connection function [simulation_helper.py 01/24] def connect_to_forecast( world: object, dict_entities: dict, dict_simulators: dict, forecast: object, forecast_sim: object, ): """Create connections for the forecasts to work. Includes mosaik connections to ems model and adding of the model entities to the forecasts list. Args: world (object): mosaik world object to orchestrate the simulation process dict_entities (dict): dict of all used model entity objects dict_simulators (dict): dict of all used simulators with their ModelFactory-objects forecast (object): forecast model entity forecast_sim (object): simulator for the forecast model """ # create connections for each entity of each model type for model_name, ent_list in dict_entities.items(): for entity in ent_list: # for ems create connections to forecast entity if "ems" in model_name or "EMS" in model_name: world.connect(entity, forecast, "forecast_request") world.connect( forecast, entity, "forecast", weak=True, initial_data={"forecast": {forecast.full_id: {}}}, ) # for other models (devices) add those entities to the forecast entity list else: forecast_sim.add_forecasted_entity( forecast.eid, {entity.full_id: dict_simulators[model_name].get_entity_by_id(entity.eid)}, ) Additionally, forecasts have to be requested by the ems, which should be done only in the corresponding time steps. .. code-block:: :lineno-start: 340 :caption: forecast request by EMS [EMS_model.py 01/24] # request forecasts if needed if self.use_forecast and self.calc_forecast: self.forecast_request = {} for model_type, entity_list in self.controlled_eid_by_type.items(): if model_type in self.forecasted_attrs.keys(): # add forecast request for every entity of this model type for e_full_id in entity_list.keys(): self.forecast_request[e_full_id] = { "attr": self.forecasted_attrs[model_type], "t": range(self.forecast_start, self.forecast_end), } Due to the connection by mosaik, the forecasts are calculated and afterwards send back, such that they should be processed. It is now also possible to calculate schedules with set values for the devices that no forecast can be directly extracted from (charging station, heatpump, battery). .. code-block:: :lineno-start: 352 :caption: handling of forecasts and calculation of schedules in EMS [EMS_model.py 01/24] # CALC SCHEDULE WITH UNCONTROLLED (CSV) DEVICES if self.forecast != {} and self.calc_forecast is True: # calculate the residual load schedule including all not controllable devices schedule_residual_uncontrollable = schedule_help.residual_calc_schedule_uncontrollable(...) # calc schedules for charging station schedule_help.cs_calc_schedule_uncontrolled(...) # calc schedules for heatpump th_residual_forecast = schedule_help.thermal_calc_forecast_residual(...) schedule_help.hp_calc_schedule(...) # get schedule from battery storage based on residual load schedule schedule_help.bss_calc_schedule(...)