Skip to content

Commit

Permalink
Merge pull request #45 from manuGil/feature/model-interface
Browse files Browse the repository at this point in the history
Refactoring Models
  • Loading branch information
manuGil authored Sep 23, 2024
2 parents 1268400 + c34aee0 commit e665a1e
Show file tree
Hide file tree
Showing 113 changed files with 41,541 additions and 41,193 deletions.
105 changes: 105 additions & 0 deletions Models/construct_model.py
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
133 changes: 133 additions & 0 deletions Models/example_illuminator_2/state_space_simulator.py
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()
51 changes: 51 additions & 0 deletions pyproject.toml
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.
Loading

0 comments on commit e665a1e

Please sign in to comment.