Skip to content

Commit b8e7217

Browse files
authored
Merge pull request #699 from BDonnot/bd_dev
Add redispatching for user shedding
2 parents 4ed5d73 + 4f9c994 commit b8e7217

21 files changed

+898
-124
lines changed

.circleci/config.yml

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,15 @@ jobs:
3939
- run:
4040
command: |
4141
source venv_test/bin/activate
42-
pip install -U pip setuptools wheel
42+
pip install -U pip setuptools wheel coverage
4343
- run:
4444
command: |
4545
source venv_test/bin/activate
4646
pip install -e .[test]
4747
export _GRID2OP_FORCE_TEST=1
4848
cd grid2op/tests/
49-
python3 helper_list_test.py | circleci tests split > /tmp/tests_run
49+
python3 helper_list_test.py > li_test
50+
cat li_test | circleci tests split > /tmp/tests_run
5051
- run:
5152
command: |
5253
source venv_test/bin/activate
@@ -57,8 +58,42 @@ jobs:
5758
source venv_test/bin/activate
5859
cd grid2op/tests/
5960
export _GRID2OP_FORCE_TEST=1
60-
python3 -m unittest -v $(cat /tmp/tests_run)
61-
61+
coverage run -m unittest -v $(cat /tmp/tests_run)
62+
# ls -lah | grep .coverage
63+
# - run:
64+
# command: |
65+
# ls -lah /Grid2Op/grid2op/tests/.coverage.*
66+
67+
# - store_artifacts:
68+
# path: "/Grid2Op/grid2op/tests/test_Action.py"
69+
# destination: coverage_artifacts/
70+
71+
gen_coverage:
72+
executor: grid2op-executor
73+
resource_class: small
74+
steps:
75+
- checkout
76+
- run: python -m pip install virtualenv
77+
- run: python -m virtualenv venv_test
78+
- run:
79+
command: |
80+
source venv_test/bin/activate
81+
pip install -U pip setuptools wheel coverage
82+
- run:
83+
command: |
84+
source venv_test/bin/activate
85+
cd grid2op/tests/
86+
coverage combine ../../coverage_artifacts/ --keep
87+
coverage report -m -i
88+
coverage html -i
89+
coverage xml -i
90+
- store_artifacts:
91+
path: grid2op/tests/htmlcov
92+
destination: htmlcov
93+
- store_artifacts:
94+
path: grid2op/tests/coverage.xml
95+
destination: coverage.xml
96+
6297
install36:
6398
executor: python36
6499
resource_class: small
@@ -403,3 +438,19 @@ workflows:
403438
- install310
404439
- install311
405440
- install312
441+
442+
# gather_test_report:
443+
# requires:
444+
# - test
445+
# jobs:
446+
# - gen_coverage
447+
448+
# send_tests_report:
449+
# requires:
450+
# - gather_test_report
451+
# orbs:
452+
# coverage-reporter: codacy/[email protected]
453+
# jobs:
454+
# coverage-reporter/send_report:
455+
# coverage-reports: coverage.xml
456+
# project-token: $CODACY_PROJECT_TOKEN

CHANGELOG.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ Native multi agents support:
156156
function is called. This is to ensure proper behaviour if env is used without being reset.
157157
- [FIXED] no error was catched if the backend could not properly apply the action sent by the environment.
158158
- [FIXED] an issue in the AAA tests: when backend does not support storages, some tests were skipped not correctly
159+
- [FIXED] an issue when computing the cascading failure routine, in case multiple iterations were performed,
160+
the cooldowns were not updated correctly.
159161
- [ADDED] possibility to set the "thermal limits" when calling `env.reset(..., options={"thermal limit": xxx})`
160162
- [ADDED] possibility to retrieve some structural information about elements with
161163
with `gridobj.get_line_info(...)`, `gridobj.get_load_info(...)`, `gridobj.get_gen_info(...)`

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
[![Downloads](https://pepy.tech/badge/grid2op)](https://pepy.tech/project/grid2op)
44
[![PyPi_Version](https://img.shields.io/pypi/v/grid2op.svg)](https://pypi.org/project/Grid2Op/)
55
[![PyPi_Compat](https://img.shields.io/pypi/pyversions/grid2op.svg)](https://pypi.org/project/Grid2Op/)
6+
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/10367/badge)](https://www.bestpractices.dev/projects/10367)
67
[![LICENSE](https://img.shields.io/pypi/l/grid2op.svg)](https://www.mozilla.org/en-US/MPL/2.0/)
78
[![Documentation Status](https://readthedocs.org/projects/grid2op/badge/?version=latest)](https://grid2op.readthedocs.io/en/latest/?badge=latest)
89
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/3a4e666ba20f4f20b9131e9a6081622c)](https://app.codacy.com/gh/Grid2op/grid2op/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
author = 'Benjamin Donnot'
2323

2424
# The full version, including alpha/beta/rc tags
25-
release = '1.11.0.dev5'
25+
release = '1.11.0.dev6'
2626
version = '1.11'
2727

2828

grid2op/Action/_backendAction.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,14 @@ def set_redispatch(self, new_redispatching):
669669
This is called by the environment, do not alter.
670670
"""
671671
self.prod_p.change_val(new_redispatching)
672+
673+
def set_storage(self, new_storage):
674+
"""
675+
.. warning:: /!\\\\ Internal, do not use unless you know what you are doing /!\\\\
676+
677+
This is called by the environment, do not alter.
678+
"""
679+
self.storage_power.set_val(new_storage)
672680

673681
def _aux_iadd_inj(self, dict_injection):
674682
"""

grid2op/Backend/backend.py

Lines changed: 93 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
DivergingPowerflow,
3636
Grid2OpException,
3737
)
38+
import grid2op.Environment # for type hints
3839
from grid2op.Space import GridObjects, ElTypeInfo, DEFAULT_N_BUSBAR_PER_SUB, DEFAULT_ALLOW_DETACHMENT
3940
import grid2op.Observation # for type hints
4041
import grid2op.Action # for type hints
@@ -193,6 +194,7 @@ def __init__(self,
193194
self._load_bus_target = None
194195
self._gen_bus_target = None
195196
self._storage_bus_target = None
197+
self._shunt_bus_target = None
196198

197199
#: .. versionadded: 1.11.0
198200
# will be used later on in future grid2op version
@@ -259,7 +261,6 @@ def cannot_handle_more_than_2_busbar(self):
259261
"upgrade it to a newer version.")
260262
self.n_busbar_per_sub = DEFAULT_N_BUSBAR_PER_SUB
261263

262-
263264
def can_handle_detachment(self):
264265
"""
265266
.. versionadded:: 1.11.0
@@ -278,7 +279,8 @@ def can_handle_detachment(self):
278279
:func:`Backend.cannot_handle_detachment`.
279280
280281
If not, then the environments created with your backend will not be able to
281-
"operate" grid with load and generator detachment.
282+
"operate" the grid with load and generator detached (episode will be terminated
283+
if this happens).
282284
283285
.. danger::
284286
We highly recommend you do not try to override this function.
@@ -390,6 +392,8 @@ def load_grid_public(self,
390392
self._load_bus_target = np.empty(self.n_load, dtype=dt_int)
391393
self._gen_bus_target = np.empty(self.n_gen, dtype=dt_int)
392394
self._storage_bus_target = np.empty(self.n_storage, dtype=dt_int)
395+
if self.shunts_data_available:
396+
self._shunt_bus_target = np.empty(self.n_shunt, dtype=dt_int)
393397

394398
if self._missing_detachment_support_info:
395399
self.detachment_is_allowed = DEFAULT_ALLOW_DETACHMENT
@@ -474,10 +478,16 @@ def apply_action_public(self, backend_action: Union["grid2op.Action._backendActi
474478
self._storage_bus_target.flags.writeable = True
475479
self._storage_bus_target[stos_bus.changed] = stos_bus.values[stos_bus.changed]
476480
self._storage_bus_target.flags.writeable = False
477-
# TODO shunts
481+
482+
if type(self).shunts_data_available:
483+
shunts_bus = backend_action.shunt_bus
484+
self._shunt_bus_target.flags.writeable = True
485+
self._shunt_bus_target[shunts_bus.changed] = shunts_bus.values[shunts_bus.changed]
486+
self._shunt_bus_target.flags.writeable = False
487+
478488
return self.apply_action(backend_action)
479489

480-
def update_bus_target_after_pf(self, loads_bus, gens_bus, stos_bus):
490+
def update_bus_target_after_pf(self, loads_bus, gens_bus, stos_bus, shunt_bus=None):
481491
self._load_bus_target.flags.writeable = True
482492
self._load_bus_target[:] = loads_bus
483493
self._load_bus_target.flags.writeable = False
@@ -487,6 +497,10 @@ def update_bus_target_after_pf(self, loads_bus, gens_bus, stos_bus):
487497
self._storage_bus_target.flags.writeable = True
488498
self._storage_bus_target[:] = stos_bus
489499
self._storage_bus_target.flags.writeable = False
500+
if type(self).shunts_data_available and shunt_bus is not None:
501+
self._shunt_bus_target.flags.writeable = True
502+
self._shunt_bus_target[:] = shunt_bus
503+
self._shunt_bus_target.flags.writeable = False
490504

491505
def handle_grid2op_compat(self):
492506
"""This function will resize the _load_bus_target, _gen_bus_target and _storage_bus_target
@@ -500,8 +514,13 @@ def handle_grid2op_compat(self):
500514
self._gen_bus_target.resize(cls.n_gen)
501515
self._gen_bus_target.flags.writeable = False
502516
self._storage_bus_target.flags.writeable = True
503-
self._storage_bus_target.resize(cls.n_storage)
517+
self._storage_bus_target.resize(cls.n_storage, refcheck=False)
504518
self._storage_bus_target.flags.writeable = False
519+
if cls.shunts_data_available:
520+
self._shunt_bus_target.flags.writeable = True
521+
self._shunt_bus_target.resize(cls.n_shunt)
522+
self._shunt_bus_target.flags.writeable = False
523+
505524

506525
@abstractmethod
507526
def apply_action(self, backend_action: "grid2op.Action._backendAction._BackendAction") -> None:
@@ -847,6 +866,7 @@ def copy_public(self) -> Self:
847866
res._load_bus_target = copy.deepcopy(self._load_bus_target)
848867
res._gen_bus_target = copy.deepcopy(self._gen_bus_target)
849868
res._storage_bus_target = copy.deepcopy(self._storage_bus_target)
869+
res._shunt_bus_target = copy.deepcopy(self._shunt_bus_target)
850870
res._prevent_automatic_disconnection = copy.deepcopy(self._prevent_automatic_disconnection)
851871
return res
852872

@@ -1318,46 +1338,87 @@ def _runpf_with_diverging_exception(self, is_dc : bool) -> Optional[Exception]:
13181338
# .. versionadded:: 1.11.0
13191339
topo_vect = self.get_topo_vect()
13201340
load_buses = topo_vect[cls.load_pos_topo_vect]
1321-
if not cls.detachment_is_allowed and (load_buses == -1).any():
1322-
raise BackendError(cls.ERR_DETACHMENT.format("loads", "loads", (load_buses == -1).nonzero()[0]))
1341+
load_disco = (load_buses == -1)
1342+
if not cls.detachment_is_allowed and load_disco.any():
1343+
raise BackendError(cls.ERR_DETACHMENT.format("loads", "loads", load_disco.nonzero()[0]))
13231344

13241345
gen_buses = topo_vect[cls.gen_pos_topo_vect]
1325-
if not cls.detachment_is_allowed and (gen_buses == -1).any():
1326-
raise BackendError(cls.ERR_DETACHMENT.format("gens", "gens", (gen_buses == -1).nonzero()[0]))
1346+
gen_disco = (gen_buses == -1)
1347+
if not cls.detachment_is_allowed and gen_disco.any():
1348+
raise BackendError(cls.ERR_DETACHMENT.format("gens", "gens", gen_disco.nonzero()[0]))
13271349

13281350
if cls.n_storage > 0:
13291351
storage_buses = topo_vect[cls.storage_pos_topo_vect]
13301352
storage_p, *_ = self.storages_info()
1331-
sto_maybe_error = (storage_buses == -1) & (np.abs(storage_p) >= 1e-6)
1353+
storage_p_withpower = np.abs(storage_p) >= 1e-6
1354+
sto_maybe_error = (storage_buses == -1) & storage_p_withpower
13321355
if not cls.detachment_is_allowed and sto_maybe_error.any():
13331356
raise BackendError((cls.ERR_DETACHMENT.format("storages", "storages", sto_maybe_error.nonzero()[0]) +
13341357
" NB storage units are allowed to be disconnected even if "
1335-
"`detachment_is_allowed` is False but only if the don't produce active power."))
1336-
1358+
"`detachment_is_allowed` is False but only if the don't produce / absorb active power."))
1359+
else:
1360+
sto_maybe_error = None
13371361
# additional check: if the backend detach some things incorrectly
13381362
if cls.detachment_is_allowed:
13391363
# if the backend automatically disconnect things, I need to catch them
13401364
# with grid2op 1.11.0 it is not feasible
1341-
if self._prevent_automatic_disconnection and ((self._load_bus_target != -1) & (load_buses == -1)).any():
1342-
issue = (self._load_bus_target != -1) & (load_buses == -1)
1343-
raise BackendError(f"Your backend apparently disconnected load(s) id {issue.nonzero()[0]}, "
1344-
f"named {type(self).name_load[issue.nonzero()[0]]}")
1345-
if self._prevent_automatic_disconnection and ((self._gen_bus_target != -1) & (gen_buses == -1)).any():
1346-
issue = (self._gen_bus_target != -1) & (gen_buses == -1)
1347-
raise BackendError(f"Your backend apparently disconnected gens(s) id {issue.nonzero()[0]}, "
1348-
f"named {type(self).name_gen[issue.nonzero()[0]]}")
1349-
# TODO storage units
1365+
self._catch_automatic_disconnection(load_disco, gen_disco, sto_maybe_error)
13501366

13511367
except Grid2OpException as exc_:
13521368
exc_me = exc_
13531369

13541370
if not conv and exc_me is None:
13551371
exc_me = BackendError(
1356-
"GAME OVER: Powerflow has diverged during computation "
1357-
"or a load has been disconnected or a generator has been disconnected."
1372+
f"GAME OVER: {exc_me}"
13581373
)
13591374
return exc_me
13601375

1376+
def _catch_automatic_disconnection(self,
1377+
load_disco: np.ndarray,
1378+
gen_disco: np.ndarray,
1379+
sto_maybe_error: Optional[np.ndarray]):
1380+
"""
1381+
INTERNAL
1382+
1383+
.. warning:: /!\\\\ Internal, do not use unless you know what you are doing /!\\\\
1384+
1385+
This function "automatically" detects if elements have been disconnected (bus -1 in the results table but bus > 0 in
1386+
the target table). If that is the case, it is expected that (provided that
1387+
:attr:`Backend._prevent_automatic_disconnection` is ``True`` - default) the backend raises a BackendError exception.
1388+
1389+
Args:
1390+
load_disco (np.ndarray): _description_
1391+
gen_disco (np.ndarray): _description_
1392+
sto_maybe_error (Optional[np.ndarray]): _description_
1393+
1394+
"""
1395+
if not self._prevent_automatic_disconnection:
1396+
# in this case, the backend is allowed to disconnect some things
1397+
return
1398+
1399+
cls = type(self)
1400+
if ((self._load_bus_target != -1) & load_disco).any():
1401+
issue = (self._load_bus_target != -1) & load_disco
1402+
raise BackendError(f"Your backend apparently disconnected load(s) id {issue.nonzero()[0]}, "
1403+
f"named {cls.name_load[issue.nonzero()[0]]}")
1404+
if ((self._gen_bus_target != -1) & gen_disco).any():
1405+
issue = (self._gen_bus_target != -1) & gen_disco
1406+
raise BackendError(f"Your backend apparently disconnected gens(s) id {issue.nonzero()[0]}, "
1407+
f"named {cls.name_gen[issue.nonzero()[0]]}")
1408+
1409+
if cls.shunts_data_available:
1410+
*_, shunt_buses = self.shunt_info()
1411+
if ((self._shunt_bus_target != -1) & (shunt_buses == -1)).any():
1412+
issue = (self._shunt_bus_target != -1) & (shunt_buses == -1)
1413+
raise BackendError(f"Your backend apparently disconnected shunt(s) id {issue.nonzero()[0]}, "
1414+
f"named {cls.name_shunt[issue.nonzero()[0]]}")
1415+
1416+
if cls.n_storage > 0:
1417+
if ((self._storage_bus_target != -1) & sto_maybe_error).any():
1418+
issue = (self._storage_bus_target != -1) & (sto_maybe_error)
1419+
raise BackendError(f"Your backend apparently disconnected stprage unit(s) id {issue.nonzero()[0]}, "
1420+
f"named {cls.name_storage[issue.nonzero()[0]]}")
1421+
13611422
def next_grid_state(self,
13621423
env: "grid2op.Environment.BaseEnv",
13631424
is_dc: Optional[bool]=False):
@@ -1392,14 +1453,15 @@ def next_grid_state(self,
13921453
13931454
"""
13941455
infos = []
1395-
disconnected_during_cf = np.full(self.n_line, fill_value=-1, dtype=dt_int)
1456+
disconnected_during_cf = np.full(type(self).n_line, fill_value=-1, dtype=dt_int)
13961457
conv_ = self._runpf_with_diverging_exception(is_dc)
13971458
if env._no_overflow_disconnection or conv_ is not None:
13981459
return disconnected_during_cf, infos, conv_
13991460

14001461
# the environment disconnect some powerlines
14011462
init_time_step_overflow = copy.deepcopy(env._timestep_overflow)
1402-
ts = 0
1463+
counter_increased = np.zeros_like(init_time_step_overflow, dtype=dt_bool)
1464+
iter_num = 0
14031465
while True:
14041466
# simulate the cascading failure
14051467
lines_flows = 1.0 * self.get_line_flow()
@@ -1412,7 +1474,10 @@ def next_grid_state(self,
14121474
) & lines_status
14131475

14141476
# b) deals with soft overflow (disconnect them if lines still connected)
1415-
init_time_step_overflow[(lines_flows >= thermal_limits) & lines_status] += 1
1477+
mask_inc = (lines_flows >= thermal_limits) & lines_status
1478+
mask_inc[counter_increased] = False
1479+
init_time_step_overflow[mask_inc] += 1
1480+
counter_increased[mask_inc] = True
14161481
to_disc[
14171482
(init_time_step_overflow > env._nb_timestep_overflow_allowed)
14181483
& lines_status
@@ -1423,7 +1488,7 @@ def next_grid_state(self,
14231488
# no powerlines have been disconnected at this time step,
14241489
# i stop the computation there
14251490
break
1426-
disconnected_during_cf[to_disc] = ts
1491+
disconnected_during_cf[to_disc] = iter_num
14271492

14281493
# perform the disconnection action
14291494
for i, el in enumerate(to_disc):
@@ -1437,7 +1502,7 @@ def next_grid_state(self,
14371502

14381503
if conv_ is not None:
14391504
break
1440-
ts += 1
1505+
iter_num += 1
14411506
return disconnected_during_cf, infos, conv_
14421507

14431508
def storages_info(self) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:

grid2op/Backend/pandaPowerBackend.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -730,7 +730,7 @@ def _init_private_attrs(self) -> None:
730730

731731
self._compute_pos_big_topo()
732732

733-
self._topo_vect = np.full(self.dim_topo, fill_value=-1, dtype=dt_int)
733+
self._topo_vect : np.ndarray = np.full(self.dim_topo, fill_value=-1, dtype=dt_int)
734734

735735
# utilities for imeplementing apply_action
736736
self._corresp_name_fun = {}
@@ -841,7 +841,7 @@ def storage_deact_for_backward_comaptibility(self) -> None:
841841
self.storage_q = np.full(cls.n_storage, dtype=dt_float, fill_value=np.nan)
842842
self.storage_v = np.full(cls.n_storage, dtype=dt_float, fill_value=np.nan)
843843
self._topo_vect.flags.writeable = True
844-
self._topo_vect.resize(cls.dim_topo)
844+
self._topo_vect.resize(cls.dim_topo, refcheck=False)
845845
self._topo_vect.flags.writeable = False
846846
self._get_topo_vect()
847847

0 commit comments

Comments
 (0)