Skip to content

Commit

Permalink
Merge pull request #159 from Total-RD/ahalev_docs_v3
Browse files Browse the repository at this point in the history
genset/renewable/unbalanced energy docstrings
  • Loading branch information
ahalev authored Dec 1, 2022
2 parents c3809ae + fbf0bba commit 8503b38
Show file tree
Hide file tree
Showing 14 changed files with 351 additions and 40 deletions.
32 changes: 29 additions & 3 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
import os
import sys

from copy import deepcopy
from builtins import object

import pymgrid


Expand All @@ -29,6 +32,7 @@
'sphinx.ext.autosummary',
'sphinx.ext.doctest',
'sphinx.ext.linkcode',
'sphinx.ext.intersphinx',
'nbsphinx',
'nbsphinx_link',
'IPython.sphinxext.ipython_console_highlighting'
Expand All @@ -50,8 +54,13 @@

html_static_path = ['_static']


skip_members = ['yaml_flow_style']
# These are attributes that don't have a __doc__ attribute to read ':meta private:' from.
skip_members = ['yaml_flow_style',
'metadata',
'render_mode',
'reward_range',
'spec'
]


def autodoc_skip_member(app, what, name, obj, skip, options):
Expand All @@ -68,6 +77,17 @@ def autodoc_skip_member(app, what, name, obj, skip, options):
return None


def autodoc_process_signature(app, what, name, obj, options, signature, return_annotation):
"""
If a class signature is being read from cls.__new__, we want to replace it with the signature from cls.__init__.
"""
if what == 'class' and signature[1:] in str(inspect.signature(obj.__new__)):
obj_copy = deepcopy(obj)
obj_copy.__new__ = object.__new__
signature = str(inspect.signature(obj_copy))
return signature, return_annotation


def linkcode_resolve(domain, info):
"""
Determine the URL corresponding to Python object
Expand Down Expand Up @@ -116,8 +136,14 @@ def linkcode_resolve(domain, info):

fn = os.path.relpath(fn, start=os.path.dirname(pymgrid.__file__))

return f"https://github.com/Total-RD/pymgrid/tree/v{pymgrid.__version__}/src/pymgrid/{fn}{linespec}"
return f'https://github.com/Total-RD/pymgrid/tree/v{pymgrid.__version__}/src/pymgrid/{fn}{linespec}'


intersphinx_mapping = {
'gym': ('https://www.gymlibrary.dev/', None)
}


def setup(app):
app.connect('autodoc-skip-member', autodoc_skip_member)
app.connect('autodoc-process-signature', autodoc_process_signature)
30 changes: 30 additions & 0 deletions docs/source/reference/envs/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.. _api.envs:

Reinforcement Learning
======================

.. currentmodule:: pymgrid.envs

Environment classes using the `OpenAI Gym API <https://www.gymlibrary.dev//>`_ for reinforcement learning.

Discrete Environment
--------------------

Environment with a discrete action space.


.. autosummary::
:toctree: ../api/envs/

DiscreteMicrogridEnv

Continuous Environment
----------------------

Environment with a discrete action space.


.. autosummary::
:toctree: ../api/envs/

ContinuousMicrogridEnv
1 change: 1 addition & 0 deletions docs/source/reference/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ This page contains an overview of all public *pymgrid* objects and functions.

microgrid
modules/index
envs/index
Binary file added local/pymgrid reports.docx
Binary file not shown.
93 changes: 84 additions & 9 deletions src/pymgrid/envs/base/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,63 @@


class BaseMicrogridEnv(Microgrid, Env):
"""
Base class for all microgrid environments.
Implements the `OpenAI Gym API <https://www.gymlibrary.dev//>`_ for a microgrid;
inherits from both :class:`.Microgrid` and :class:`gym.Env`.
Parameters
----------
modules : list, Microgrid, NonModularMicrogrid, or int.
The constructor can be called in three ways:
1. Passing a list of microgrid modules. This is identical to the :class:`.Microgrid` constructor.
2. Passing a :class:`.Microgrid` or :class:`.NonModularMicrogrid` instance.
This will effectively wrap the microgrid instance with the Gym API.
3. Passing an integer in [0, 25).
This will be result in loading the corresponding `pymgrid25` benchmark microgrids.
add_unbalanced_module : bool, default True.
Whether to add an unbalanced energy module to your microgrid. Such a module computes and attributes
costs to any excess supply or demand.
Set to True unless ``modules`` contains an :class:`.UnbalancedEnergyModule`.
loss_load_cost : float, default 10.0
Cost per unit of unmet demand. Ignored if ``add_unbalanced_module=False``.
overgeneration_cost : float, default 2.0
Cost per unit of excess generation. Ignored if ``add_unbalanced_module=False``.
flat_spaces : bool, default True
Whether the environment's spaces should be flat.
If True, all continuous spaces are :class:`gym:gym.spaces.Box`.
Otherwise, they are nested :class:`gym:gym.spaces.Dict` of :class:`gym:gym.spaces.Tuple`
of :class:`gym:gym.spaces.Box`, corresponding to the structure of the ``control`` arg of :meth:`.Microgrid.run`.
"""

action_space = None
'Space object corresponding to valid actions.'

observation_space = None
'Space object corresponding to valid observations.'

def __new__(cls, modules, *args, **kwargs):
if isinstance(modules, (NonModularMicrogrid, Microgrid)):
instance = cls.from_microgrid(modules)
cls.__init__ = skip_init(cls, cls.__init__)
return instance
elif "scenario" in kwargs or "microgrid_number" in kwargs:
scenario = kwargs.get("scenario", "pymgrid25")
microgrid_number = kwargs.get("microgrid_number", 0)
instance = cls.from_scenario(microgrid_number=microgrid_number)
cls.__init__ = skip_init(cls, cls.__init__)
return instance

return super().__new__(cls)
elif isinstance(modules, int):
instance = cls.from_scenario(modules)
else:
return super().__new__(cls)

cls.__init__ = skip_init(cls, cls.__init__)
return instance

def __init__(self,
modules,
Expand Down Expand Up @@ -59,8 +103,39 @@ def reset(self):
obs = super().reset()
return flatten(self._nested_observation_space, obs) if self._flat_spaces else obs

@property
def flat_spaces(self):
"""
Whether the environment's spaces are flat.
If True, all continuous spaces are :class:`gym:gym.spaces.Box`.
Otherwise, they are nested :class:`gym:gym.spaces.Dict` of :class:`gym:gym.spaces.Tuple`
of :class:`gym:gym.spaces.Box`, corresponding to the structure of the ``control`` arg of :meth:`Microgrid.run`.
Returns
-------
flat_spaces : bool
Whether the environment's spaces are flat.
"""
return self._flat_spaces

@classmethod
def from_microgrid(cls, microgrid):
"""
Construct a microgrid from
Parameters
----------
microgrid_number : int, default 0
Number of the microgrid to return. ``0<=microgrid_number<25``.
Returns
-------
scenario : pymgrid.Microgrid
The loaded microgrid.
"""
try:
return cls(microgrid.module_tuples(), add_unbalanced_module=False)
except AttributeError:
Expand Down
16 changes: 13 additions & 3 deletions src/pymgrid/envs/base/skip_init.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
def skip_init(cls, init):
"""
Skip init once on cls, and then revert to original init.
:param cls: Class to skip init on.
:param init: original init.
:return: callable that skips init once.
Parameters
----------
cls : Type
Class to skip init on.
init : callable
Original init.
Returns
-------
skip_init : callable
Callable that skips init once.
"""
def reset_init(*args, **kwargs):
cls.__init__ = init
Expand Down
53 changes: 40 additions & 13 deletions src/pymgrid/envs/continuous/continuous.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,53 @@


class ContinuousMicrogridEnv(BaseMicrogridEnv):
def __init__(self,
modules,
add_unbalanced_module=True,
loss_load_cost=10,
overgeneration_cost=2,
flat_spaces=True
):
"""
Microgrid environment with a continuous action space.
self._nested_action_space = self._get_nested_action_space()
super().__init__(modules,
add_unbalanced_module=add_unbalanced_module,
loss_load_cost=loss_load_cost,
overgeneration_cost=overgeneration_cost,
flat_spaces=flat_spaces)
Implements the `OpenAI Gym API <https://www.gymlibrary.dev//>`_ for a microgrid;
inherits from both :class:`.Microgrid` and :class:`gym.Env`.
Parameters
----------
modules : list, Microgrid, NonModularMicrogrid, or int.
The constructor can be called in three ways:
1. Passing a list of microgrid modules. This is identical to the :class:`.Microgrid` constructor.
2. Passing a :class:`.Microgrid` or :class:`.NonModularMicrogrid` instance.
This will effectively wrap the microgrid instance with the Gym API.
3. Passing an integer in [0, 25).
This will be result in loading the corresponding `pymgrid25` benchmark microgrids.
add_unbalanced_module : bool, default True.
Whether to add an unbalanced energy module to your microgrid. Such a module computes and attributes
costs to any excess supply or demand.
Set to True unless ``modules`` contains an :class:`.UnbalancedEnergyModule`.
loss_load_cost : float, default 10.0
Cost per unit of unmet demand. Ignored if ``add_unbalanced_module=False``.
overgeneration_cost : float, default 2.0
Cost per unit of excess generation. Ignored if ``add_unbalanced_module=False``.
flat_spaces : bool, default True
Whether the environment's spaces should be flat.
If True, all continuous spaces are :class:`gym:gym.spaces.Box`.
Otherwise, they are nested :class:`gym:gym.spaces.Dict` of :class:`gym:gym.spaces.Tuple`
of :class:`gym:gym.spaces.Box`, corresponding to the structure of the ``control`` arg of :meth:`.Microgrid.run`.
"""
_nested_action_space = None

def _get_nested_action_space(self):
return Dict({name: Tuple([module.action_spaces['normalized'] for module in modules_list])
for name, modules_list in self.fixed.iterdict() if modules_list[0].is_source})

def _get_action_space(self):
self._nested_action_space = self._get_nested_action_space()
return flatten_space(self._nested_action_space) if self._flat_spaces else self._nested_action_space

def _get_action(self, action):
Expand Down
11 changes: 9 additions & 2 deletions src/pymgrid/envs/discrete/discrete.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from itertools import permutations
from warnings import warn
import numpy as np
import yaml

from itertools import permutations
from gym.spaces import Discrete
from math import isclose
from warnings import warn

from pymgrid.envs.base import BaseMicrogridEnv
from pymgrid.utils.logger import ModularLogger
Expand All @@ -24,6 +26,11 @@ class DiscreteMicrogridEnv(BaseMicrogridEnv):
Each tuple contains three elements (module_name, total_actions_for_{module_name}, action_num).
For example: (('genset', 0), 2, 1) is a tuple defining the first element (of two) for ('genset', 0).
"""

yaml_tag = u"!DiscreteMicrogridEnv"
yaml_loader = yaml.SafeLoader
yaml_dumper = yaml.SafeDumper

def __init__(self,
modules,
add_unbalanced_module=True,
Expand Down
8 changes: 6 additions & 2 deletions src/pymgrid/microgrid/microgrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,7 @@ def dump(self, stream=None):
on the value of ``stream``. If ``stream is None``, array-like objects are serialized inline. If ``stream`` is
a stream to a file-like object, however, array-like objects will be serialized as `.csv.gz` files in a
directory relative to ``stream``, and the relative locations stored inline in the YAML file. For an example of
this behavior, see `data/scenario/pymgrid25/microgrid_0`.
this behavior, see `data/scenario/pymgrid25/microgrid_0`.
"""
return yaml.safe_dump(self, stream=stream)
Expand Down Expand Up @@ -630,7 +630,7 @@ def from_scenario(cls, microgrid_number=0):
Parameters
----------
microgrid_number : int, default 0
Number of the microgrid to return. 0<=microgrid_number<25.
Number of the microgrid to return. ``0<=microgrid_number<25``.
Returns
-------
Expand All @@ -639,6 +639,10 @@ def from_scenario(cls, microgrid_number=0):
"""
from pymgrid import PROJECT_PATH
n = microgrid_number

if n not in np.arange(25):
raise TypeError(f'Invalid microgrid_number {n}, must be an integer in the range [0, 25).')

with open(PROJECT_PATH / f"data/scenario/pymgrid25/microgrid_{n}/microgrid_{n}.yaml", "r") as f:
return cls.load(f)

Expand Down
2 changes: 1 addition & 1 deletion src/pymgrid/modules/base/base_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ def to_normalized(self, value, act=False, obs=False):

def from_normalized(self, value, act=False, obs=False):
"""
Un-normalized an action or observation.
Un-normalize an action or observation.
Parameters
----------
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 @@ -48,7 +48,7 @@ class GensetModule(BaseMicrogridModule):
If False, actions are clipped to the limit possible.
provided_energy_name : str, default "genset_production"
Name of the energy provided by this microgrid used in logging.
Name of the energy provided by this module, to be used in logging.
"""
module_type = 'genset', 'fixed'
Expand Down
Loading

0 comments on commit 8503b38

Please sign in to comment.