Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various bug fixes #166

Merged
merged 20 commits into from
Dec 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
EXTRAS["genset_mpc"] = ["Mosek", "cvxopt"]
EXTRAS["dev"] = [
"pytest",
"pytest-subtests",
"flake8",
"sphinx",
"pydata_sphinx_theme",
Expand Down
2 changes: 1 addition & 1 deletion src/pymgrid/convert/to_nonmodular_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def add_pv_params(pv_module, params_dict):
_add_to_architecture(params_dict, 'PV')
_add_to_parameters(params_dict, PV_rated_power=pv_module.max_act)
_add_to_df_actions(params_dict, 'pv_consummed','pv_curtailed','pv')
_add_to_df_status(params_dict, pv=[pv_module.current_renewable.item()])
_add_to_df_status(params_dict, pv=[pv_module.current_renewable])
_add_to_df_actual_generation(params_dict, 'pv_consummed','pv_curtailed')
_add_to_control_dict(params_dict, 'pv_consummed', 'pv_curtailed', 'pv')

Expand Down
29 changes: 14 additions & 15 deletions src/pymgrid/envs/discrete/discrete.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ def __init__(self,
overgeneration_cost=overgeneration_cost)

self.action_space, self.actions_list = self._get_action_space()
self.log_dict = ModularLogger()

def _get_action_space(self):
"""
Expand All @@ -55,19 +54,19 @@ def _get_action_space(self):
:return:
"""
# n_actions = 2**(self.modules.fixed.)
fixed_sources = [(module.name, module.action_space['unnormalized'].shape[0], n_actions)
for module in self.modules.fixed.sources.iterlist()
for n_actions in range(module.action_space['unnormalized'].shape[0])]
controllable_sources = [(module.name, module.action_space.shape[0], n_actions)
for module in self.modules.controllable.sources.iterlist()
for n_actions in range(module.action_space.shape[0])]

fixed_sources.extend([(module.name, module.action_space['unnormalized'].shape[0], n_actions)
for module in self.modules.fixed.source_and_sinks.iterlist()
for n_actions in range(module.action_space['unnormalized'].shape[0])])
controllable_sources.extend([(module.name, module.action_space.shape[0], n_actions)
for module in self.modules.controllable.source_and_sinks.iterlist()
for n_actions in range(module.action_space.shape[0])])

priority_lists = list(permutations(fixed_sources))
priority_lists = list(permutations(controllable_sources))
n_actions = len(priority_lists)

if n_actions > 1000:
warn(f'Microgrid with {len(fixed_sources)} fixed source modules defines large action space with '
warn(f'Microgrid with {len(controllable_sources)} fixed source modules defines large action space with '
f'{n_actions} elements.')

space = Discrete(n_actions)
Expand All @@ -80,10 +79,6 @@ def _get_action(self, action_num):

action = self.get_empty_action()
loads, total_load = self._get_load()
for load_module, load in loads.items():
module_name, module_num = load_module
action[module_name][module_num] = -1.0 * load

renewable = self._get_renewable()
assert total_load >= 0 and renewable >= 0

Expand Down Expand Up @@ -147,18 +142,22 @@ def _get_action(self, action_num):
# If we have, e.g. a genset (with two actions)
action[module_name][element_number] = np.array(action[module_name][element_number])

bad_keys = [k for k, v in action.items() if v is None]
if len(bad_keys):
raise RuntimeError(f'None values found in action, corresponding to keys\n\t{bad_keys}')

return action

def step(self, action):
self.log_dict.log(action=action)
self._microgrid_logger.log(action=action)
microgrid_action = self._get_action(action)
return super().step(microgrid_action, normalized=False)

def _get_load(self):
loads = dict()
total_load = 0.0
for fixed_sink in self.fixed.sinks.iterlist():
loads[fixed_sink.name] = fixed_sink.max_consumption.item()
loads[fixed_sink.name] = fixed_sink.max_consumption
total_load += fixed_sink.max_consumption

return loads, total_load
Expand Down
49 changes: 31 additions & 18 deletions src/pymgrid/microgrid/microgrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ def __init__(self,
overgeneration_cost)

self._balance_logger = ModularLogger()
self._microgrid_logger = ModularLogger() # log additional information.

def _get_unbalanced_energy_module(self,
loss_load_cost,
Expand Down Expand Up @@ -150,7 +151,8 @@ def reset(self):
"""
return {
**{name: [module.reset() for module in module_list] for name, module_list in self.modules.iterdict()},
**{"balance": self._balance_logger.flush()}
**{"balance": self._balance_logger.flush(),
"other": self._microgrid_logger.flush()}
}

def run(self, control, normalized=True):
Expand Down Expand Up @@ -181,11 +183,14 @@ def run(self, control, normalized=True):
control_copy = control.copy()
microgrid_step = MicrogridStep()

for name, modules in self.static.iterdict():
for name, modules in self.fixed.iterdict():
for module in modules:
microgrid_step.append(name, *module.step(0.0, normalized=False))

for name, modules in self.fixed.iterdict():
fixed_provided, fixed_consumed, _ = microgrid_step.balance()
log_dict = self._get_log_dict(fixed_provided, fixed_consumed, prefix='fixed')

for name, modules in self.controllable.iterdict():
try:
module_controls = control_copy.pop(name)
except KeyError:
Expand All @@ -203,7 +208,8 @@ def run(self, control, normalized=True):
provided, consumed, _ = microgrid_step.balance()
difference = provided - consumed # if difference > 0, have an excess. Try to use flex sinks to dissapate
# otherwise, insufficient. Use flex sources to make up
log_dict = self._get_log_dict(provided, consumed, prefix='fixed')

log_dict = self._get_log_dict(provided-fixed_provided, consumed-fixed_consumed, log_dict=log_dict, prefix='controllable')

if len(control_copy) > 0:
warn(f'\nIgnoring the following keys in passed control:\n {list(control_copy.keys())}')
Expand Down Expand Up @@ -250,7 +256,7 @@ def run(self, control, normalized=True):
return microgrid_step.output()

def _get_log_dict(self, provided_energy, absorbed_energy, log_dict=None, prefix=None):
_log_dict = dict(provided_to_microgrid=provided_energy, absorbed_by_microgrid=absorbed_energy)
_log_dict = dict(provided_to_microgrid=provided_energy, absorbed_from_microgrid=absorbed_energy)
_log_dict = {(prefix + '_' + k if prefix is not None else k): v for k, v in _log_dict.items()}
if log_dict:
_log_dict.update(log_dict)
Expand Down Expand Up @@ -278,7 +284,7 @@ def sample_action(self, strict_bound=False, sample_flex_modules=False):

"""

module_iterator = self._modules.module_dict() if sample_flex_modules else self._modules.fixed.module_dict()
module_iterator = self._modules.module_dict() if sample_flex_modules else self._modules.controllable.module_dict()
return {module_name: [module.sample_action(strict_bound=strict_bound) for module in module_list]
for module_name, module_list in module_iterator.items()
if module_list[0].action_space.shape[0]}
Expand All @@ -302,7 +308,7 @@ def get_empty_action(self, sample_flex_modules=False):
Empty action.

"""
module_iterator = self._modules.module_dict() if sample_flex_modules else self._modules.fixed.module_dict()
module_iterator = self._modules.module_dict() if sample_flex_modules else self._modules.controllable.module_dict()

return {module_name: [None]*len(module_list) for module_name, module_list in module_iterator.items()
if module_list[0].action_space.shape[0]}
Expand Down Expand Up @@ -380,25 +386,21 @@ def get_log(self, as_frame=True, drop_singleton_key=False):
for key, value in self._balance_logger.to_dict().items():
_log_dict[('balance', 0, key)] = value

if hasattr(self, 'log_dict'):
for key, value in self.log_dict.items():
_log_dict[(key, '', '')] = value
for key, value in self._microgrid_logger.items():
_log_dict[(key, 0, '')] = value

col_names = ['module_name', 'module_number', 'field']

df = pd.DataFrame(_log_dict)
df.columns = pd.MultiIndex.from_tuples(df.columns.to_list(), names=col_names)

if drop_singleton_key:
keys_arr = np.array(list(_log_dict.keys()))
module_counters = keys_arr[:, 1].astype(np.int64)
if module_counters.min() == module_counters.max():
_log_dict = {(key[0], key[2]): value for key, value in _log_dict.items()}
col_names.pop(1)
df.columns = df.columns.remove_unused_levels()

if as_frame:
df = pd.DataFrame(_log_dict)
df.columns.set_names(col_names, inplace=True)
return df

return _log_dict
return df.to_dict()

def get_forecast_horizon(self):
"""
Expand Down Expand Up @@ -484,6 +486,17 @@ def flex(self):
"""
return self._modules.flex

@property
def controllable(self):
"""
List of all controllable modules in the microgrid.

Returns
-------
list of modules
"""
return self._modules.controllable

@property
def module_list(self):
"""
Expand Down
8 changes: 7 additions & 1 deletion src/pymgrid/modules/base/base_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,9 @@ def as_source(self, energy_demand):
f'module is not a source and ' \
f'can only be called with negative energy.'

if self.module_type[-1] == 'fixed':
return self.update(None, as_source=True)

if energy_demand > self.max_production:
if self.raise_errors:
self._raise_error(energy_demand, self.max_production, as_source=True)
Expand Down Expand Up @@ -290,6 +293,9 @@ def as_sink(self, energy_excess):

assert energy_excess >= 0

if self.module_type[-1] == 'fixed':
return self.update(None, as_sink=True)

if energy_excess > self.max_consumption:
if self.raise_errors:
self._raise_error(energy_excess, self.max_consumption, as_sink=True)
Expand Down Expand Up @@ -324,7 +330,7 @@ def update(self, external_energy_change, as_source=False, as_sink=False):

Parameters
----------
external_energy_change : float
external_energy_change : float or None
Amount of energy to provide or absorb.
as_source : bool
Whether the module is acting as a source.
Expand Down
2 changes: 1 addition & 1 deletion src/pymgrid/modules/battery_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class BatteryModule(BaseMicrogridModule):
If False, actions are clipped to the limit possible.

"""
module_type = ('battery', 'fixed')
module_type = ('battery', 'controllable')
yaml_tag = f"!BatteryModule"
yaml_dumper = yaml.SafeDumper
yaml_loader = yaml.SafeLoader
Expand Down
2 changes: 1 addition & 1 deletion src/pymgrid/modules/genset_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class GensetModule(BaseMicrogridModule):
Name of the energy provided by this module, to be used in logging.

"""
module_type = 'genset', 'fixed'
module_type = 'genset', 'controllable'
yaml_tag = f"!Genset"
yaml_dumper = yaml.SafeDumper
yaml_loader = yaml.SafeLoader
Expand Down
2 changes: 1 addition & 1 deletion src/pymgrid/modules/grid_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class GridModule(BaseTimeSeriesMicrogridModule):

"""

module_type = ('grid', 'fixed')
module_type = ('grid', 'controllable')

yaml_tag = u"!GridModule"
yaml_loader = yaml.SafeLoader
Expand Down
2 changes: 1 addition & 1 deletion src/pymgrid/modules/load_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class LoadModule(BaseTimeSeriesMicrogridModule):
If False, actions are clipped to the limit possible.

"""
module_type = ('load', 'static')
module_type = ('load', 'fixed')
yaml_tag = u"!LoadModule"
yaml_dumper = yaml.SafeDumper
yaml_loader = yaml.SafeLoader
Expand Down
28 changes: 14 additions & 14 deletions src/pymgrid/modules/module_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,23 +174,23 @@ def get_subcontainers(modules):
"""

:return: List[Tuple]
3-element tuples of (fixed_flex_static, source_or_sink, container)
3-element tuples of (fixed_flex_controllable, source_or_sink, container)

"""
source_sink_keys = ('sources' , 'sinks', 'source_and_sinks')
source_sink_keys = ('sources', 'sinks', 'source_and_sinks')
fixed = {k: dict() for k in source_sink_keys}
flex = {k: dict() for k in source_sink_keys}
static = {k: dict() for k in source_sink_keys}
controllable = {k: dict() for k in source_sink_keys}

module_names = dict()

for module in modules:
try: # module is a tuple
module_name, module = module
fixed_flex_static = module.__class__.module_type[1]
fixed_flex_controllable = module.__class__.module_type[1]
except TypeError: # module is a module
try:
module_name, fixed_flex_static = module.__class__.module_type
module_name, fixed_flex_controllable = module.__class__.module_type
except TypeError:
raise NotImplementedError(
f'Must define the class attribute module_type for class {module.__class__.__name__}')
Expand All @@ -201,19 +201,19 @@ def get_subcontainers(modules):
source_sink_both = 'source_and_sinks' if module.is_sink and module.is_source else \
'sources' if module.is_source else 'sinks'

if fixed_flex_static == 'fixed':
if fixed_flex_controllable == 'fixed':
d = fixed
elif fixed_flex_static == 'flex':
elif fixed_flex_controllable == 'flex':
d = flex
elif fixed_flex_static == 'static':
d = static
elif fixed_flex_controllable == 'controllable':
d = controllable
else:
raise TypeError(f'Cannot parse fixed_flex_static from module type {module.__class__.module_type}')
raise TypeError(f'Cannot parse fixed_flex_controllable from module type {module.__class__.module_type}')

try:
module_names[module_name] = (fixed_flex_static, source_sink_both)
module_names[module_name] = (fixed_flex_controllable, source_sink_both)
except KeyError:
raise NameError(f'Attempted to add module {module_name} of type {(fixed_flex_static, source_sink_both)}, '
raise NameError(f'Attempted to add module {module_name} of type {(fixed_flex_controllable, source_sink_both)}, '
f'but there is an identically named module of type {module_names[module_name]}.')

try:
Expand All @@ -224,10 +224,10 @@ def get_subcontainers(modules):

modules_dict = dict(fixed=fixed,
flex=flex,
static=static)
controllable=controllable)

containers = {(ffs, source_sink_both): _ModuleSubContainer(modules_dict[ffs][source_sink_both])
for ffs in ('fixed', 'flex', 'static')
for ffs in ('fixed', 'flex', 'controllable')
for source_sink_both in source_sink_keys}

return containers
Expand Down
5 changes: 3 additions & 2 deletions src/pymgrid/modules/renewable_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ def update(self, external_energy_change, as_source=False, as_sink=False):
assert as_source, f'Class {self.__class__.__name__} can only be used as a source.'
assert external_energy_change <= self.current_renewable, f'Cannot provide more than {self.current_renewable}'

info = {'provided_energy': external_energy_change, 'curtailment': self.current_renewable.item()-external_energy_change}
info = {'provided_energy': external_energy_change,
'curtailment': self.current_renewable-external_energy_change}

return 0.0, self._done(), info

Expand All @@ -99,7 +100,7 @@ def current_renewable(self):
Renewable production.

"""
return self._time_series[self._current_step]
return self._time_series[self._current_step].item()

@property
def is_source(self):
Expand Down
17 changes: 11 additions & 6 deletions tests/helpers/modular_microgrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
)


def get_modular_microgrid(remove_modules=(), additional_modules=None, add_unbalanced_module=True):
def get_modular_microgrid(remove_modules=(), retain_only=None, additional_modules=None, add_unbalanced_module=True):

modules = dict(
genset=GensetModule(running_min_production=10, running_max_production=50, genset_cost=0.5),
Expand All @@ -30,11 +30,16 @@ def get_modular_microgrid(remove_modules=(), additional_modules=None, add_unbala
grid=GridModule(max_import=100, max_export=0, time_series=np.ones((100, 3)), raise_errors=True)
)

for module in remove_modules:
try:
modules.pop(module)
except KeyError:
raise NameError(f"Module {module} not one of default modules {list(modules.keys())}.")
if retain_only is not None:
modules = {k: v for k, v in modules.items() if k in retain_only}
if remove_modules:
raise RuntimeError('Can pass either remove_modules or retain_only, but not both.')
else:
for module in remove_modules:
try:
modules.pop(module)
except KeyError:
raise NameError(f"Module {module} not one of default modules {list(modules.keys())}.")

modules = list(modules.values())
modules.extend(additional_modules if additional_modules else [])
Expand Down
Loading