Skip to content

Commit b0d9b56

Browse files
committed
adding tests for earliest lightsim2grid versions
1 parent b539ae9 commit b0d9b56

File tree

6 files changed

+259
-15
lines changed

6 files changed

+259
-15
lines changed

.circleci/config.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,33 @@ jobs:
163163
cd /tmp
164164
grid2op.testinstall
165165
166+
legacy_lightsim:
167+
executor: python38
168+
resource_class: small
169+
steps:
170+
- checkout
171+
- run:
172+
command: |
173+
apt-get update
174+
apt-get install -y coinor-cbc
175+
- run: python -m pip install virtualenv
176+
- run: python -m virtualenv venv_test
177+
- run:
178+
command: |
179+
source venv_test/bin/activate
180+
python -m pip install -U pip setuptools wheel
181+
python -m pip install -U lightsim2grid==0.5.3 gymnasium "numpy<1.22"
182+
- run:
183+
command: |
184+
source venv_test/bin/activate
185+
python -m pip install -e .
186+
pip freeze
187+
- run:
188+
command: |
189+
source venv_test/bin/activate
190+
export _GRID2OP_FORCE_TEST=1
191+
python -m unittest grid2op/tests/test_basic_env_ls.py
192+
166193
install39:
167194
executor: python39
168195
resource_class: small
@@ -313,6 +340,7 @@ workflows:
313340
test:
314341
jobs:
315342
- test
343+
- legacy_lightsim
316344
install:
317345
jobs:
318346
- install38

CHANGELOG.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ Change Log
3434
[1.10.1] - 2024-03-xx
3535
----------------------
3636
- [FIXED] issue https://github.com/rte-france/Grid2Op/issues/593
37+
- [FIXED] backward compatibility issues with "oldest" lightsim2grid versions
38+
(now tested in basic settings)
3739
- [ADDED] a "compact" way to store the data in the Runner
3840
- [IMPROVED] the "`train_val_split`" functions, now more names (for the folders)
3941
can be used

grid2op/Backend/backend.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1945,21 +1945,28 @@ def assert_grid_correct(self) -> None:
19451945
from grid2op.Action import CompleteAction
19461946
from grid2op.Action._backendAction import _BackendAction
19471947

1948-
if self._missing_two_busbars_support_info:
1949-
warnings.warn("The backend implementation you are using is probably too old to take advantage of the "
1950-
"new feature added in grid2op 1.10.0: the possibility "
1951-
"to have more than 2 busbars per substations (or not). "
1952-
"To silence this warning, you can modify the `load_grid` implementation "
1953-
"of your backend and either call:\n"
1954-
"- self.can_handle_more_than_2_busbar if the current implementation "
1955-
" can handle more than 2 busbsars OR\n"
1956-
"- self.cannot_handle_more_than_2_busbar if not."
1957-
"\nAnd of course, ideally, if the current implementation "
1958-
"of your backend cannot "
1959-
"handle more than 2 busbars per substation, then change it :-)\n"
1960-
"Your backend will behave as if it did not support it.")
1948+
if hasattr(self, "_missing_two_busbars_support_info"):
1949+
if self._missing_two_busbars_support_info:
1950+
warnings.warn("The backend implementation you are using is probably too old to take advantage of the "
1951+
"new feature added in grid2op 1.10.0: the possibility "
1952+
"to have more than 2 busbars per substations (or not). "
1953+
"To silence this warning, you can modify the `load_grid` implementation "
1954+
"of your backend and either call:\n"
1955+
"- self.can_handle_more_than_2_busbar if the current implementation "
1956+
" can handle more than 2 busbsars OR\n"
1957+
"- self.cannot_handle_more_than_2_busbar if not."
1958+
"\nAnd of course, ideally, if the current implementation "
1959+
"of your backend cannot "
1960+
"handle more than 2 busbars per substation, then change it :-)\n"
1961+
"Your backend will behave as if it did not support it.")
1962+
self._missing_two_busbars_support_info = False
1963+
self.n_busbar_per_sub = DEFAULT_N_BUSBAR_PER_SUB
1964+
else:
19611965
self._missing_two_busbars_support_info = False
19621966
self.n_busbar_per_sub = DEFAULT_N_BUSBAR_PER_SUB
1967+
warnings.warn("Your backend is missing the `_missing_two_busbars_support_info` "
1968+
"attribute. This is known issue in lightims2grid <= 0.7.5. Please "
1969+
"upgrade your backend. This will raise an error in the future.")
19631970

19641971
orig_type = type(self)
19651972
if orig_type.my_bk_act_class is None:

grid2op/Runner/runner.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -615,12 +615,32 @@ def __init__(
615615

616616
self.__used = False
617617

618+
def _make_new_backend(self):
619+
try:
620+
res = self.backendClass(**self._backend_kwargs)
621+
except TypeError:
622+
# for backward compatibility, some backend might not
623+
# handle full kwargs (that might be added later)
624+
import inspect
625+
possible_params = inspect.signature(self.backendClass.__init__).parameters
626+
this_kwargs = {}
627+
for el in self._backend_kwargs:
628+
if el in possible_params:
629+
this_kwargs[el] = self._backend_kwargs[el]
630+
else:
631+
warnings.warn("Runner: your backend does not support the kwargs "
632+
f"`{el}={self._backend_kwargs[el]}`. This usually "
633+
"means it is outdated. Please upgrade it.")
634+
res = self.backendClass(**this_kwargs)
635+
return res
636+
618637
def _new_env(self, chronics_handler, parameters) -> Tuple[BaseEnv, BaseAgent]:
619638
# the same chronics_handler is used for all the environments.
620639
# make sure to "reset" it properly
621640
# (this is handled elsewhere in case of "multi chronics")
622641
if not self.chronics_handler.chronicsClass.MULTI_CHRONICS:
623642
self.chronics_handler.next_chronics()
643+
backend = self._make_new_backend()
624644
with warnings.catch_warnings():
625645
warnings.filterwarnings("ignore")
626646
res = self.envClass.init_obj_from_kwargs(
@@ -629,7 +649,7 @@ def _new_env(self, chronics_handler, parameters) -> Tuple[BaseEnv, BaseAgent]:
629649
init_env_path=self.init_env_path,
630650
init_grid_path=self.init_grid_path,
631651
chronics_handler=chronics_handler,
632-
backend=self.backendClass(**self._backend_kwargs),
652+
backend=backend,
633653
parameters=parameters,
634654
name=self.name_env,
635655
names_chronics_to_backend=self.names_chronics_to_backend,

grid2op/tests/test_MaskedEnvironment.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,6 @@ def test_gym_discrete(self):
210210
act = 0
211211
self._aux_run_envs(act, env_gym_in, env_gym_out)
212212

213-
214213
def test_gym_multidiscrete(self):
215214
"""test I can create the gym env with multi discrete act space"""
216215
env_gym_in = GymEnv(self.env_in)

grid2op/tests/test_basic_env_ls.py

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# Copyright (c) 2019-2023, RTE (https://www.rte-france.com)
2+
# See AUTHORS.txt
3+
# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
4+
# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
5+
# you can obtain one at http://mozilla.org/MPL/2.0/.
6+
# SPDX-License-Identifier: MPL-2.0
7+
# This file is part of Grid2Op, Grid2Op a testbed platform to model sequential decision making in power systems.
8+
9+
import warnings
10+
import unittest
11+
import numpy as np
12+
13+
import grid2op
14+
try:
15+
from lightsim2grid import LightSimBackend
16+
LS_AVAIL = True
17+
except ImportError:
18+
LS_AVAIL = False
19+
pass
20+
from grid2op.Environment import Environment
21+
from grid2op.Runner import Runner
22+
from grid2op.gym_compat import (GymEnv,
23+
BoxGymActSpace,
24+
BoxGymObsSpace,
25+
DiscreteActSpace,
26+
MultiDiscreteActSpace)
27+
28+
29+
class TestEnvironmentBasic(unittest.TestCase):
30+
def setUp(self) -> None:
31+
if not LS_AVAIL:
32+
self.skipTest("lightsim not installed")
33+
with warnings.catch_warnings():
34+
warnings.filterwarnings("ignore")
35+
self.env = grid2op.make("l2rpn_case14_sandbox",
36+
test=True,
37+
_add_to_name=type(self).__name__,
38+
backend=LightSimBackend())
39+
self.line_id = 3
40+
th_lim = self.env.get_thermal_limit() * 2. # avoid all problem in general
41+
th_lim[self.line_id] /= 10. # make sure to get trouble in line 3
42+
self.env.set_thermal_limit(th_lim)
43+
44+
TestEnvironmentBasic._init_env(self.env)
45+
46+
@staticmethod
47+
def _init_env(env):
48+
env.set_id(0)
49+
env.seed(0)
50+
env.reset()
51+
52+
def tearDown(self) -> None:
53+
self.env.close()
54+
return super().tearDown()
55+
56+
def test_right_type(self):
57+
assert isinstance(self.env, Environment)
58+
59+
def test_ok(self):
60+
act = self.env.action_space()
61+
for i in range(10):
62+
obs_in, reward, done, info = self.env.step(act)
63+
if i < 2: # 2 : 2 full steps already
64+
assert obs_in.timestep_overflow[self.line_id] == i + 1, f"error for step {i}: {obs_in.timestep_overflow[self.line_id]}"
65+
else:
66+
# cooldown applied for line 3:
67+
# - it disconnect stuff in `self.env_in`
68+
# - it does not affect anything in `self.env_out`
69+
assert not obs_in.line_status[self.line_id]
70+
71+
def test_reset(self):
72+
# timestep_overflow should be 0 initially even if the flow is too high
73+
obs = self.env.reset()
74+
assert obs.timestep_overflow[self.line_id] == 0
75+
assert obs.rho[self.line_id] > 1.
76+
77+
78+
class TestEnvironmentBasicCpy(TestEnvironmentBasic):
79+
def setUp(self) -> None:
80+
super().setUp()
81+
init_int = self.env
82+
self.env = self.env.copy()
83+
init_int.close()
84+
85+
86+
class TestBasicEnvironmentRunner(unittest.TestCase):
87+
def setUp(self) -> None:
88+
TestEnvironmentBasic.setUp(self)
89+
self.max_iter = 10
90+
91+
def tearDown(self) -> None:
92+
self.env.close()
93+
return super().tearDown()
94+
95+
def test_runner_can_make(self):
96+
runner = Runner(**self.env.get_params_for_runner())
97+
env2 = runner.init_env()
98+
assert isinstance(env2, Environment)
99+
100+
def test_runner(self):
101+
# create the runner
102+
runner_in = Runner(**self.env.get_params_for_runner())
103+
res_in, *_ = runner_in.run(nb_episode=1, max_iter=self.max_iter, env_seeds=[0], episode_id=[0], add_detailed_output=True)
104+
res_in2, *_ = runner_in.run(nb_episode=1, max_iter=self.max_iter, env_seeds=[0], episode_id=[0])
105+
# check correct results are obtained when agregated
106+
assert res_in[3] == 10
107+
assert res_in2[3] == 10
108+
assert np.allclose(res_in[2], 645.4992065)
109+
assert np.allclose(res_in2[2], 645.4992065)
110+
111+
# check detailed results
112+
ep_data_in = res_in[-1]
113+
for i in range(self.max_iter + 1):
114+
obs_in = ep_data_in.observations[i]
115+
if i < 3:
116+
assert obs_in.timestep_overflow[self.line_id] == i, f"error for step {i}: {obs_in.timestep_overflow[self.line_id]}"
117+
else:
118+
# cooldown applied for line 3:
119+
# - it disconnect stuff in `self.env_in`
120+
# - it does not affect anything in `self.env_out`
121+
assert not obs_in.line_status[self.line_id], f"error for step {i}: line is not disconnected"
122+
123+
124+
class TestBasicEnvironmentGym(unittest.TestCase):
125+
def setUp(self) -> None:
126+
TestEnvironmentBasic.setUp(self)
127+
128+
def tearDown(self) -> None:
129+
self.env.close()
130+
return super().tearDown()
131+
132+
def _aux_run_envs(self, act, env_gym):
133+
for i in range(10):
134+
obs_in, reward, done, truncated, info = env_gym.step(act)
135+
if i < 2: # 2 : 2 full steps already
136+
assert obs_in["timestep_overflow"][self.line_id] == i + 1, f"error for step {i}: {obs_in['timestep_overflow'][self.line_id]}"
137+
else:
138+
# cooldown applied for line 3:
139+
# - it disconnect stuff in `self.env_in`
140+
# - it does not affect anything in `self.env_out`
141+
assert not obs_in["line_status"][self.line_id]
142+
143+
def test_gym_with_step(self):
144+
"""test the step function also disconnects (or not) the lines"""
145+
env_gym = GymEnv(self.env)
146+
act = {}
147+
self._aux_run_envs(act, env_gym)
148+
env_gym.reset()
149+
self._aux_run_envs(act, env_gym)
150+
151+
def test_gym_normal(self):
152+
"""test I can create the gym env"""
153+
env_gym = GymEnv(self.env)
154+
env_gym.reset()
155+
156+
def test_gym_box(self):
157+
"""test I can create the gym env with box ob space and act space"""
158+
env_gym = GymEnv(self.env)
159+
with warnings.catch_warnings():
160+
warnings.filterwarnings("ignore")
161+
env_gym.action_space = BoxGymActSpace(self.env.action_space)
162+
env_gym.observation_space = BoxGymObsSpace(self.env.observation_space)
163+
env_gym.reset()
164+
165+
def test_gym_discrete(self):
166+
"""test I can create the gym env with discrete act space"""
167+
env_gym = GymEnv(self.env)
168+
with warnings.catch_warnings():
169+
warnings.filterwarnings("ignore")
170+
env_gym.action_space = DiscreteActSpace(self.env.action_space)
171+
env_gym.reset()
172+
act = 0
173+
self._aux_run_envs(act, env_gym)
174+
175+
def test_gym_multidiscrete(self):
176+
"""test I can create the gym env with multi discrete act space"""
177+
env_gym = GymEnv(self.env)
178+
with warnings.catch_warnings():
179+
warnings.filterwarnings("ignore")
180+
env_gym.action_space = MultiDiscreteActSpace(self.env.action_space)
181+
env_gym.reset()
182+
act = env_gym.action_space.sample()
183+
act[:] = 0
184+
self._aux_run_envs(act, env_gym)
185+
186+
187+
if __name__ == "__main__":
188+
unittest.main()

0 commit comments

Comments
 (0)