Skip to content
Open
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 CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ The "simulate" function :

Better handling of the voltages:

- add voltage angle attributes to the complete observation vector (issue #711)
- model better the voltage, include voltage constraints
- shunts in observation too, for real (but what to do when backend is not shunt compliant to prevent the
stuff to break)
Expand Down
27 changes: 26 additions & 1 deletion grid2op/Observation/baseObservation.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
NoForecastAvailable,
BaseObservationError,
)
from grid2op.Space import GridObjects, ElTypeInfo
from grid2op.Space import GridObjects, ElTypeInfo, GRID2OP_CURRENT_VERSION_STR

# TODO have a method that could do "forecast" by giving the _injection by the agent,
# TODO if he wants to make custom forecasts
Expand Down Expand Up @@ -1375,6 +1375,24 @@ def _aux_process_grid2op_compat_1_11_0(cls):
except ValueError as exc_: # noqa: F841
# this attribute was not there in the first place
pass

@classmethod
def _aux_process_grid2op_compat_1_12_6(cls):
"""Keep the observation vector compatible with versions before 1.12.6."""
theta_attrs = [
"theta_or",
"theta_ex",
"load_theta",
"gen_theta",
"storage_theta",
]
cls.attr_list_vect = copy.deepcopy(cls.attr_list_vect)
cls.attr_list_json = copy.deepcopy(cls.attr_list_json)
for el in theta_attrs:
if el in cls.attr_list_vect:
cls.attr_list_vect.remove(el)
if el not in cls.attr_list_json:
cls.attr_list_json.append(el)

@classmethod
def process_grid2op_compat(cls) -> None:
Expand Down Expand Up @@ -1408,6 +1426,13 @@ def process_grid2op_compat(cls) -> None:
if glop_ver < cls.MIN_VERSION_DETACH:
# detachment has been added in grid2op 1.11
cls._aux_process_grid2op_compat_1_11_0()

if (
glop_ver < version.parse("1.12.6")
and cls.glop_version != GRID2OP_CURRENT_VERSION_STR
):
# voltage angles have been added to the observation vector in 1.12.6
cls._aux_process_grid2op_compat_1_12_6()

cls.attr_list_set = copy.deepcopy(cls.attr_list_set)
cls.attr_list_set = set(cls.attr_list_vect)
Expand Down
30 changes: 24 additions & 6 deletions grid2op/Observation/completeObservation.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,24 @@ class CompleteObservation(BaseObservation):
and 0 means "not used"
#. :attr:`BaseObservation.attack_under_alert` For each attackable line `i` it says if an alert has been sent (+1) or not (-1)
for each attackable line currently under attack.

#. :attr:`BaseObservation.theta_or` voltage angle at the origin of each powerline
[:attr:`grid2op.Space.GridObjects.n_line` elements].
#. :attr:`BaseObservation.theta_ex` voltage angle at the extremity of each powerline
[:attr:`grid2op.Space.GridObjects.n_line` elements].
#. :attr:`BaseObservation.load_theta` voltage angle at each load
[:attr:`grid2op.Space.GridObjects.n_load` elements].
#. :attr:`BaseObservation.gen_theta` voltage angle at each generator
[:attr:`grid2op.Space.GridObjects.n_gen` elements].
#. :attr:`BaseObservation.storage_theta` voltage angle at each storage unit
[:attr:`grid2op.Space.GridObjects.n_storage` elements].

These attributes are set to zero when the backend does not support voltage
angles; see :attr:`BaseObservation.support_theta`.

Voltage angles use the reference selected by the backend. A uniform shift
of every angle represents the same grid state, so users can subtract a
common reference or statistic when reference-invariant inputs are needed.

"""

attr_list_vect = [
Expand Down Expand Up @@ -208,17 +225,18 @@ class CompleteObservation(BaseObservation):
"gen_p_detached",
"storage_p_detached",
# protections (>= 1.11.0)
"timestep_protection_engaged"
]
attr_list_json = [
"_thermal_limit",
"support_theta",
"timestep_protection_engaged",
# voltage angles (>= 1.12.6)
"theta_or",
"theta_ex",
"load_theta",
"gen_theta",
"storage_theta",
]
attr_list_json = [
"_thermal_limit",
"support_theta",
]
attr_list_set = set(attr_list_vect)

def __init__(self,
Expand Down
2 changes: 0 additions & 2 deletions grid2op/tests/test_GymConverter.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@ def _aux_test_json(self, space, obj=None):
tmp = obj[k]
if isinstance(tmp, (int, float, dt_float, dt_int, dt_bool, np.int64, np.int32)):
assert np.all(np.abs(float(obj[k]) - float(obj2[k])) <= self.tol)
elif len(tmp) == 1:
assert np.all(np.abs(float(obj[k]) - float(obj2[k])) <= self.tol)
else:
assert np.all(
np.abs(obj[k].astype(dt_float) - obj2[k].astype(dt_float))
Expand Down
61 changes: 58 additions & 3 deletions grid2op/tests/test_Observation.py
Original file line number Diff line number Diff line change
Expand Up @@ -959,7 +959,13 @@ def setUp(self):
# dt_float,
# dt_float,
# timestep_protection_engaged
dt_int
dt_int,
# voltage angles (>= 1.12.6)
dt_float,
dt_float,
dt_float,
dt_float,
dt_float,
],
dtype=object,
)
Expand Down Expand Up @@ -1039,10 +1045,16 @@ def setUp(self):
# 5,
# 0,
# timestep_protection_engaged
20
20,
# voltage angles (>= 1.12.6)
20,
20,
11,
5,
0,
]
)
self.size_obs = 429 + 4 + 4 + 2 + 1 + 10 + 5 + 0 + 5 + 20
self.size_obs = 429 + 4 + 4 + 2 + 1 + 10 + 5 + 0 + 5 + 20 + 56

def tearDown(self):
self.env.close()
Expand Down Expand Up @@ -2133,6 +2145,49 @@ def test_4_to_from_vect(self):
vect2 = obs2.to_vect()
assert np.all(vect == vect2)

def test_theta_attributes_in_vector(self):
with warnings.catch_warnings():
warnings.filterwarnings("ignore")
env = grid2op.make(
"educ_case14_storage",
test=True,
_add_to_name=type(self).__name__,
)

with env:
obs = env.reset()
theta_attrs = (
"theta_or",
"theta_ex",
"load_theta",
"gen_theta",
"storage_theta",
)
expected_by_attr = {}

for attr_id, attr_nm in enumerate(theta_attrs):
assert attr_nm in type(obs).attr_list_vect
assert attr_nm not in type(obs).attr_list_json
values = (
np.arange(getattr(obs, attr_nm).size, dtype=dt_float)
+ 100.0 * (attr_id + 1)
)
getattr(obs, attr_nm)[:] = values
expected_by_attr[attr_nm] = values

obs._vectorized = None
obs_vect = obs.to_vect()
obs_from_vect = env.observation_space.from_vect(obs_vect)

for attr_nm, expected in expected_by_attr.items():
np.testing.assert_array_equal(
env.observation_space.extract_from_vect(obs_vect, attr_nm),
expected,
)
np.testing.assert_array_equal(
getattr(obs_from_vect, attr_nm), expected
)

def test_5_simulate_proper_timestep(self):
self.skipTest(
"This is extensively tested elswhere, and the chronics have been changed."
Expand Down
12 changes: 8 additions & 4 deletions grid2op/tests/test_attached_envs.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ def test_observation_space(self):
assert issubclass(self.env.observation_space.subtype, CompleteObservation)
# size_th = 467
# size_th = 473 # gen_delta
size_th = 473 + 20 # n_line added for timestep_protection_engaged
# timestep_protection_engaged + theta_or/theta_ex/load/gen/storage
size_th = 473 + 20 + 2 * 20 + 11 + 6
assert self.env.observation_space.n == size_th, (
f"obs space size is {self.env.observation_space.n}," f"should be {size_th}"
)
Expand Down Expand Up @@ -229,7 +230,8 @@ def test_action_space(self):

def test_observation_space(self):
assert issubclass(self.env.observation_space.subtype, CompleteObservation)
size_th = 518 + 20 # n_line added for timestep_protection_engaged
# timestep_protection_engaged + theta_or/theta_ex/load/gen/storage
size_th = 518 + 20 + 2 * 20 + 11 + 6
assert self.env.observation_space.n == size_th, (
f"obs space size is {self.env.observation_space.n}," f"should be {size_th}"
)
Expand Down Expand Up @@ -274,7 +276,8 @@ def test_action_space(self):
def test_observation_space(self):
assert issubclass(self.env.observation_space.subtype, CompleteObservation)
# size_th = 467
size_th = 473 + 20 # n_line added for timestep_protection_engaged
# timestep_protection_engaged + theta_or/theta_ex/load/gen/storage
size_th = 473 + 20 + 2 * 20 + 11 + 6
assert self.env.observation_space.n == size_th, (
f"obs space size is {self.env.observation_space.n}," f"should be {size_th}"
)
Expand Down Expand Up @@ -319,7 +322,8 @@ def test_action_space(self):
def test_observation_space(self):
assert issubclass(self.env.observation_space.subtype, CompleteObservation)
# size_th = 475
size_th = 481 + 20 # n_line added for timestep_protection_engaged
# timestep_protection_engaged + theta_or/theta_ex/load/gen/storage
size_th = 481 + 20 + 2 * 20 + 11 + 6 + 2
assert self.env.observation_space.n == size_th, (
f"obs space size is {self.env.observation_space.n}," f"should be {size_th}"
)
Expand Down