-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #45 from manuGil/feature/model-interface
Refactoring Models
- Loading branch information
Showing
113 changed files
with
41,541 additions
and
41,193 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
from abc import ABC, abstractmethod | ||
from dataclasses import dataclass | ||
from enum import Enum | ||
from mosaik_api import Simulator | ||
|
||
class SimulatorType(Enum): | ||
TIME_BASED = 'time-based' | ||
EVENT_BASED = 'event-based' | ||
HYBRID = 'hybrid' | ||
|
||
# NOTE: I have also inherited from the mosaik_api.Simulator class to integrate this model | ||
# This seems naïve to me. Lots of redundancy with defining which methods are mandatory. | ||
# As for the: the models created seem to have a wide variety of attributes and parameters that are defined. | ||
# In addition, the naming convection of the methods created in them is also non-existant | ||
@dataclass | ||
class IlluminatorModel(Simulator, ABC): | ||
""" | ||
A common interface for adding energy models to the Illuminator. | ||
This class combines the creation of model and simulators for the Mosaik engine. | ||
... | ||
Parameters | ||
---------- | ||
simulator_type: SimulatorType.TIME_BASED | ||
Defines how the simulator is advanced through time and whether its attributes are persistent in time or transient | ||
model_name: str | ||
The name of the model | ||
public: bool | ||
Determines whether a model can be instantiated by a user | ||
parameters: list | ||
Variables which values can be set when instantiating the model (can this be always considered inputs?) | ||
attrs: list | ||
Model properties that can be read from (can these be always considered outputs?) | ||
trigger: list | ||
Attribute names that cause the simulator to be stepped when another simulator provides output which is connected to one of those. | ||
Empty if no triggers. | ||
inputs: list | ||
??? | ||
outputs: list | ||
??? | ||
states: list | ||
??? | ||
""" | ||
|
||
# TODO:the ideal solution provides a single interface for creating a model and its simulator | ||
|
||
### meta data ### | ||
model_name: str | ||
parameters: list # variables which values can be set when instantiating the model (can this be always considered inputs?) | ||
attrs: list # model properties that can be read from (can these be always considered outputs?) | ||
trigger: list | ||
|
||
inputs: list | ||
outputs: list | ||
states: list | ||
|
||
simulator_type: SimulatorType = SimulatorType.TIME_BASED | ||
public: bool = True | ||
################# | ||
|
||
@classmethod | ||
def __test__(cls, subclass): | ||
return hasattr(subclass, "__init__") | ||
|
||
def __post_init__(self): | ||
""" | ||
Post-init method which creates and initializes the metadata used by mosaik_api | ||
""" | ||
META = { | ||
'type': self.simulator_type, | ||
'models': { | ||
self.model_name: { | ||
'public': self.public, | ||
'params': self.parameters, | ||
'attrs': self.attrs, | ||
'trigger': self.trigger | ||
}, | ||
}, | ||
} | ||
super().__init__(META) | ||
|
||
@abstractmethod | ||
def init(self, sid, time_resolution, *args): | ||
""" Initialize the simulator with the ID `sid` and pass the `time_resolution` and additional parameters sent by mosaik. """ | ||
raise NotImplementedError | ||
|
||
@abstractmethod | ||
def create(self, num, model, *args): | ||
""" Create `num` instances of `model` using the provided additional arguments """ | ||
raise NotImplementedError | ||
|
||
@abstractmethod | ||
def step(self, time, inputs, max_advance): | ||
""" | ||
Perform the next simulation step from time `time` using input values | ||
from `inputs` and return the new simulation time (the time at which | ||
`step()` should be called again) | ||
""" | ||
raise NotImplementedError | ||
|
||
@abstractmethod | ||
def get_data(self, outputs): | ||
""" Return the data for the requested outputs """ | ||
raise NotImplementedError |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
import mosaik_api_v3 | ||
try: | ||
from models.state_space_model import Model as model # Modify model.state_space_model to model.<model_name>_model | ||
except ImportError: | ||
from state_space_model import Model as model # Modify model.state_space_model to model.<model_name>_model | ||
else: | ||
raise ImportError('Model not found.') | ||
# END OF IMPORTS | ||
|
||
""" Mandatory prefilled .yaml file | ||
- name: Batteries | ||
path: 'Models/state_space_model.py' # path to the models's code | ||
models: | ||
- name: Battery1 | ||
Inputs: [demand_power_flow=0] # Positive values are charging, negative values are discharging | ||
Outputs: [effective_power_flow=0] # effective_power_flow is the actual power flow | ||
Parameters: [capacity=1000] # capacity is the battery capacity in kWh | ||
States: [soc=500] # soc is the state of charge in kWh | ||
""" | ||
|
||
META = { | ||
'type': 'time-based', | ||
'models': { | ||
'SSM_Example': { | ||
'public': True, | ||
'params': ['soc', 'capacity'], | ||
'attrs': ['requested_power_flow', 'effective_power_flow', 'soc'], | ||
}, | ||
}, | ||
} | ||
|
||
class StateSpaceSimulator(mosaik_api_v3.Simulator): | ||
""" | ||
A simulator class representing a state space model. To illustrate we implemented a simple battery. | ||
To use your own model you must: | ||
1) Files: | ||
- Make a new folder in the models directory with the name of your model. | ||
- Copy the state_space_simulator.py and state_space_model.py into the new folder. | ||
- Modify the name of the file as such: <model_name>_simulator.py and <model_name>_model.py. | ||
2) Simulator: | ||
- Change the model import to point to your model. | ||
- Modify the META dictionary: | ||
- Include the model name. | ||
- Include the model parameters, which in a SSM model are the states + parameters. | ||
- Include the model attributes, which in a SSM model are the inputs + outputs. | ||
- Modify the empty .yaml file at the top: | ||
- Include the model name. | ||
- Include the model inputs. Make sure to select good default values, that ensure stability. Or set the default to None. | ||
- Include the model outputs. Make sure to select good default values, that ensure stability. Or set the default to None. | ||
- Include the model parameters. Set relevant default values and explain the meaning of the parameters and when the defaults are valid. | ||
- Include the model states. Set relevant default values and explain the meaning of the states and when the defaults are valid. | ||
3) Model: | ||
- Modify the model to include the model logic. | ||
- Modify the model to include the model step function. | ||
Attributes: | ||
entities (dict): A dictionary storing all instances of this model by ID. | ||
Methods: | ||
init(sid, model, model_name, step_size): | ||
Initializes the state space model. | ||
create(num, inputs, outputs, parameters, states): | ||
Creates multiple entities of the model. | ||
step(time, inputs): | ||
Processes inputs and steps the model. | ||
get_data(outputs): | ||
Retrieves output data from the model. | ||
""" | ||
def __init__(self): | ||
super().__init__(META) | ||
self.entities = {} # Stores all instances of this model by ID | ||
|
||
def init(self, sid, step_size, model_name): | ||
self.sid = sid # Store the simulation ID | ||
self.model = model # Pointer to model to create such as: 'Battery', 'PV', 'Wind', etc. | ||
self.model_name = model_name # Store the model name to ensure uniqueness of entity IDs. Sometimes called eid_prefix. | ||
self.step_size = step_size # Store the step size | ||
return self.meta | ||
|
||
def create(self, num, inputs, outputs, parameters, states): | ||
# Create num entities | ||
entities = [] | ||
for i in range(num): | ||
eid = f'{self.model_name}_{i}' # Create unique entity ID | ||
new_model = self.model( # Create a new model instance, passing the inputs, outputs, parameters, and states | ||
inputs=inputs, | ||
outputs=outputs, | ||
parameters=parameters, | ||
states=states | ||
) | ||
self.entities[eid] = new_model # Store the model instance internally | ||
entities.append({'eid': eid, 'type': self.model}) # Append the entity ID and type to the list of entities | ||
return entities | ||
|
||
def step(self, time, inp uts): | ||
self.time = time | ||
# Process inputs and step the model | ||
for eid, entity_inputs in inputs.items(): | ||
current_model = self.entities[eid] # Get the model instance | ||
# Update the inputs | ||
for attr, value in entity_inputs.items(): | ||
current_model.inputs[attr] = value | ||
|
||
# Perform a simulation step | ||
current_model.step() | ||
|
||
return time + self.step_size | ||
|
||
def get_data(self, outputs): | ||
data = {} # Create a dictionary to store the output data | ||
data['time'] = self.time | ||
for eid, attrs in outputs.items(): | ||
model = self.entities[eid] # Get the model instance | ||
data[eid] = {} | ||
for attr in attrs: | ||
# Get model outputs: | ||
try: | ||
data[eid][attr] = getattr(model, attr) # Get the attribute value from the model instance | ||
except ValueError as e: | ||
print(f'Error getting attribute {attr} for entity {eid}: {e}') # Log an error if the attribute is not found | ||
return data | ||
|
||
|
||
def main(): | ||
return mosaik_api_v3.start_simulation(StateSpaceSimulator()) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
[build-system] | ||
requires = ["hatchling"] | ||
build-backend = "hatchling.build" | ||
# [tool.hatch.build.targets.wheel] | ||
# packages = ["src/illuminator"] | ||
|
||
[project] | ||
name = "illuminator" | ||
version = "0.1.0" | ||
description = "A development toolkit for simulating energy systems" | ||
license = {file = "LICENSE"} | ||
readme = "README.md" | ||
requires-python = ">=3.8" | ||
authors = [ | ||
{ name = "Your Name", email = "[email protected]" } | ||
] | ||
maintainers = [ | ||
{ name = "Your Name", email = "[email protected]"} | ||
] | ||
dependencies = [ | ||
"arrow==1.2.3", | ||
"lxml==4.9.3", | ||
"matplotlib==3.7.2", | ||
"paho-mqtt==1.6.1", | ||
"pandapower==2.13.1", | ||
"pandas==1.5.3", | ||
"mosaik==3.1.1" | ||
] | ||
keywords = [ | ||
"energy", | ||
"simulation", | ||
"development", | ||
"toolkit" | ||
] | ||
classifiers = [ | ||
"Development Status :: 5 - Production/Stable", | ||
"Programming Language :: Python" | ||
] | ||
|
||
|
||
[project.optional-dependencies] | ||
dev = [ | ||
"pytest" | ||
] | ||
|
||
[project.urls] | ||
Homepage = "https://example.com" | ||
Documentation = "https://readthedocs.org" | ||
Repository = "https://github.com/Illuminator-team/Illuminator.git" | ||
"Bug Tracker" = "https://github.com/Illuminator-team/Illuminator/issues" | ||
Changelog = "https://github.com/me/spam/blob/master/CHANGELOG.md" |
File renamed without changes.
File renamed without changes.
Oops, something went wrong.