Skip to content

Commit

Permalink
Merge pull request #96 from Illuminator-team/model-dev
Browse files Browse the repository at this point in the history
First models implemented, running version
  • Loading branch information
Eutardigrada authored Dec 11, 2024
2 parents 2ae9f3d + 856dd45 commit 48d215d
Show file tree
Hide file tree
Showing 14 changed files with 563 additions and 61 deletions.
25 changes: 21 additions & 4 deletions examples/battery.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,26 @@ models: # list of models for the energy network
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: './battery_charger.csv' # path to the file with the data
datafile: './examples/battery_charger.csv' # path to the file with the data
delimiter: ','
date_format: 'YYYY-MM-DD HH:mm:ss'
time_resolution: 900

- name: Load1
type: Load
parameters:
houses: 1 # number of houses that determine the total load demand
output_type: 'power' # type of output for consumption calculation ('energy' or 'power')
inputs:
load: 0 # incoming energy or power demand per house (kWh) for each time step (15 minutes)
outputs:
load_dem: 0 # total energy or power consumption for all houses (kWh) over the time step
consumption: 0 # Current energy or power consumption based on the number of houses and input load (kWh)
time: None, # Current simulation time step in seconds
forecast: None # Forecasted load demand (if applicable, not defined in the code but mentioned in META)
states:
consumption: 0
time: None
forecast: None

# - name: Adder1 # name for the model (must be unique)
# type: Adder # most match the class inheriting from IlluminatorConstructor
Expand All @@ -33,7 +49,6 @@ models: # list of models for the energy network
discharge_efficiency: 50 # efficiency of discharging the battery (%)
soc_min: 3 # minimum allowable state of charge for the battery (%)
soc_max: 80 # maximum allowable state of charge for the battery (%)
resolution: 1800 # time resolution for simulation steps (seconds) TODO: SEEMS TO NOT DO ANYTHING
inputs:
flow2b: 0 # power flow to/from the battery. Positive for charging, negative for discharging (kW)
outputs:
Expand All @@ -50,9 +65,11 @@ connections:
#time_shifted: True
- from: CSVB.flow2b
to: Battery1.flow2b
- from: CSVB.load
to: Load1.load

monitor:
file: './out_battery.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:
- CSVB.flow2b
#- Battery1.soc
- Battery1.soc
6 changes: 6 additions & 0 deletions examples/battery_charger.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Charge_data
Time,flow2b,load
2012-01-01 00:00:00,-40,10
2012-01-01 00:15:00,40,10
2012-01-01 00:30:00,0,10
2012-01-01 00:45:00,-40,10
5 changes: 5 additions & 0 deletions out.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
date,CSVB-0.time-based_0-flow2b,Battery1-0.time-based_0-soc
2012-01-01 00:00:00,-40.0,40.0
2012-01-01 00:00:01,40.0,45.0
2012-01-01 00:00:02,0.0,45.0
2012-01-01 00:00:03,-40.0,25.0
5 changes: 5 additions & 0 deletions out_battery.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
date,CSVB-0.time-based_0-flow2b,Battery1-0.time-based_0-soc
2012-01-01 00:00:00,-40.0,40.0
2012-01-01 00:15:00,40.0,45.0
2012-01-01 00:30:00,0.0,45.0
2012-01-01 00:45:00,-40.0,25.0
33 changes: 23 additions & 10 deletions src/illuminator/builder/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ 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 = 900 # This is closely related to logic in the step method.
time_step_size: int = 1 # 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.
Expand Down Expand Up @@ -125,11 +125,12 @@ def __init__(self, **kwargs) -> None:
#model: IlluminatorModel
model_vals = engine.current_model
model = IlluminatorModel(
parameters=model_vals['parameters'],
inputs=model_vals["inputs"],
outputs=model_vals["outputs"],
states={},
model_type=model_vals["type"]
parameters=model_vals.get('parameters', self.parameters), # get the yaml values or the default from the model
inputs=model_vals.get('inputs', self.inputs),
outputs=model_vals.get('outputs', self.outputs),
states=model_vals.get('states', self.states),
model_type=model_vals.get('type', {}),
time_step_size=model_vals.get('time_step_size', self.time_step_size)
)
super().__init__(meta=model.simulator_meta)
self._model = model
Expand Down Expand Up @@ -161,6 +162,8 @@ def step(self, time:int, inputs:dict=None, max_advance:int=None) -> int:
pass

def init(self, sid, time_resolution=1, **sim_params): # can be use to update model parameters set in __init__
# TODO: from engine.py, time_resolution is never passed

print(f"running extra init")
# This is the standard Mosaik init method signature
self.sid = sid
Expand Down Expand Up @@ -201,22 +204,32 @@ def get_data(self, outputs) -> Dict: # TODO remove the print statements here
A dictionary of model outputs and states.
"""
data = {}
print(f"Here are your outputs: {outputs}")
# print(f"Here are your outputs: {outputs}")
# for eid, attrs in self._model.outputs.items():
for eid, attrs in outputs.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]
data[eid] = {}
for attr in attrs:
if attr in model_instance.outputs:
data[eid][attr] = model_instance.outputs[attr]
else:
data[eid][attr] = model_instance.states[attr]
print(f"data: {data}")
# print(f"data: {data}")
return data


def unpack_inputs(self, inputs):
data = {}
for attrs in inputs.values():
for attr, sources in attrs.items():
values = list(sources.values()) # we expect each attribute to just have one sources (only one connection per input)
if len(values) > 1:
raise RuntimeError(f"Why are you passing multiple values {value}to a single input? ")
data[attr] = values[0]
return data

if __name__ == "__main__":

pass
40 changes: 24 additions & 16 deletions src/illuminator/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,15 @@ def apply_default_values(config_simulation: dict) -> dict:
"""

# defaults
time_resolution = {'time_resolution': 900} # 15 minutes
out_file = {'file': './out.csv'}
# TODO: set other default values
# time_resolution = {'time_resolution': 900} # 15 minutes
# out_file = {'file': './out.csv'}
# # TODO: set other default values

if 'time_resolution' not in config_simulation['scenario']:
config_simulation.update(time_resolution)
# file to store the results
if 'file' not in config_simulation['monitor']:
config_simulation.update(out_file)
# if 'time_resolution' not in config_simulation['scenario']:
# config_simulation.update(time_resolution)
# # file to store the results
# if 'file' not in config_simulation['monitor']:
# config_simulation.update(out_file)

#TODO: Write a unit test for this
return config_simulation
Expand Down Expand Up @@ -173,18 +173,19 @@ def start_simulators(world: MosaikWorld, models: list) -> dict:
raise ValueError("The CSV model requires 'start' and 'datafile' parameters. Check your YAML configuration file.")

simulator = world.start(sim_name=model_name,
sim_start=model_parameters['start'], datafile=model_parameters['datafile'])
sim_start=model_parameters['start'], datafile=model_parameters['datafile'], sim_params={model_name: model})

model_factory = getattr(simulator, model_type)
entity = model_factory.create(num=1)

else:
simulator = world.start(sim_name=model_name,
# **model_parameters
model_name = model_name,
sim_params= {model_name: model} # This value gets picked up in the init() function
# Some items must be passed here, and some other at create()
)
# simulator = world.start(sim_name=model_name,
# # **model_parameters
# model_name = model_name,
# sim_params= {model_name: model} # This value gets picked up in the init() function
# # Some items must be passed here, and some other at create()
# )
simulator = world.start(sim_name=model_name, sim_params={model_name: model})

# TODO: make all parameters in create() **kwargs
# TODO: model_type must match model name in META for the simulator
Expand All @@ -210,7 +211,7 @@ def start_simulators(world: MosaikWorld, models: list) -> dict:
# cap=500,
# output_type='power'
# )
entity = model_factory.create(num=1, param1="Not in use")
entity = model_factory.create(num=1, **model_parameters)

model_entities[model_name] = entity
print(model_entities)
Expand Down Expand Up @@ -327,6 +328,13 @@ def set_current_model(model):
print(f"Warning: Missing 'outputs' key in model. {e}")
except Exception as e:
print(f"Warning: An error occurred while assigning 'outputs'. {e}")

try:
current_model['time_step_size'] = model["time_step_size"]
except KeyError as e:
print(f"Warning: Missing 'time_step_size' key in model. {e}")
except Exception as e:
print(f"Warning: An error occurred while assigning 'time_step_size'. {e}")


def connect_monitor(world: MosaikWorld, model_entities: dict[MosaikEntity],
Expand Down
33 changes: 29 additions & 4 deletions src/illuminator/models/Battery/battery_v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@


# Define the model parameters, inputs, outputs...
# TODO: Currently if a value or category isn't defined in the yaml it doesn't default to the ones below, it simply doesn't run.
battery = IlluminatorModel(
parameters={'max_p': 150, # maximum charging power limit (kW)
'min_p': 250, # maximum discharging power limit (kW)
Expand Down Expand Up @@ -30,7 +31,31 @@

# construct the model
class Battery(ModelConstructor):
parameters={'max_p': 150, # maximum charging power limit (kW)
'min_p': 250, # maximum discharging power limit (kW)
'max_energy': 50, # maximum energy storage capacity of the battery (kWh)
'charge_efficiency': 90, # efficiency of charging the battery (%)
'discharge_efficiency': 90, # efficiency of discharging the battery (%)
'soc_min': 3, # minimum allowable state of charge for the battery (%)
'soc_max': 80, # maximum allowable state of charge for the battery (%)
#'resolution': 1 # time resolution for simulation steps (seconds)
}
inputs={'flow2b': 0, # power flow to/from the battery. Positive for charging, negative for discharging (kW)
}
outputs={'p_out': 20, # output power from the battery after discharge/charge decision (Kw)
'p_in': 20, # input power to the battery (kW)
'soc': 0, # updated state of charge after battery operation (%)
'mod': 0, # operation mode: 0=no action, 1=charge, -1=discharge
'flag': -1, # flag indicating battery status: 1=fully charged, -1=fully discharged, 0=available for control
}
states={'soc': 0,
'flag': 0
}
time_step_size=1
time=None

def __init__(self, **kwargs) -> None:
# TODO make a generalised way of doing this shit in the ModelConstructor __init__()
super().__init__(**kwargs)
self.max_p = self._model.parameters.get('max_p')
self.min_p = self._model.parameters.get('min_p')
Expand All @@ -47,14 +72,13 @@ def step(self, time, inputs, max_advance=900) -> None:
print("\nBattery")
print("inputs (passed): ", inputs)
print("inputs (internal): ", self._model.inputs)
input_data = self.parse_inputs(inputs)
input_data = self.unpack_inputs(inputs)
print("input_data: ", input_data)

current_time = time * self.time_resolution
print('from battery %%%%%%%%', current_time)
for eid, _ in self.model_entities.items(): # weird, I know, but I just want the eid of the only entity there will be
pass
print('eid: ', eid)
eid = list(self.model_entities)[0] # there is only one entity per simulator, so get the first entity

print('state of charge: ', self._model.outputs['soc'])

self._cache = {}
Expand All @@ -76,6 +100,7 @@ def step(self, time, inputs, max_advance=900) -> None:
return time + self._model.time_step_size

def parse_inputs(self, inputs):
# TODO: Move this function to model.py to unpack the inputs, for now we will keep it model specific.
data = {}
for attrs in inputs.values():
for attr, sources in attrs.items():
Expand Down
45 changes: 27 additions & 18 deletions src/illuminator/models/CSV_reader_v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@
'start': None,
'date_format': None,
'delimiter': ',',
'datafile': None,
'next_row': None,
'datafile': None,
},
inputs={},
outputs={},
outputs={'next_row'},
states={'next_row'},
time_step_size=1,
time=None
Expand All @@ -21,21 +20,29 @@
# construct the model
class CSV(ModelConstructor):

parameters={'start': 0,
'date_format': '',
'delimiter': ',',
'datafile': '',
}
inputs={}
outputs={'next_row': ''}
states={}
time_step_size=1
time=None

start_date = None
date_format = None
delimiter = None
datafile = None
next_row = None
modelname = None
attrs = None
#self.eids = []
cache = None

def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)

self.time_resolution = None
self.start_date = None
self.date_format = None
self.delimiter = None
self.datafile = None
self.next_row = None
self.modelname = None
self.attrs = None
#self.eids = []
self.cache = None

self.time_resolution = float(self._model.parameters.get('time_resolution'))
self.delimiter = self._model.parameters.get('delimiter')
self.date_format = self._model.parameters.get('date_format')
self.start_date = arrow.get(self._model.parameters.get('start'), self.date_format)
Expand Down Expand Up @@ -74,11 +81,13 @@ def __init__(self, **kwargs) -> None:
#
# run super().init(self, sid, time_resolution=1, **sim_params)


def init(self, sid, time_resolution=1, **sim_params):
print("check")
meta = super().init(sid, time_resolution, **sim_params)
return meta


def step(self, time, inputs, max_advance=900) -> None:
"""
Perform one step of the simulation.
Expand Down Expand Up @@ -106,7 +115,7 @@ def step(self, time, inputs, max_advance=900) -> None:

# Check date
date = data[0]
expected_date = self.start_date.shift(seconds=time * self.time_resolution)
expected_date = self.start_date.shift(seconds=time * self.time_step_size)
if date != expected_date:
raise IndexError(f'Wrong date "{date}", expected "{expected_date}"')

Expand All @@ -117,7 +126,7 @@ def step(self, time, inputs, max_advance=900) -> None:

self._read_next_row()
if self.next_row is not None:
return time + int((self.next_row[0].int_timestamp - date.int_timestamp) / self.time_resolution)
return time + int((self.next_row[0].int_timestamp - date.int_timestamp) / self.time_step_size)
else:
return max_advance

Expand Down
Loading

0 comments on commit 48d215d

Please sign in to comment.