Skip to content

Commit

Permalink
battery model converted to version 3 structure. Example provided (bat…
Browse files Browse the repository at this point in the history
…tery.yaml)
  • Loading branch information
JortGroen committed Dec 3, 2024
1 parent ab51950 commit 702ecc8
Show file tree
Hide file tree
Showing 3 changed files with 325 additions and 2 deletions.
58 changes: 58 additions & 0 deletions examples/battery.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
scenario:
name: "batteryTest" # 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: './battery_charger.csv' # path to the file with the data
delimiter: ','
date_format: 'YYYY-MM-DD HH:mm:ss'
time_resolution: 900

# - 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: 1
# outputs:
# out1: 0
# parameters:
# param1: "adding tens"

- name: Battery1
type: Battery # models can reuse the same type
parameters:
max_p: 150 # maximum charging power limit (kW)
min_p: -250 # maximum discharging power limit (kW)
max_energy: 100 # maximum energy storage capacity of the battery (kWh)
charge_efficiency: 50 # efficiency of charging the battery (%)
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:
p_out: 20 # output power from the battery after discharge/charge decision (Kw)
p_in: 20 # input power to the battery (kW)
soc: 60 # 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

connections:
# - from: CSVB.p # start model, pattern: model_name.output_name/input_name
# to: Battery1.p_in
# to: Adder1.in1 # end model
#time_shifted: True
- from: CSVB.flow2b
to: Battery1.flow2b

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
264 changes: 264 additions & 0 deletions src/illuminator/models/Battery/battery_v3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
from illuminator.builder import IlluminatorModel, ModelConstructor
import arrow


# Define the model parameters, inputs, outputs...
battery = IlluminatorModel(
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
)

# construct the model
class Battery(ModelConstructor):
def __init__(self, **kwargs) -> None:
super().__init__(**kwargs)
self.max_p = self._model.parameters.get('max_p')
self.min_p = self._model.parameters.get('min_p')
self.max_energy = self._model.parameters.get('max_energy')
self.charge_efficiency = self._model.parameters.get('charge_efficiency')/100
self.discharge_efficiency = self._model.parameters.get('discharge_efficiency')/100
self.soc_min = self._model.parameters.get('soc_min')
self.soc_max = self._model.parameters.get('soc_max')
#self. = self._model.parameters.get('')


def step(self, time, inputs, max_advance=900) -> None:
# charge the battery
print("\nBattery")
print("inputs (passed): ", inputs)
print("inputs (internal): ", self._model.inputs)
input_data = self.parse_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)
print('state of charge: ', self._model.outputs['soc'])

self._cache = {}
# if input_data['flow2b'] != 0:
#raghav: In this model, the input should come from the controller p_ask
self._cache[eid] = self.output_power(input_data['flow2b'], self._model.outputs['soc'])

# self._cache[eid] = self.entities[eid].output_power(energy_ask, self.soc[eid]) # * max_advance if trigger

# print(self._cache[eid])
# [p_out:,soc:,flag:]
self._model.outputs['soc'] = self._cache[eid]['soc']
self._model.outputs['flag'] = self._cache[eid]['flag']
# check = list(self.soc.values())
# check2 = check[0] # this is so that the value that battery sends is dictionary and not a dictionary of a dictionary.
# out = yield self.mosaik.set_data({'Battery-0': {'Controller-0.ctrl_0': {'soc': check2}}}) # this code is supposed to hold the soc value and
print("new state of charge:", self.soc)
print('\n')
return time + self._model.time_step_size

def parse_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


# this method is called from the output_power method when conditions are met.
def discharge_battery(self, flow2b:int) -> dict: #flow2b is in kw
"""
Discharge the battery, calculate the state of charge and return parameter information
...
Parameters
----------
flow2b : int
(???) in kW
Returns
-------
re_params : dict
Collection of parameters and their respective values
"""
hours = self.time_resolution / 60 / 60
flow = max(self.min_p, flow2b)
if (flow < 0):
energy2discharge = flow * hours / self.discharge_efficiency # (-ve)
energy_capacity = ((self.soc_min - self.soc) / 100) * self.max_energy
if self.soc <= self.soc_min:
self.flag = -1
self.powerout = 0
else:
if energy2discharge > energy_capacity:
# more than enough energy to discharge
# Check if minimum energy of the battery is reached -> Adjust power if necessary
self.soc = self.soc + (energy2discharge / self.max_energy * 100)
self.powerout = flow
self.flag = 0 # Set flag as ready to discharge or charge

else: # Fully-discharge Case
self.powerout = energy_capacity / self.discharge_efficiency / hours
# warn('\n Home Battery is fully discharged!! Cannot deliver more energy!')
self.soc = self.soc_min
self.flag = -1 # Set flag as 1 to show fully discharged state
self.soc = round(self.soc, 3)
re_params = {'p_out': self.powerout,
# 'energy_drain': output_show,
# 'energy_consumed': 0,
'p_in': flow,
'soc': self.soc,
'mod': -1,
'flag': self.flag}
#'i_soc': self.i_soc}

# here we are returning the values of these parameters which will be needed by another python model
return re_params


def charge_battery(self, flow2b:int) -> dict:
"""
Charge the battery, calculate the state of charge and return parameter information
...
Parameters
----------
flow2b : int
(???) in kW
Returns
-------
re_params : dict
Collection of parameters and their respective values
"""
hours = self.time_resolution / 60 / 60
flow = min(self.max_p, flow2b)
if (flow > 0):
energy2charge = flow * hours * self.charge_efficiency # (-ve)
energy_capacity = ((self.soc_max - self.soc) / 100) * self.max_energy
if self.soc >= self.soc_max:
self.flag = 1
self.powerout = 0
else:
if energy2charge <= energy_capacity:
self.soc = self.soc + (energy2charge / self.max_energy * 100)
self.powerout = flow
self.flag = 0 # Set flag as ready to discharge or charge

else: # Fully-charge Case
self.powerout = energy_capacity / self.charge_efficiency / hours
# warn('\n Home Battery is fully discharged!! Cannot deliver more energy!')
self.soc = self.soc_max
self.flag = 1 # Set flag as 1 to show fully discharged state
self.soc = round(self.soc, 3)
re_params = {'p_out': self.powerout,
# 'energy_consumed': output_show,
# 'energy_drain': 0,
'p_in': flow,
'soc': self.soc,
'mod': 1,
'flag': self.flag}

return re_params


# this method is like a controller which calls a method depending on the condition.
# first, this is checked. As per the p_ask and soc, everything happens.
# p_ask and soc are the parameters whos values we have to provide when we want to create an object of this class. i.e,
# when we want to make a battery model.
def output_power(self, flow2b:int, soc:int) -> dict:#charging power: positive; discharging power:negative
"""
Gives information depending on the current flow2b and soc value.
If there is no power demand it gives in current battery state of charge information.
If there is a negative demand for power then it discharged the battery. Alternatively it charges the battery.
...
Parameters
----------
flow2b : int
???
soc : int
The current state of charge (SOC)
Returns
-------
re_params : dict
Collection of parameters and their respective values
"""
self.soc = soc # here we assign the value of soc we provide to the attribute self.soc
data_ret = {}
# {'p_out',
# 'soc',
# 'mod', # 0 = noaction,1 = charge,-1=discharge
# 'flag',} # 1 means full charge, -1 means full discharge, 0 means available for control
# conditions start:
if flow2b == 0: # i.e when there isn't a demand of power at all,

# soc can never exceed the limit so when it is equal to the max, we tell it is completely charged
if self.soc >= self.soc_max:
self.flag = 1 # meaning battery object we created is fully charged

# soc can never exceed the limit so when it is equal to the min, we tell it is completely discharged
elif self.soc <= self.soc_min:
self.flag = -1

# if the soc is between the min and max values, it is ready to be discharged or charged as per the situation
else:
self.flag = 0 # meaning it is available to operate.

# here we are sending the current state of the battery
re_params={'p_out': 0,
# 'energy_drain': 0,
# 'energy_consumed': 0,
'p_in': 0,
'soc': self.soc,
'mod': 0,
'flag': self.flag}
else:
# if the p_ask is a -ve value, it means battery needs to discharge.
if flow2b < 0: #discharge

# Can the battery discharge or not depends on the current state of the battery for which we call the
# method discharge_battery. If the p_ask < 0 condition is met, the program directly goes the method.
re_params = self.discharge_battery(flow2b)

# other option is for p_ask to be > 0 which means we need to charge.
else:

# Can the battery charge depends on the current state of the battery for which we call the
# method charge_battery. If the p_ask > 0 condition is met, the program directly goes the method.
re_params = self.charge_battery(flow2b)


return re_params

if __name__ == '__main__':
# Create a model by inheriting from ModelConstructor
# and implementing the step method
battery_model = Battery(battery)

print(battery_model.step(1))
5 changes: 3 additions & 2 deletions src/illuminator/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
# This is a temporary fix to make models more accessible
# In the future, a model will be contained in a single file, where the
# name of the file matches the name of the model
from .Battery.battery_model import BatteryModel
#from .Battery.battery_model import BatteryModel
from .Battery.battery_v3 import Battery
from .collector import Collector
# from .mosaik_csv import CSV
from .CSV_reader_v3 import CSV
from .PV.pv_mosaik import PvAdapter
from .adder import Adder

__all__ = ['BatteryModel',
__all__ = ['Battery',
'Collector',
'Adder',
'CSV',
Expand Down

0 comments on commit 702ecc8

Please sign in to comment.