Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Demand Response #661

Draft
wants to merge 10 commits into
base: dev_1.11.0
Choose a base branch
from
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ Grid2op extended features:
- jax everything that can be: create a simple env based on jax for topology manipulation, without
redispatching or rules
- backend in jax, maybe ?
- Support for demand response in loads

The "simulate" function :

Expand Down
91 changes: 86 additions & 5 deletions grid2op/Observation/baseObservation.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,19 @@ class BaseObservation(GridObjects):
redispatching is implemented at the current time step, and this is what this vector stores. Note that there is
information about all generators there, even the one that are not
dispatchable.

target_flex: :class:`numpy.ndarray`, dtype:float
For **each** load, it gives the target flexibility, asked by the agent. This is the sum of all
flexibility asked by the agent during the episode. For each load it is a number between:
0 and `load_size`. Note that there is information about all loads here, even the one that are not
flexible.

actual_flex: :class:`numpy.ndarray`, dtype:float
For **each** load, it gives the flexibility currently implemented by the environment.
Indeed, the environment tries to implement at best the :attr:`BaseObservation.target_flex`, but sometimes,
due to physical limitations (e.g. size) it cannot. In this case, only the best possible
flexibility is implemented at the current time step, and this is what this vector stores. Note that there is
information about all loads here, even the one that are not flexible.

storage_charge: :class:`numpy.ndarray`, dtype:float
The actual 'state of charge' of each storage unit, expressed in MWh.
Expand Down Expand Up @@ -457,6 +470,8 @@ class BaseObservation(GridObjects):
"duration_next_maintenance",
"target_dispatch",
"actual_dispatch",
"target_flex",
"actual_flex",
"_shunt_p",
"_shunt_q",
"_shunt_v",
Expand Down Expand Up @@ -562,6 +577,10 @@ def __init__(self,
# redispatching
self.target_dispatch = np.empty(shape=cls.n_gen, dtype=dt_float)
self.actual_dispatch = np.empty(shape=cls.n_gen, dtype=dt_float)

# flexibility
self.target_flex = np.empty(shape=cls.n_load, dtype=dt_float)
self.actual_flex = np.empty(shape=cls.n_load, dtype=dt_float)

# storage unit
self.storage_charge = np.empty(shape=cls.n_storage, dtype=dt_float) # in MWh
Expand Down Expand Up @@ -664,6 +683,8 @@ def _aux_copy(self, other : Self) -> None:
"storage_charge",
"actual_dispatch",
"target_dispatch",
"actual_flex",
"target_flex",
"duration_next_maintenance",
"time_next_maintenance",
"time_before_cooldown_sub",
Expand Down Expand Up @@ -780,9 +801,9 @@ def state_of(
storage_id=None,
substation_id=None,
) -> Dict[Literal["p", "q", "v", "theta", "bus", "sub_id", "actual_dispatch", "target_dispatch",
"maintenance", "cooldown_time", "storage_power", "storage_charge",
"storage_power_target", "storage_theta",
"topo_vect", "nb_bus", "origin", "extremity"],
"actual_flex", "target_flex", "maintenance", "cooldown_time", "storage_power",
"storage_charge", "storage_power_target", "storage_theta", "topo_vect",
"nb_bus", "origin", "extremity"],
Union[int, float, Dict[Literal["p", "q", "v", "a", "sub_id", "bus", "theta"], Union[int, float]]]
]:
"""
Expand Down Expand Up @@ -828,6 +849,9 @@ def state_of(
- "theta" (optional) the voltage angle (in degree) of the bus to which the load is connected
- "bus" on which bus the load is connected in the substation
- "sub_id" the id of the substation to which the load is connected
- "actual_flex" the actual flexibility implemented for this load
- "target_flex" the target flexibility (cumulation of all previously asked flexibility by the agent)
for this load

- if a generator is inspected, then the keys are:

Expand Down Expand Up @@ -924,6 +948,8 @@ def state_of(
"p": self.load_p[load_id],
"q": self.load_q[load_id],
"v": self.load_v[load_id],
"target_flex": self.target_flex[load_id],
"actual_flex": self.actual_flex[load_id],
"bus": self.topo_vect[self.load_pos_topo_vect[load_id]],
"sub_id": cls.load_to_subid[load_id],
}
Expand Down Expand Up @@ -1171,6 +1197,28 @@ def _aux_process_grid2op_compat_191(cls):
except ValueError as exc_:
# this attribute was not there in the first place
pass

@classmethod
def _aux_process_grid2op_compat_1110(cls):
cls.attr_list_vect = copy.deepcopy(cls.attr_list_vect)

for el in [
"load_size",
"load_flexible",
"load_max_ramp_up",
"load_max_ramp_down",
"load_min_uptime",
"load_min_downtime",
"load_cost_per_MW",
"target_flex",
"actual_flex",
]:
try:
cls.attr_list_vect.remove(el)
except ValueError as exc_:
# this attribute was not there in the first place
pass


@classmethod
def process_grid2op_compat(cls) -> None:
Expand Down Expand Up @@ -1200,6 +1248,10 @@ def process_grid2op_compat(cls) -> None:
if glop_ver < version.parse("1.9.1"):
# alert attributes have been added in 1.9.1
cls._aux_process_grid2op_compat_191()

if glop_ver < version.parse("1.11.0.dev0"):
# flexibility attributes addded in 1.10.4
cls._aux_process_grid2op_compat_1110()

cls.attr_list_set = copy.deepcopy(cls.attr_list_set)
cls.attr_list_set = set(cls.attr_list_vect)
Expand Down Expand Up @@ -1275,6 +1327,10 @@ def reset(self) -> None:
# redispatching
self.target_dispatch[:] = np.NaN
self.actual_dispatch[:] = np.NaN

# flexibility
self.target_flex[:] = np.NaN
self.actual_flex[:] = np.NaN

# storage units
self.storage_charge[:] = np.NaN
Expand Down Expand Up @@ -1376,6 +1432,10 @@ def set_game_over(self,
# redispatching
self.target_dispatch[:] = 0.0
self.actual_dispatch[:] = 0.0

# flexibility
self.target_flex[:] = 0.0
self.actual_flex[:] = 0.0

# storage
self.storage_charge[:] = 0.0
Expand Down Expand Up @@ -2699,6 +2759,8 @@ def _aux_add_buses(self, graph, cls, first_id):
return bus_ids

def _aux_add_loads(self, graph, cls, first_id):
nodes_prop = [("target_flex", self.target_flex),
("actual_flex", self.actual_flex)]
edges_prop=[
("p", self.load_p),
("q", self.load_q),
Expand All @@ -2713,7 +2775,7 @@ def _aux_add_loads(self, graph, cls, first_id):
cls.n_load,
self.load_bus,
cls.load_to_subid,
nodes_prop=None,
nodes_prop=nodes_prop,
edges_prop=edges_prop)
return load_ids

Expand Down Expand Up @@ -3709,13 +3771,24 @@ def to_dict(self):
self._dictionnarized["cooldown"][
"substation"
] = self.time_before_cooldown_sub

# redispatching
self._dictionnarized["redispatching"] = {}
self._dictionnarized["redispatching"][
"target_redispatch"
] = self.target_dispatch
self._dictionnarized["redispatching"][
"actual_dispatch"
] = self.actual_dispatch

# flexibility
self._dictionnarized["flexibility"] = {}
self._dictionnarized["flexibility"][
"target_flex"
] = self.target_flex
self._dictionnarized["flexibility"][
"actual_flex"
] = self.actual_flex

# storage
self._dictionnarized["storage_charge"] = 1.0 * self.storage_charge
Expand Down Expand Up @@ -4006,6 +4079,9 @@ def add_act(self, act : "grid2op.Action.BaseAction", issue_warn=True) -> Self:
"by the environment (consult the documentation about the modeling of the "
"generators for example) so we will not even try to mimic this here."
)

if "flexibility" in cls_act.authorized_keys:
pass # TODO: Implement Flexibility in BaseAction

if "set_storage" in cls_act.authorized_keys:
storage_p = act.storage_p
Expand Down Expand Up @@ -4226,7 +4302,12 @@ def _update_obs_complete(self, env: "grid2op.Environment.BaseEnv", with_forecast
# redispatching
self.target_dispatch[:] = env._target_dispatch
self.actual_dispatch[:] = env._actual_dispatch


# flexibility
# TODO: Add _target_flex and _actual_flex to BaseEnv / GridObject
self.target_flex[:] = np.zeros(env.n_load, dtype=dt_float)
self.actual_flex[:] = np.zeros(env.n_load, dtype=dt_float)

self._thermal_limit[:] = env.get_thermal_limit()

if self.redispatching_unit_commitment_availble:
Expand Down
58 changes: 32 additions & 26 deletions grid2op/Observation/completeObservation.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,63 +74,67 @@ class CompleteObservation(BaseObservation):
[:attr:`grid2op.Space.GridObjects.n_gen` elements]
30. :attr:`BaseObservation.actual_dispatch` the actual dispatch for each generator
[:attr:`grid2op.Space.GridObjects.n_gen` elements]
31. :attr:`BaseObservation.storage_charge` the actual state of charge of each storage unit
31. :attr:`BaseObservation.target_flex` the target flexibility for each load
[:attr:`grid2op.Space.GridObjects.n_gen` elements]
32. :attr:`BaseObservation.actual_flex` the actual flexibility for each load
[:attr:`grid2op.Space.GridObjects.n_gen` elements]
33. :attr:`BaseObservation.storage_charge` the actual state of charge of each storage unit
[:attr:`grid2op.Space.GridObjects.n_storage` elements]
32. :attr:`BaseObservation.storage_power_target` the production / consumption of setpoint of each storage unit
34. :attr:`BaseObservation.storage_power_target` the production / consumption of setpoint of each storage unit
[:attr:`grid2op.Space.GridObjects.n_storage` elements]
33. :attr:`BaseObservation.storage_power` the realized production / consumption of each storage unit
35. :attr:`BaseObservation.storage_power` the realized production / consumption of each storage unit
[:attr:`grid2op.Space.GridObjects.n_storage` elements]
34. :attr:`BaseObservation.gen_p_before_curtail` : the theoretical generation that would have happened
36. :attr:`BaseObservation.gen_p_before_curtail` : the theoretical generation that would have happened
if no generator from renewable energy sources have been performed (in MW)
[:attr:`grid2op.Space.GridObjects.n_gen` elements]
35. :attr:`BaseObservation.curtailment` : the current curtailment applied
37. :attr:`BaseObservation.curtailment` : the current curtailment applied
[:attr:`grid2op.Space.GridObjects.n_gen` elements]
36. :attr:`BaseObservation.is_alarm_illegal` whether the last alarm has been illegal (due to budget
38. :attr:`BaseObservation.is_alarm_illegal` whether the last alarm has been illegal (due to budget
constraint) [``bool``],
.. warning: /!\\\\ Only valid with "l2rpn_icaps_2021" environment /!\\\\
37. :attr:`BaseObservation.curtailment_limit` : the current curtailment limit (if any)
39. :attr:`BaseObservation.curtailment_limit` : the current curtailment limit (if any)
[:attr:`grid2op.Space.GridObjects.n_gen` elements]
38. :attr:`BaseObservation.time_since_last_alarm` number of step since the last alarm has been raised
40. :attr:`BaseObservation.time_since_last_alarm` number of step since the last alarm has been raised
successfully [``int``]
.. warning: /!\\\\ Only valid with "l2rpn_icaps_2021" environment /!\\\\
39. :attr:`BaseObservation.last_alarm` : for each alarm zone, gives the last step at which an alarm has
41. :attr:`BaseObservation.last_alarm` : for each alarm zone, gives the last step at which an alarm has
been successfully raised at this zone
.. warning: /!\\\\ Only valid with "l2rpn_icaps_2021" environment /!\\\\
[:attr:`grid2op.Space.GridObjects.dim_alarms` elements]
40. :attr:`BaseObservation.attention_budget` : the current attention budget
42. :attr:`BaseObservation.attention_budget` : the current attention budget
[``int``]
41. :attr:`BaseObservation.was_alarm_used_after_game_over` : was the last alarm used to compute anything related
43. :attr:`BaseObservation.was_alarm_used_after_game_over` : was the last alarm used to compute anything related
to the attention budget when there was a game over (can only be set to ``True`` if the observation
corresponds to a game over), warning: /!\\\\ Only valid with "l2rpn_icaps_2021" environment /!\\\\
[``bool``]
42. :attr:`BaseObservation.is_alarm_illegal` whether the last alert has been illegal (due to budget
44. :attr:`BaseObservation.is_alarm_illegal` whether the last alert has been illegal (due to budget
constraint) [``bool``]
43. :attr:`BaseObservation.curtailment_limit` : the current curtailment limit (if any)
45. :attr:`BaseObservation.curtailment_limit` : the current curtailment limit (if any)
[:attr:`grid2op.Space.GridObjects.n_gen` elements]
44. :attr:`BaseObservation.curtailment_limit_effective` Limit (in ratio of gen_pmax) imposed on
46. :attr:`BaseObservation.curtailment_limit_effective` Limit (in ratio of gen_pmax) imposed on
each renewable generator effectively imposed by the environment.
45. :attr:`BaseObservation.current_step` the number of steps since the beginning of the episode (it's
47. :attr:`BaseObservation.current_step` the number of steps since the beginning of the episode (it's
0 for the observation after a call to `env.reset()`)
46. :attr:`BaseObservation.max_step` maximum number of steps that can be done by the environment.
48. :attr:`BaseObservation.max_step` maximum number of steps that can be done by the environment.
When :attr:`BaseObservation.current_step` is :attr:`BaseObservation.max_step` the the environment
is done.
47. :attr:`BaseObservation.delta_time` Amount of time (in minutes) represented by a step. In general, there
49. :attr:`BaseObservation.delta_time` Amount of time (in minutes) represented by a step. In general, there
are the equivalent of 5 minutes between two steps.
48. :attr:`BaseObservation.gen_margin_up` From how much can you increase each generators production between this
50. :attr:`BaseObservation.gen_margin_up` From how much can you increase each generators production between this
step and the next.
49. :attr:`BaseObservation.gen_margin_down` From how much can you decrease each generators production between this
51. :attr:`BaseObservation.gen_margin_down` From how much can you decrease each generators production between this
step and the next.
50. :attr:`BaseObservation.active_alert` This attribute gives the lines "under alert" at the given observation.
51. :attr:`BaseObservation.time_since_last_alert` Give the time since an alert has been raised for each powerline.
52. :attr:`BaseObservation.alert_duration` Give the time since an alert has started for all attackable line.
53. :attr:`BaseObservation.total_number_of_alert` Total number of alerts since the beginning of the episode sent by
52. :attr:`BaseObservation.active_alert` This attribute gives the lines "under alert" at the given observation.
53. :attr:`BaseObservation.time_since_last_alert` Give the time since an alert has been raised for each powerline.
54. :attr:`BaseObservation.alert_duration` Give the time since an alert has started for all attackable line.
55. :attr:`BaseObservation.total_number_of_alert` Total number of alerts since the beginning of the episode sent by
the agent
54. :attr:`BaseObservation.time_since_last_attack` For each attackable line `i` it counts the number of steps since the powerline has
56. :attr:`BaseObservation.time_since_last_attack` For each attackable line `i` it counts the number of steps since the powerline has
been attacked
55. :attr:`BaseObservation.was_alert_used_after_attack` For each attackable line `i` it says if an alert has been used or not
57. :attr:`BaseObservation.was_alert_used_after_attack` For each attackable line `i` it says if an alert has been used or not
for the computation of the reward: +1 means "used and the alert was correct", -1 means "used and the alert was not correct"
and 0 means "not used"
56. :attr:`BaseObservation.attack_under_alert` For each attackable line `i` it says if an alert has been sent (+1) or not (-1)
58. :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.

"""
Expand Down Expand Up @@ -166,6 +170,8 @@ class CompleteObservation(BaseObservation):
"duration_next_maintenance",
"target_dispatch",
"actual_dispatch",
"target_flex",
"actual_flex",
"storage_charge",
"storage_power_target",
"storage_power",
Expand Down
3 changes: 2 additions & 1 deletion grid2op/tests/_aux_test_gym_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ def test_convert_togym(self):
for el in env_gym.observation_space.spaces
]
)
size_th = 536 # as of grid2Op 1.7.1 (where all obs attributes are there)
size_th = 558 # as of grid2Op 1.7.1 (where all obs attributes are there)
# as of grid2op 1.11.0 (where flexibility was added)
assert (
dim_obs_space == size_th
), f"Size should be {size_th} but is {dim_obs_space}"
Expand Down
13 changes: 12 additions & 1 deletion grid2op/tests/test_Observation.py
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,8 @@ def setUp(self):
],
"target_dispatch": [0.0, 0.0, 0.0, 0.0, 0.0],
"actual_dispatch": [0.0, 0.0, 0.0, 0.0, 0.0],
"target_flex": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
"actual_flex": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
"_shunt_p": [0.0],
"_shunt_q": [-17.923625946044922],
"_shunt_v": [0.20202238857746124],
Expand Down Expand Up @@ -878,8 +880,13 @@ def setUp(self):
dt_int,
dt_int,
dt_int,
# Redispatch
dt_float,
dt_float,
# Flexibility
dt_float,
dt_float,
# Storage
dt_float,
dt_float,
dt_float,
Expand Down Expand Up @@ -951,8 +958,12 @@ def setUp(self):
14,
20,
20,
# Redispatch
5,
5,
# Flexibility
11,
11,
0,
0,
0,
Expand Down Expand Up @@ -985,7 +996,7 @@ def setUp(self):
0
]
)
self.size_obs = 429 + 4 + 4 + 2 + 1 + 10 + 5 + 0
self.size_obs = 429 + 4 + 4 + 2 + 1 + 10 + 5 + 0 + 11 + 11

def tearDown(self):
self.env.close()
Expand Down
Loading