Skip to content

Commit

Permalink
prep the addition of another reward to compute N-1
Browse files Browse the repository at this point in the history
  • Loading branch information
BDonnot committed Mar 22, 2024
1 parent aae1d2c commit 98b748a
Show file tree
Hide file tree
Showing 17 changed files with 243 additions and 80 deletions.
11 changes: 10 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,21 @@ Change Log
--------------------
- [FIXED] a bug with shunts when `nb_busbar_per_sub` >= 2
- [FIXED] some bugs preventing backward compatibility
- [FIXED] an issue in the computation of gen_q when intialized with pypowsybl
(some overflow cpp side leading to infinite number in gen_q)
- [ADDED] some information of compilation directly in the cpp module
- [ADDED] some information of compilation available in the python `compilation_options`
module python side
- [ADDED] some convenient methods for `ContingencyAnalysis` python side (most
notably the possibility to initialize it from a `LightSimBackend` and to
change the topology of the grid)
- [ADDED] a "reward" module in lightsim2grid with custom reward
based on lightsim2grid.
- [IMPROVED] time measurments in python and c++
- [IMPROVED] now test lightsim2grid with oldest grid2op version
- [IMPROVED] speed, by accelerating the reading back of the data (now read only once and then
pointers are re used)
- [IMPROVED] c++ side avoid allocating memory (which allow to gain speed python side too)
- [ADDED] some information of compilation directly in the cpp module

[0.8.0] 2024-03-18
--------------------
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
author = 'Benjamin DONNOT'

# The full version, including alpha/beta/rc tags
release = "0.8.1.dev0"
release = "0.8.1.dev1"
version = '0.8'

# -- General configuration ---------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ As from version 0.5.3:
use_with_grid2op
benchmarks
use_solver
rewards
physical_law_checker


Expand Down
2 changes: 1 addition & 1 deletion docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ this way:
from grid2op.Agent import RandomAgent
# create an environment
env_name = "rte_case14_realistic" # for example, other environments might be usable
env_name = "l2rpn_case14_sandbox" # for example, other environments might be usable
env = grid2op.make(env_name,
backend=LightSimBackend() # this is the only change you have to make!
)
Expand Down
13 changes: 13 additions & 0 deletions docs/rewards.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Custom Rewards (doc in progress)
=======================================

Detailed usage
--------------------------

.. automodule:: lightsim2grid.rewards
:members:
:autosummary:

* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
13 changes: 10 additions & 3 deletions lightsim2grid/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Copyright (c) 2020, RTE (https://www.rte-france.com)
# Copyright (c) 2020-2024, RTE (https://www.rte-france.com)
# See AUTHORS.txt
# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
# you can obtain one at http://mozilla.org/MPL/2.0/.
# SPDX-License-Identifier: MPL-2.0
# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
__version__ = "0.8.1.dev0"
__version__ = "0.8.1.dev1"

__all__ = ["newtonpf", "SolverType", "ErrorType", "solver"]
__all__ = ["newtonpf", "SolverType", "ErrorType", "solver", "compilation_options"]

# import directly from c++ module
from lightsim2grid.solver import SolverType
Expand Down Expand Up @@ -46,3 +46,10 @@
# grid2op is not installed, the SecurtiyAnalysis module will not be available
pass
print(f"ContingencyAnalysis import error: {exc_}")

try:
__all__.append("rewards")
except ImportError as exc_:
# grid2op is not installed, the SecurtiyAnalysis module will not be available
pass
print(f"ContingencyAnalysis import error: {exc_}")
46 changes: 32 additions & 14 deletions lightsim2grid/contingencyAnalysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,20 @@ class ContingencyAnalysis(object):
STR_TYPES = (str, np.str_) # np.str deprecated in numpy 1.20 and earlier versions not supported anyway

def __init__(self, grid2op_env):
if not isinstance(grid2op_env.backend, LightSimBackend):
raise RuntimeError("This class only works with LightSimBackend")
self.grid2op_env = grid2op_env.copy()
self.computer = ContingencyAnalysisCPP(self.grid2op_env.backend._grid)
from grid2op.Environment import Environment
if isinstance(grid2op_env, Environment):
if not isinstance(grid2op_env.backend, LightSimBackend):
raise RuntimeError("This class only works with LightSimBackend")
self._ls_backend = grid2op_env.backend.copy()
elif isinstance(grid2op_env, LightSimBackend):
if hasattr(grid2op_env, "_is_loaded") and not grid2op_env._is_loaded:
raise RuntimeError("Impossible to init a `ContingencyAnalysis` "
"with a backend that has not been initialized.")
self._ls_backend = grid2op_env.copy()
else:
raise RuntimeError("`ContingencyAnalysis` can only be created "
"with a grid2op `Environment` or a `LightSimBackend`")
self.computer = ContingencyAnalysisCPP(self._ls_backend._grid)
self._contingency_order = {} # key: contingency (as tuple), value: order in which it is entered
self._all_contingencies = []
self.__computed = False
Expand All @@ -100,16 +110,24 @@ def all_contingencies(self, val):
raise RuntimeError("Impossible to add new topologies like this. Please use `add_single_contingency` "
"or `add_multiple_contingencies`.")

def clear(self):
def update_grid(self, backend_act):
self.clear(with_contlist=False)
self._ls_backend.apply_action(backend_act)
self.computer = ContingencyAnalysisCPP(self._ls_backend._grid)

def clear(self, with_contlist=True):
"""
Clear the list of contingencies to simulate
"""
self.computer.clear()
self._contingency_order = {}
self.__computed = False
self._vs = None
self._ampss = None
self._all_contingencies = []
if with_contlist:
self.computer.clear()
self._contingency_order = {}
self._all_contingencies = []
else:
self.computer.clear_results_only()

def _single_cont_to_li_int(self, single_cont):
li_disc = []
Expand All @@ -118,7 +136,7 @@ def _single_cont_to_li_int(self, single_cont):

for stuff in single_cont:
if isinstance(stuff, type(self).STR_TYPES):
stuff = np.where(self.grid2op_env.name_line == stuff)
stuff = np.where(type(self._ls_backend).name_line == stuff)
stuff = stuff[0]
if stuff.size == 0:
# name is not found
Expand Down Expand Up @@ -233,7 +251,7 @@ def add_all_n1_contingencies(self):
for single_cont_id in range(env.n_line):
self.add_single_contingency(single_cont_id)
"""
for single_cont_id in range(self.grid2op_env.n_line):
for single_cont_id in range(type(self._ls_backend).n_line):
self.add_single_contingency(single_cont_id)

def get_flows(self, *args):
Expand Down Expand Up @@ -302,10 +320,10 @@ def compute_V(self):
been entered. Please use `get_flows()` method for easier reading back of the results
"""
v_init = self.grid2op_env.backend.V
v_init = self._ls_backend.V
self.computer.compute(v_init,
self.grid2op_env.backend.max_it,
self.grid2op_env.backend.tol)
self._ls_backend.max_it,
self._ls_backend.tol)
self._vs = self.computer.get_voltages()
self.__computed = True
return self._vs
Expand Down Expand Up @@ -342,6 +360,6 @@ def compute_P(self):

def close(self):
"""permanently close the object"""
self.grid2op_env.close()
self._ls_backend.close()
self.clear()
self.computer.close()
4 changes: 2 additions & 2 deletions lightsim2grid/gridmodel/from_pypowsybl.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ def init(net : pypo.network,
# to handle encoding in 32 bits and overflow when "splitting" the Q values among
min_q = df_gen["min_q"].values.astype(np.float32)
max_q = df_gen["max_q"].values.astype(np.float32)
min_q[~np.isfinite(min_q)] = np.finfo(np.float32).min * 0.5 + 1.
max_q[~np.isfinite(max_q)] = np.finfo(np.float32).max * 0.5 - 1.
min_q[~np.isfinite(min_q)] = np.finfo(np.float32).min * 1e-4 + 1.
max_q[~np.isfinite(max_q)] = np.finfo(np.float32).max * 1e-4 - 1.
gen_bus, gen_disco = _aux_get_bus(bus_df, df_gen)
model.init_generators_full(df_gen["target_p"].values,
df_gen["target_v"].values / voltage_levels.loc[df_gen["voltage_level_id"].values]["nominal_v"].values,
Expand Down
23 changes: 8 additions & 15 deletions lightsim2grid/lightSimBackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,9 +215,9 @@ def __init__(self,
# it takes only into account the time spend in the powerflow algorithm
self.comp_time = 0.

# computation time of the powerflow
# it takes into account everything in the gridmodel, including the mapping
# to the solver, building of Ybus and Sbus AND the time to solve the powerflow
#: computation time of the powerflow
#: it takes into account everything in the gridmodel, including the mapping
#: to the solver, building of Ybus and Sbus AND the time to solve the powerflow
self.timer_gridmodel_xx_pf = 0.

self._timer_postproc = 0.
Expand Down Expand Up @@ -247,6 +247,7 @@ def __init__(self,
self._trafo_hv_res = None
self._trafo_lv_res = None
self._storage_res = None
self._reset_res_pointers()

def _aux_init_super(self,
detailed_infos_for_cascading_failures,
Expand Down Expand Up @@ -615,6 +616,7 @@ def _load_grid_pypowsybl(self, path=None, filename=None):
self.name_line = np.concatenate((lor_sub.index, tor_sub.index))
self.name_storage = np.array(batt_sub.index)
self.name_shunt = np.array(sh_sub.index)
self.name_sub = np.array(buses_sub_id.index)

if "reconnect_disco_gen" in loader_kwargs and loader_kwargs["reconnect_disco_gen"]:
for el in self._grid.get_generators():
Expand Down Expand Up @@ -1346,19 +1348,9 @@ def get_line_status(self):

def get_line_flow(self):
return self.a_or

def _grid2op_bus_from_klu_bus(self, klu_bus):
res = 0
if klu_bus != 0:
# object is connected
res = 1 if klu_bus < self.__nb_bus_before else 2
return res

def _klu_bus_from_grid2op_bus(self, grid2op_bus, grid2op_bus_init):
return grid2op_bus_init[grid2op_bus - 1]


def get_topo_vect(self):
return self.topo_vect
return 1 * self.topo_vect

def generators_info(self):
return self.cst_1 * self.prod_p, self.cst_1 * self.prod_q, self.cst_1 * self.prod_v
Expand Down Expand Up @@ -1430,5 +1422,6 @@ def reset(self, grid_path, grid_filename=None):
self._timer_fetch_data_cpp = 0.
self._timer_apply_act = 0.
self._grid.tell_solver_need_reset()
self._reset_res_pointers()
self.sh_bus[:] = 1 # TODO self._compute_shunt_bus_with_compat(self._grid.get_all_shunt_buses())
self.topo_vect[:] = self.__init_topo_vect # TODO
11 changes: 11 additions & 0 deletions lightsim2grid/rewards/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Copyright (c) 2020-2024, RTE (https://www.rte-france.com)
# See AUTHORS.txt
# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
# you can obtain one at http://mozilla.org/MPL/2.0/.
# SPDX-License-Identifier: MPL-2.0
# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
_
__all__ = ["N1ContingencyReward"]

from lightsim2grid.rewards.n1ContingencyReward import N1ContingencyReward
124 changes: 124 additions & 0 deletions lightsim2grid/rewards/n1ContingencyReward.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Copyright (c) 2020-2024, RTE (https://www.rte-france.com)
# See AUTHORS.txt
# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
# you can obtain one at http://mozilla.org/MPL/2.0/.
# SPDX-License-Identifier: MPL-2.0
# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.

from grid2op.Reward import BaseReward
from grid2op.Environment import Environment
from grid2op.Backend import PandaPowerBackend
from grid2op.Action._backendAction import _BackendAction

from lightsim2grid import LightSimBackend, ContingencyAnalysis
from lightsim2grid.compilation_options import klu_solver_available
from lightsim2grid.solver import SolverType


class N1ContingencyReward(BaseReward):
"""
This class implements a reward that leverage the :class:`lightsim2grid.ContingencyAnalysis`
to compute the number of unsafe contingency at any given time.
Examples
--------
This can be used as:
.. code-block:: python
import grid2op
from lightsim2grid.rewards import N1ContingencyReward
l_ids = [0, 1, 7]
env = grid2op.make("l2rpn_case14_sandbox",
reward_class=N1ContingencyReward(l_ids=l_ids)
)
obs = env.reset()
obs, reward, *_ = env.step(env.action_space())
print(f"reward: {reward:.3f}")
"""

def __init__(self,
l_ids=None,
threshold_margin=1.,
dc=False,
normalize=False,
logger=None,):
BaseReward.__init__(self, logger=logger)
self._backend = None
self._backend_action = None
self._l_ids = None
self._dc = dc
self._normalize = normalize
if l_ids is not None:
self._l_ids = [int(el) for el in l_ids]
self._threshold_margin = float(threshold_margin)
if klu_solver_available:
if self._dc:
self._solver_type = SolverType.KLUDC
else:
self._solver_type = SolverType.KLU
else:
if self._dc:
self._solver_type = SolverType.DC
else:
self._solver_type = SolverType.SparseLU

def initialize(self, env: Environment):
if not isinstance(env.backend, (PandaPowerBackend, LightSimBackend)):
raise RuntimeError("Impossible to use the `N1ContingencyReward` with "
"a environment with a backend that is not "
"``PandaPowerBackend` nor `LightSimBackend`."
)
if isinstance(env.backend, LightSimBackend):
self._backend = env.backend.copy()
elif isinstance(env.backend, PandaPowerBackend):
from lightsim2grid.gridmodel import init
self._backend = init(env.backend)
else:
raise NotImplementedError()

bk_act_cls = _BackendAction.init_grid(env.backend)
self._backend_action = bk_act_cls()
if self._l_ids is None:
self._l_ids = list(range(type(env).n_line))

self.reward_min = 0.
self.reward_max = type(env).n_line if not self._normalize else 1.
self._contingecy_analyzer = ContingencyAnalysis(self._backend)
self._contingecy_analyzer.add_multiple_contingencies(self._l_ids)

def __call__(self, action, env, has_error, is_done, is_illegal, is_ambiguous):
if is_done:
return self.reward_min
self._backend_action.reset()
act = env.backend.get_action_to_set()
th_lim = env.get_thermal_limit()
th_lim[th_lim <= 1] = 1 # assign 1 for the thermal limit
this_n1 = copy.deepcopy(act)
self._backend_action += this_n1

self._backend.apply_action(self._backend_action)
self._backend._disconnect_line(self.l_id)
div_exc_ = None
try:
# TODO there is a bug in lightsimbackend that make it crash instead of diverging
conv, div_exc_ = self._backend.runpf()
except Exception as exc_:
conv = False
div_exc_ = exc_

if conv:
flow = self._backend.get_line_flow()
res = (flow / th_lim).max()
else:
self.logger.info(f"Divergence of the backend at step {env.nb_time_step} for N1Reward with error `{div_exc_}`")
res = self.reward_min
return res

def close(self):
self._backend.close()
del self._backend
self._backend = None
Loading

0 comments on commit 98b748a

Please sign in to comment.