diff --git a/docs/references/engine-api.rst b/docs/references/engine-api.rst index 9fd3fcd..8885e5a 100644 --- a/docs/references/engine-api.rst +++ b/docs/references/engine-api.rst @@ -1,10 +1,30 @@ -Engine's API +Engine API ===================== -.. automodule:: illuminator.engine +The simulation engine provides a wrapper around Mosaik simulation to simplify the process of creating and running simulations. + +Python Interface +--------------------- + +.. autoclass:: illuminator.engine.Simulation :members: :show-inheritance: +Utility Functions +--------------------- + +.. autofunction:: illuminator.engine.apply_default_values + +.. autofunction:: illuminator.engine.build_connections + +.. autofunction:: illuminator.engine.compute_mosaik_end_time + +.. autofunction:: illuminator.engine.connect_monitor + +.. autofunction:: illuminator.engine.create_world + +.. autofunction:: illuminator.engine.genarate_mosaik_configuration + .. diff --git a/docs/user/config-file.md b/docs/user/config-file.md index 9ec4afe..91ca97e 100644 --- a/docs/user/config-file.md +++ b/docs/user/config-file.md @@ -68,26 +68,26 @@ monitor: | Keyword | Description | Optional | Default | |---------|-------------|----------|---------| -| **scenario:** | a set of global values for a simulation. | | | -|`name` | A name for the simulation, internally this name will be asssigned to what the Mosaik World created during runtime. | | | -| `start_time` | start time for the simulation. Must be a timestamp in ISO 8601 format | | | -| `end_time` | end time for the simulation. Must be a timestamp in ISO 8601 format. | | | -| `time_resolution` | number of seconds between simulation steps | ☑ | 900 (15 min) -| **models:** | a list of models for the simulation | | | -| `name` | a name for the model. Must be unique for each simulation | | | -| `type` | type of model. This must correspond with the name of the model registered in the Illuminator. | | | -| `inputs` | a set of input-names and initial values for the model. The model type determines which names and values are applicable to each model, and they must be declared accordingly. Inputs are optional | | If the value is set to `null`, the default value will be used. See the respective model type for details.| -| `outputs` | a set of output-names and initial values for the model. Similar to *inputs* valid names and values for each model are determined by the model *type*. See the respective model type for details. | | If the value is set to `null`, the default value will be used. | -| `parameters` | a set of name-value pairs for the model. Parameters declared constants for a model during runtime. | ☑ | If ommited, the default values will be used. See the respective model type for details. | -| `states` | a set of name-value pairs considered as states for the model. The values modify the internal initial values of a state. | ☑ | If ommited, the default values will be used. See the respective model type for details. | -| `triggers` | names of inputs, output or states that are use as triggers for a particular model. Triggers can only be declared by models that implement the *event-based paradigm*. See the respective model type to know if it accepts triggers. | ☑ | | -| `connect` | to declare in which client a model runs when using a Raspberry Pi cluster. | ☑ | | -| `ip` | Ip of the client manchine that will run the model. Only IP version 4 format. | | | -| `port` | TCP port to use to connect to the client machine| ☑ | | +| **scenario:** | a set of global values
for a simulation. | | | +|`name` | A name for the simulation, internally
this name will be asssigned to what
the Mosaik World created during runtime. | | | +| `start_time` | start time for the simulation.
Must be a timestamp in ISO 8601 format | | | +| `end_time` | end time for the simulation.
Must be a timestamp in ISO 8601 format. | | | +| `time_resolution` | number of seconds between
simulation steps | ☑ | 900 (15 min) +| **models:** | a list of models for
the simulation | | | +| `name` | a name for the model. Must
be unique for each simulation | | | +| `type` | type of model. This must correspond
with the name of the model
registered in the Illuminator. | | | +| `inputs` | a set of input-names and initial
values for the model. The model
type determines which names and
values are applicable to each
model, and they must be declared accordingly.
Inputs are optional | | If the value is set to `null`, the default value will be used. See the respective model type for details.| +| `outputs` | a set of output-names and initial
values for the model. Similar to
*inputs* valid names and values
for each model are determined by
the model *type*. See the respective
model type for details. | | If the value is set to `null`, the default value will be used. | +| `parameters` | a set of name-value pairs for
the model. Parameters declared constants
for a model during runtime. | ☑ | If ommited, the default values will be used. See the respective model type for details. | +| `states` | a set of name-value pairs considered
as states for the model. The values modify
the internal initial values of a state. | ☑ | If ommited, the default values will be used. See the respective model type for details. | +| `triggers` | names of inputs, output or states
that are use as triggers for a particular model.
Triggers can only be declared by models
that implement the *event-based paradigm*.
See the respective model type to know if
it accepts triggers. | ☑ | | +| `connect` | to declare in which client a model runs
when using a Raspberry Pi cluster. | ☑ | | +| `ip` | Ip of the client manchine that will run
the model. Only IP version 4 format. | | | +| `port` | TCP port to use to connect to the
client machine| ☑ | | | **connections:** | how models connect to each other. | | | -| `from` | origin of the connection declared as `.`. Input names use here must also appear as *inputs* in the models section. | | | -| `to` | destination of the connection declared as `.`. Output names use here must also appear as *outputs* in the models section. | | +| `from` | origin of the connection declared as
`.`. Input names
use here must also appear as *inputs* in
the models section. | | | +| `to` | destination of the connection declared as
`.`. Output names
use here must also appear as *outputs* in
the models section. | | | **monitor:** | -| `file` | path to a CSV file to store results of the simulation. File will be created if necessary. | ☑ | a `out.csv` file saved to the current directory | -|`items` | a list of which inputs, outputs or states of models that most be monitored during runtime. Items must be declared as `.`, where *name* is an input, output or stated clared in the *models* section. No duplicated values are allowed | | | +| `file` | path to a CSV file to store results of
the simulation. File will be created if
necessary. | ☑ | a `out.csv` file saved to the current directory | +|`items` | a list of which inputs, outputs or states
of models that most be monitored during
runtime. Items must be declared as
`.`, where *name* is an
input, output or stated clared in the
*models* section. No duplicated values
are allowed | | | diff --git a/examples/adder.yaml b/examples/adder.yaml deleted file mode 100644 index 818c3ce..0000000 --- a/examples/adder.yaml +++ /dev/null @@ -1,31 +0,0 @@ -scenario: - name: "AddingNumbers" # in mosaik so called world - start_time: '2012-01-02 00:00:00' # ISO 8601 start time of the simulation - end_time: '2012-01-02 00:00:10' # duration in seconds - time_resolution: 900 # time step in seconds. Defaults to 15 minutes (900 s) -models: # list of models for the energy network -- name: Adder1 # name for the model (must be unique) - type: Adder # most match the class inheriting from IlluminatorConstructor - inputs: - in1: 10 # input-name: initial value, default value will be used if not defined - in2: 20 - outputs: - out1: 0 - parameters: - param1: "adding tens" -- name: Adder2 - type: Adder # models can reuse the same type - inputs: - in1: 100 # input-name: initial value, default value will be used if not defined - in2: 200 - parameters: - param1: "adding hundreds" -connections: -- from: Adder1.out1 # start model, pattern: model_name.output_name/input_name - to: Adder2.in1 # end model -monitor: # a list of models, its inputs, output and states to be monitored and logged -- Adder2.out2 # pattern model_name.state_name - - - - diff --git a/examples/adder_copy.yaml b/examples/adder_copy.yaml index 9b23b85..6468a5d 100644 --- a/examples/adder_copy.yaml +++ b/examples/adder_copy.yaml @@ -1,9 +1,8 @@ scenario: name: "AddingNumbers" # in mosaik so called world start_time: '2012-01-02 00:00:00' # ISO 8601 start time of the simulation - end_time: '2012-01-03 00:00:10' # duration in seconds + end_time: '2012-01-02 00:45:01' # duration in seconds time_resolution: 900 # time step in seconds. Defaults to 15 minutes (900 s) - results: './out.csv' models: # list of models for the energy network - name: Adder2 type: Adder # models can reuse the same type @@ -26,17 +25,17 @@ models: # list of models for the energy network connections: - from: Adder1.out1 # start model, pattern: model_name.output_name/input_name to: Adder2.in1 # end model - time_shifted: False + time_shifted: True # TODO: Below is what Illuminator looks for, but is not valid. Needs to be fixed # - from: Adder1-0.hybrid_0.out1 # to: Adder2-0.hybrid_0.in1 - #monitor: # a list of models, its inputs, output and states to be monitored and logged #- Adder2.out2 # pattern model_name.state_name monitor: file: './out.csv' items: - Adder2.out1 + - Adder1.out1 diff --git a/examples/adder_model.py b/examples/adder_model.py index be955ba..c7665bf 100644 --- a/examples/adder_model.py +++ b/examples/adder_model.py @@ -25,8 +25,6 @@ def step(self, time) -> None: return time + self._model.time_step_size - # todo: adopt get_data() as defined by Mosaik - if __name__ == '__main__': import mosaik diff --git a/examples/python_api.py b/examples/python_api.py new file mode 100644 index 0000000..19f93ba --- /dev/null +++ b/examples/python_api.py @@ -0,0 +1,5 @@ +from illuminator.engine import Simulation + +sim = Simulation('examples/adder_copy.yaml') + +sim.run() \ No newline at end of file diff --git a/examples/simulation.example.yaml b/examples/simulation.example.yaml index 48ec508..f2cd0a2 100644 --- a/examples/simulation.example.yaml +++ b/examples/simulation.example.yaml @@ -1,10 +1,9 @@ scenario: +# this is only for illustrative purposes, it won't run scuccessfully name: "ExampleScenario" # in mosaik so called world start_time: '2012-01-02 00:00:00' # ISO 8601 start time of the simulation end_time: '2012-01-02 00:00:10' # duration in seconds time_resolution: 900 # time step in seconds (optional). Defaults to 15 minutes (900 s) - results: './out.csv' # optional with default, path to the results file for the scenario. This should be optional -models: # list of models for the energy network - name: Battery1 # name for the model (must be unique) type: Battery # name of the model registered in the Illuminator inputs: @@ -55,7 +54,9 @@ connections: - from: Battery2.output to: PV1.input2 monitor: # a list of models, its inputs, output and states to be monitored and logged -- Battery1.input1 # pattern model_name.state_name -- Battery2.output1 # no duplicates allowed -- PV1.soc # pattern model_name.state_name -- PV1.output1 + file: './out.csv' # optional with default, path to the results file for the scenario. This should be optional + items: + - Battery1.input1 # pattern model_name.state_name + - Battery2.output1 # no duplicates allowed + - PV1.soc # pattern model_name.state_name + - PV1.output1 diff --git a/simple_test2.yaml b/simple_test2.yaml new file mode 100644 index 0000000..9283edc --- /dev/null +++ b/simple_test2.yaml @@ -0,0 +1,43 @@ +scenario: + name: "SimpleTest" # in mosaik so called world + start_time: '2012-01-01 00:00:00' # ISO 8601 start time of the simulation + end_time: '2012-01-01 01:00:00' # duration in seconds + time_resolution: 900 # time step in seconds (optional). Defaults to 15 minutes (900 s) +models: # list of models for the energy network +- name: CSVB # name for the model (must be unique) + type: CSV # name of the model registered in the Illuminator + parameters: # a CSV model must have a start time and a file as parameters + start: '2012-01-01 00:00:00' # ISO 8601 start time of the simulation + datafile: './tests/data/solar-sample.csv' # path to the file with the data +- name: PV + type: PvAdapter # models can reuse the same type + inputs: + G_Gh: null # are meant to ovewrite initial values + G_Dh: null + G_Bn: null + Ta: null + hs: null + FF: null + Az: null + outputs: + G_Gh: null +connections: +- from: CSVB.G_Gh # start model, pattern: model_name.output_name/input_name + to: PV.G_Gh # end model +- from: CSVB.G_Dh + to: PV.G_Dh +- from: CSVB.G_Bn + to: PV.G_Bn +- from: CSVB.Ta + to: PV.Ta +- from: CSVB.hs + to: PV.hs +- from: CSVB.FF + to: PV.FF +- from: CSVB.Az + to: PV.Az +monitor: + file: './out_test2.csv' # optional with default, path to the results file for the scenario. This should be optional # a list of models, its inputs, output and states to be monitored and logged + items: + - PV.pv_gen + - PV.G_Dh \ No newline at end of file diff --git a/src/illuminator/builder/model.py b/src/illuminator/builder/model.py index 1591db3..23e0566 100644 --- a/src/illuminator/builder/model.py +++ b/src/illuminator/builder/model.py @@ -4,6 +4,7 @@ from enum import Enum from datetime import datetime import illuminator.engine as engine +from mosaik_api_v3 import Simulator class SimulatorType(Enum): TIME_BASED = 'time-based' @@ -44,9 +45,11 @@ class IlluminatorModel(): simulator_type: SimulatorType The type of simulator that the model belongs to. time_step_size: int - The time step size for the simulator. + The time of each simulation step in seconds. Default 900. time: int The current time of the simulation. + model_type: str + A name for the class of model that is being defined. Default 'Model'. """ parameters: Dict = field(default_factory=dict) @@ -55,13 +58,16 @@ class IlluminatorModel(): states: Dict = field(default_factory=dict) triggers: Optional[Dict] = field(default_factory=list) simulator_type: SimulatorType = SimulatorType.TIME_BASED - time_step_size: int = 15 # This is closely related to logic in the step method. Currently, all models have the same time step size (15 minutes). This is a global setting for the simulation, not a model-specific setting. + time_step_size: int = 900 # This is closely related to logic in the step method. + # Currently, all models have the same time step size (15 minutes). + # This is a global setting for the simulation, not a model-specific setting. time: Optional[datetime] = None # Shouldn't be modified by the user. - model_type: Optional[str] = "Model" + model_type: Optional[str] = "Model" + def __post_init__(self): - self.validate_states() - self.validate_triggers() + self._validate_states() + self._validate_triggers() # self.validate_simulator_type() @property @@ -71,7 +77,7 @@ def simulator_meta(self) -> dict: meta = { 'type': self.simulator_type.value, 'models': { - self.model_type : { + self.model_type: { # This must be the name of he model 'public': True, 'params': list(self.parameters.keys()), 'attrs': list(self.inputs.keys()) + list(self.outputs.keys()) @@ -79,7 +85,7 @@ def simulator_meta(self) -> dict: }} return meta - def validate_states(self): + def _validate_states(self): """Check if items in 'states' are in parameters, inputs or outputs""" for state in self.states: if state not in self.parameters and state not in self.inputs and \ @@ -87,14 +93,14 @@ def validate_states(self): raise ValueError(f"State: {state} must be either a parameter, " "an input or an output") - def validate_triggers(self): + def _validate_triggers(self): """Check if triggers are in inputs or outputs""" for trigger in self.triggers: if trigger not in self.inputs and trigger not in self.outputs: raise ValueError(f"Trigger: {trigger} must be either an input " "or an output") - def validate_simulator_type(self): + def _validate_simulator_type(self): """Check if triggers are defined for time-based and event-based simulations only """ @@ -107,7 +113,10 @@ def validate_simulator_type(self): raise ValueError("Triggers are required in event-based simulators") -from mosaik_api_v3 import Simulator +# COTINUE FROM HERE +# need to find a convenient an easy way to define new models +# Ideas: +# - add model definition as a method of the ModelConstructor class. will it work? class ModelConstructor(ABC, Simulator): """A common interface for constructing models in the Illuminator""" diff --git a/src/illuminator/engine.py b/src/illuminator/engine.py index a6195a2..f220fc9 100644 --- a/src/illuminator/engine.py +++ b/src/illuminator/engine.py @@ -218,7 +218,8 @@ def start_simulators(world: MosaikWorld, models: list) -> dict: return model_entities -def build_connections(world:MosaikWorld, model_entities: dict[MosaikEntity], connections: list[dict], model_config: list[dict]) -> MosaikWorld: +def build_connections(world:MosaikWorld, model_entities: dict[MosaikEntity], connections: list[dict], + models: list[dict]) -> MosaikWorld: # TODO: limit model_config to only models """ Connects the model entities in the Mosaik world based on the connections specified in the YAML configuration file. @@ -230,6 +231,8 @@ def build_connections(world:MosaikWorld, model_entities: dict[MosaikEntity], con A dictionary of model entities created for the Mosaik world. connections: list A list of connections to be established between the model entities. + models: list + The models involved in the connections based on the configuration file. Returns ------- @@ -241,7 +244,8 @@ def build_connections(world:MosaikWorld, model_entities: dict[MosaikEntity], con for connection in connections: from_model, from_attr = connection['from'].split('.') to_model, to_attr = connection['to'].split('.') - to_model_config = next((m for m in model_config if m['name'] == to_model)) + # TODO: check if this will work for the cases in which there are not time_shifted connections + to_model_config = next((m for m in models if m['name'] == to_model)) time_shifted = connection['time_shifted'] # Establish connections in the Mosaik world try: @@ -412,7 +416,7 @@ def run(self): model_entities = start_simulators(world, config['models']) # Connect the models based on the connections specified in the configuration - world = build_connections(world, model_entities, config['connections']) + world = build_connections(world, model_entities, config['connections'], config['models']) # Connect monitor world = connect_monitor(world, model_entities, monitor, config['monitor']) diff --git a/src/illuminator/models/Battery/battery_mosaik.py b/src/illuminator/models/Battery/battery_mosaik.py index c22234e..30c6ead 100644 --- a/src/illuminator/models/Battery/battery_mosaik.py +++ b/src/illuminator/models/Battery/battery_mosaik.py @@ -1,12 +1,13 @@ # only can build one battery model import mosaik.scheduler -import mosaik_api -try: - import Models.Battery.battery_model as batterymodelset -except ModuleNotFoundError: - import battery_model as batterymodelset -else: - import Models.Battery.battery_model as batterymodelset +import mosaik_api_v3 as mosaik_api +# try: +import illuminator.models.Battery.battery_model as batterymodelset +# except ModuleNotFoundError: +# import battery_model as batterymodelset +# else: +# import Models.Battery.battery_model as batterymodelset + import pandas as pd #todo: convert this battery model simAPI to a controller api. This becomes a mosaik API to start the battery and the electrolyser. @@ -332,5 +333,9 @@ def get_data(self, outputs:dict) -> dict: def main(): mosaik_api.start_simulation(BatteryholdSim(), 'Battery-Simulator') + import mosaik_api_v3 + + mosaik_api_v3.start_simulation() + if __name__ == "__main__": main() diff --git a/src/illuminator/models/adder.py b/src/illuminator/models/adder.py index 9628303..58a1c36 100644 --- a/src/illuminator/models/adder.py +++ b/src/illuminator/models/adder.py @@ -7,7 +7,6 @@ from illuminator.builder import IlluminatorModel, ModelConstructor - # Define the model parameters, inputs, outputs... adder = IlluminatorModel( parameters={"param1": "addition"}, @@ -25,26 +24,25 @@ def step(self, time, inputs, max_advance=900) -> None: print(f"inputs: {inputs}") print(f'internal inputs: {self._model.inputs}') for eid, attrs in inputs.items(): - print(f"eid: {eid}, attrs:{attrs}") - print(f"self.model_entities: {self.model_entities}") + # print(f"eid: {eid}, attrs:{attrs}") + # print(f"self.model_entities: {self.model_entities}") model_instance = self.model_entities[eid] for inputname, value in inputs[eid].items(): - print(f"inputname: {inputname}, value:{value}") - print(f"model_instance.inputs[inputname]: {model_instance.inputs[inputname]}") + # print(f"inputname: {inputname}, value:{value}") + # print(f"model_instance.inputs[inputname]: {model_instance.inputs[inputname]}") if len(value) > 1: raise RuntimeError(f"Why are you passing multiple values {value}to a single input? ") else: first_key = next(iter(value)) model_instance.inputs[inputname] = value[first_key] - print(f"The dictionary value: {value[first_key]}") + # print(f"The dictionary value: {value[first_key]}") self._model.outputs["out1"] = self._model.inputs["in1"] + self._model.inputs["in2"] # TODO do we add values internally or based on the current inputs print("result:", self._model.outputs["out1"]) return time + self._model.time_step_size - # todo: adopt get_data() as defined by Mosaik - + if __name__ == '__main__': # Create a model by inheriting from ModelConstructor # and implementing the step method