Skip to content

Commit

Permalink
tests for detailed topo now pass again
Browse files Browse the repository at this point in the history
  • Loading branch information
BDonnot committed Sep 11, 2024
1 parent 556f041 commit 449a4e5
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 314 deletions.
137 changes: 5 additions & 132 deletions grid2op/Action/_backendAction.py
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,9 @@ def _aux_iadd_shunt(self, other):
arr_ = shunts["shunt_bus"]
self.shunt_bus.set_val(arr_)
self.current_shunt_bus.values[self.shunt_bus.changed] = self.shunt_bus.values[self.shunt_bus.changed]

if self.shunt_bus.changed.any():
self._detailed_topo = None

def _aux_iadd_reconcile_disco_reco(self):
"""
Expand Down Expand Up @@ -1034,6 +1037,8 @@ def get_all_switches(self):
else:
shunt_bus = None
if self._detailed_topo is None:
# TODO detailed topo : optimization here : pass the substations modified
# TODO detailed topo : pass the current switches position
self._detailed_topo = detailed_topo_desc.compute_switches_position(self.current_topo.values, shunt_bus)
return self._detailed_topo

Expand Down Expand Up @@ -1126,38 +1131,6 @@ def apply_action(self, backendAction: Union["grid2op.Action._backendAction._Back
tmp_ = self.get_loads_bus()
return self._aux_to_global(tmp_, type(self).load_to_subid)

def _aux_get_bus_detailed_topo(self,
switches_state : np.ndarray,
detailed_topo_desc : DetailedTopoDescription,
el_type_as_int,
el_id):
OBJ_TYPE_COL = type(detailed_topo_desc).OBJ_TYPE_COL
OBJ_ID_COL = type(detailed_topo_desc).OBJ_ID_COL
res = tuple(switches_state[(detailed_topo_desc.switches[:,OBJ_TYPE_COL] == el_type_as_int) & (detailed_topo_desc.switches[:,OBJ_ID_COL] == el_id)].tolist())
return res

def get_loads_bus_switches(self):
tmp_ = self.get_loads_bus()
# TODO detailed topo
# for now this is working because of the super simple representation of subtation
# but in reality i need to come up with a routine to find the topology (and raise the BackendError "impossible topology"
# if not possible)
if type(self).detailed_topo_desc is None:
raise Grid2OpException(ERR_MSG_SWITCH)
detailed_topo_desc = type(self).detailed_topo_desc
# returns an iterable: for each load you have: load_index, (pos_switch1, pos_switch_2, ..., pos_switchn)
# with (pos_switch1, pos_switch_2, ..., pos_switchn) the position of the
# n switch connecting the load to one busbar
# only one of pos_switch1, pos_switch_2, ..., pos_switchn is True !
# res = [(l_id, self._aux_get_bus_detailed_topo(detailed_topo_desc.load_to_busbar_id, l_id, new_bus)) for l_id, new_bus in tmp_]

if self._detailed_topo is None:
self.get_all_switches()
busbar_connectors_state, switches_state = self._detailed_topo
LOAD_TYPE = type(detailed_topo_desc).LOAD_ID
res = [(el_id, self._aux_get_bus_detailed_topo(switches_state, detailed_topo_desc, LOAD_TYPE, el_id)) for el_id, new_bus in tmp_]
return res

def get_gens_bus(self) -> ValueStore:
"""
This function might be called in the implementation of :func:`grid2op.Backend.Backend.apply_action`.
Expand Down Expand Up @@ -1228,26 +1201,6 @@ def get_gens_bus_global(self) -> ValueStore:
tmp_ = copy.deepcopy(self.get_gens_bus())
return self._aux_to_global(tmp_, type(self).gen_to_subid)

def get_gens_bus_switches(self):
tmp_ = self.get_gens_bus()
# TODO detailed topo
# for now this is working because of the super simple representation of subtation
# but in reality i need to come up with a routine to find the topology (and raise the BackendError "impossible topology"
# if not possible)
if type(self).detailed_topo_desc is None:
raise Grid2OpException(ERR_MSG_SWITCH)
detailed_topo_desc = type(self).detailed_topo_desc
# returns an iterable: for each load you have: load_index, (pos_switch1, pos_switch_2, ..., pos_switchn)
# with (pos_switch1, pos_switch_2, ..., pos_switchn) the position of the
# n switch connecting the load to one busbar
# only one of pos_switch1, pos_switch_2, ..., pos_switchn is True !
if self._detailed_topo is None:
self.get_all_switches()
busbar_connectors_state, switches_state = self._detailed_topo
GEN_TYPE = type(detailed_topo_desc).GEN_ID
res = [(el_id, self._aux_get_bus_detailed_topo(switches_state, detailed_topo_desc, GEN_TYPE, el_id)) for el_id, new_bus in tmp_]
return res

def get_lines_or_bus(self) -> ValueStore:
"""
This function might be called in the implementation of :func:`grid2op.Backend.Backend.apply_action`.
Expand Down Expand Up @@ -1318,26 +1271,6 @@ def get_lines_or_bus_global(self) -> ValueStore:
"""
tmp_ = self.get_lines_or_bus()
return self._aux_to_global(tmp_, type(self).line_or_to_subid)

def get_lines_or_bus_switches(self):
tmp_ = self.get_lines_or_bus()
# TODO detailed topo
# for now this is working because of the super simple representation of subtation
# but in reality i need to come up with a routine to find the topology (and raise the BackendError "impossible topology"
# if not possible)
if type(self).detailed_topo_desc is None:
raise Grid2OpException(ERR_MSG_SWITCH)
detailed_topo_desc = type(self).detailed_topo_desc
# returns an iterable: for each load you have: load_index, (pos_switch1, pos_switch_2, ..., pos_switchn)
# with (pos_switch1, pos_switch_2, ..., pos_switchn) the position of the
# n switch connecting the load to one busbar
# only one of pos_switch1, pos_switch_2, ..., pos_switchn is True !
if self._detailed_topo is None:
self.get_all_switches()
busbar_connectors_state, switches_state = self._detailed_topo
LINE_OR_ID = type(detailed_topo_desc).LINE_OR_ID
res = [(el_id, self._aux_get_bus_detailed_topo(switches_state, detailed_topo_desc, LINE_OR_ID, el_id)) for el_id, new_bus in tmp_]
return res

def get_lines_ex_bus(self) -> ValueStore:
"""
Expand Down Expand Up @@ -1410,26 +1343,6 @@ def get_lines_ex_bus_global(self) -> ValueStore:
tmp_ = self.get_lines_ex_bus()
return self._aux_to_global(tmp_, type(self).line_ex_to_subid)

def get_lines_ex_bus_switches(self):
tmp_ = self.get_lines_ex_bus()
# TODO detailed topo
# for now this is working because of the super simple representation of subtation
# but in reality i need to come up with a routine to find the topology (and raise the BackendError "impossible topology"
# if not possible)
if type(self).detailed_topo_desc is None:
raise Grid2OpException(ERR_MSG_SWITCH)
detailed_topo_desc = type(self).detailed_topo_desc
# returns an iterable: for each load you have: load_index, (pos_switch1, pos_switch_2, ..., pos_switchn)
# with (pos_switch1, pos_switch_2, ..., pos_switchn) the position of the
# n switch connecting the load to one busbar
# only one of pos_switch1, pos_switch_2, ..., pos_switchn is True !
if self._detailed_topo is None:
self.get_all_switches()
busbar_connectors_state, switches_state = self._detailed_topo
LINE_EX_ID = type(detailed_topo_desc).LINE_EX_ID
res = [(el_id, self._aux_get_bus_detailed_topo(switches_state, detailed_topo_desc, LINE_EX_ID, el_id)) for el_id, new_bus in tmp_]
return res

def get_storages_bus(self) -> ValueStore:
"""
This function might be called in the implementation of :func:`grid2op.Backend.Backend.apply_action`.
Expand Down Expand Up @@ -1499,46 +1412,6 @@ def get_storages_bus_global(self) -> ValueStore:
tmp_ = self.get_storages_bus()
return self._aux_to_global(tmp_, type(self).storage_to_subid)

def get_storages_bus_switches(self):
tmp_ = self.get_storages_bus()
# TODO detailed topo
# for now this is working because of the super simple representation of subtation
# but in reality i need to come up with a routine to find the topology (and raise the BackendError "impossible topology"
# if not possible)
if type(self).detailed_topo_desc is None:
raise Grid2OpException(ERR_MSG_SWITCH)
detailed_topo_desc = type(self).detailed_topo_desc
# returns an iterable: for each load you have: load_index, (pos_switch1, pos_switch_2, ..., pos_switchn)
# with (pos_switch1, pos_switch_2, ..., pos_switchn) the position of the
# n switch connecting the load to one busbar
# only one of pos_switch1, pos_switch_2, ..., pos_switchn is True !
if self._detailed_topo is None:
self.get_all_switches()
busbar_connectors_state, switches_state = self._detailed_topo
STORAGE_ID = type(detailed_topo_desc).STORAGE_ID
res = [(el_id, self._aux_get_bus_detailed_topo(switches_state, detailed_topo_desc, STORAGE_ID, el_id)) for el_id, new_bus in tmp_]
return res

def get_shunts_bus_switches(self):
if self._shunt_bus is None:
self._shunt_bus = ValueStore(self.n_shunt, dtype=dt_int)
self._shunt_bus.copy_from_index(self.shunt_bus, np.arange(self.n_shunt))

# TODO detailed topo
if type(self).detailed_topo_desc is None:
raise Grid2OpException(ERR_MSG_SWITCH)
detailed_topo_desc = type(self).detailed_topo_desc
# returns an iterable: for each load you have: load_index, (pos_switch1, pos_switch_2, ..., pos_switchn)
# with (pos_switch1, pos_switch_2, ..., pos_switchn) the position of the
# n switch connecting the load to one busbar
# only one of pos_switch1, pos_switch_2, ..., pos_switchn is True !
if self._detailed_topo is None:
self.get_all_switches()
busbar_connectors_state, switches_state = self._detailed_topo
SHUNT_ID = type(detailed_topo_desc).SHUNT_ID
res = [(el_id, self._aux_get_bus_detailed_topo(switches_state, detailed_topo_desc, SHUNT_ID, el_id)) for el_id, new_bus in self._shunt_bus]
return res

def get_shunts_bus_global(self) -> ValueStore:
"""
This function might be called in the implementation of :func:`grid2op.Backend.Backend.apply_action`.
Expand Down
95 changes: 60 additions & 35 deletions grid2op/Space/detailed_topo_description.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,11 @@ def __init__(self):

#: Same as :attr:`DetailedTopoDescription.load_to_conn_node_id` but for shunt
self.shunt_to_conn_node_id = None

#: flag to detect that the detailed topo have been built with the
#: :func:`.DetailedTopoDescriptionfrom_ieee_grid`
#: which enables some feature that will be more generic in the future.
self._from_ieee_grid = False

@classmethod
def from_ieee_grid(cls, init_grid : "grid2op.Space.GridObjects.GridObjects"):
Expand All @@ -233,6 +238,7 @@ def from_ieee_grid(cls, init_grid : "grid2op.Space.GridObjects.GridObjects"):
raise NotImplementedError("This function has not been implemented for less "
"than 2 busbars per subs at the moment.")
res = cls()
res._from_ieee_grid = True

# define the "connection nodes"
# for ieee grid we model:
Expand Down Expand Up @@ -344,9 +350,9 @@ def from_ieee_grid(cls, init_grid : "grid2op.Space.GridObjects.GridObjects"):
res.switches[prev_el : next_el : (1 + n_bb_per_sub), cls.CONN_NODE_2_ID_COL] = conn_node_breaker_ids

# TODO detailed topo : fill switches_to_topovect_id and switches_to_shunt_id
# res.switches_to_topovect_id[prev_el : (prev_el + 2 * nb_el)] = np.repeat(pos_topo_vect[arr_subid == sub_id], 2)
# if init_grid_cls.shunts_data_available and obj_col == cls.SHUNT_ID:
# res.switches_to_shunt_id[prev_el : (prev_el + 2 * nb_el)] = np.repeat(where_el, 2)
res.switches_to_topovect_id[prev_el : next_el : (1 + n_bb_per_sub)] = pos_topo_vect
if init_grid_cls.shunts_data_available and obj_col == cls.SHUNT_ID:
res.switches_to_shunt_id[prev_el : next_el : (1 + n_bb_per_sub)] = np.arange(nb_el)
prev_el = next_el
handled += nb_el

Expand All @@ -362,6 +368,30 @@ def from_ieee_grid(cls, init_grid : "grid2op.Space.GridObjects.GridObjects"):
# TODO detailed topo: have a function to compute the switches `sub_id` columns from the `conn_node_to_subid`
return res

def _aux_compute_switches_pos_ieee(self,
bus_vect, # topo_vect
switches_to_bus_vect, # self.switches_to_topovect_id
switches_state, # result
):
if not self._from_ieee_grid:
raise NotImplementedError("This function is only implemented for detailed topology "
"generated from ieee grids.")

# compute the position for the switches of the "topo_vect" elements
# only work for current grid2op modelling !

# TODO detailed topo vectorize this ! (or cython maybe ?)
for switch_id, switch_topo_vect in enumerate(switches_to_bus_vect):
if switch_topo_vect == -1:
# this is not a switch for an element
continue
my_bus = bus_vect[switch_topo_vect]
if my_bus == -1:
# I init the swith at False, so nothing to do in this case
continue
switches_state[switch_id] = True # connector is connected
switches_state[switch_id + my_bus] = True # connector to busbar is connected

def compute_switches_position(self,
topo_vect: np.ndarray,
shunt_bus: Optional[np.ndarray]=None):
Expand All @@ -381,47 +411,29 @@ def compute_switches_position(self,
Returns
-------
Tuple of 2 elements:
- `busbar_connectors_state` state of each busbar_connector
- `switches_state` state of each switches
`switches_state` state (connected, disconnected) of each switches as
a numpy boolean array.
"""
# TODO detailed topo
# TODO in reality, for more complex environment, this requires a routine to compute it
# but for now in grid2op as only ficitive grid are modeled then
# this is not a problem
if not self._from_ieee_grid:
raise NotImplementedError("This function is only implemented for detailed topology "
"generated from ieee grids.")
switches_state = np.zeros(self.switches.shape[0], dtype=dt_bool)
# busbar_connectors_state = np.zeros(self.busbar_connectors.shape[0], dtype=dt_bool) # we can always say they are opened

# compute the position for the switches of the "topo_vect" elements
# only work for current grid2op modelling !
# compute the position for the switches of the "topo_vect" elements
self._aux_compute_switches_pos_ieee(topo_vect, self.switches_to_topovect_id, switches_state)

if self.switches_to_shunt_id is None or shunt_bus is None:
# no need to
return switches_state

# now handle the shunts
self._aux_compute_switches_pos_ieee(shunt_bus, self.switches_to_shunt_id, switches_state)

# TODO detailed topo vectorize this ! (or cython maybe ?)
for el_id, bus_id in enumerate(topo_vect):
mask_el = self.switches_to_topovect_id == el_id
if mask_el.any():
# it's a regular element
if bus_id == 1:
mask_el[np.where(mask_el)[0][1]] = False # I open the switch to busbar 2 in this case
switches_state[mask_el] = True
elif bus_id == 2:
mask_el[np.where(mask_el)[0][0]] = False # I open the switch to busbar 1 in this case
switches_state[mask_el] = True

if self.switches_to_shunt_id is not None:
# read the switches associated with the shunts
for el_id, bus_id in enumerate(shunt_bus):
# it's an element not in the topo_vect (for now only switches)
mask_el = self.switches_to_shunt_id == el_id
if mask_el.any():
# it's a shunt
if bus_id == 1:
mask_el[np.where(mask_el)[0][1]] = False # I open the switch to busbar 2 in this case
switches_state[mask_el] = True
elif bus_id == 2:
mask_el[np.where(mask_el)[0][0]] = False # I open the switch to busbar 1 in this case
switches_state[mask_el] = True
return switches_state

def from_switches_position(self):
Expand Down Expand Up @@ -455,6 +467,16 @@ def check_validity(self):
self.conn_node_to_subid[self.switches[:,type(self).CONN_NODE_2_ID_COL]]).any():
raise Grid2OpException("Inconsistencies found in the switches mapping. Some switches are "
"mapping connectivity nodes that belong to different substation id.")

arr = self.switches_to_topovect_id[self.switches_to_topovect_id != -1]
dim_topo = arr.max()
if arr.shape[0] != dim_topo + 1:
raise Grid2OpException("Inconsistencies in `self.switches_to_topovect_id`: some elements of "
"topo vect are not controlled by any switches.")
arr.sort()
if (arr != np.arange(dim_topo + 1)).any():
raise Grid2OpException("Inconsistencies in `self.switches_to_topovect_id`: two or more swtiches "
"are pointing to the same element")
# TODO detailed topo other tests
# TODO detailed topo proper exception class and not Grid2OpException

Expand All @@ -474,6 +496,8 @@ def save_to_dict(self, res, as_list=True, copy_=True):
(lambda arr: [int(el) for el in arr]) if as_list else None,
copy_,
)
res["_from_ieee_grid"] = self._from_ieee_grid

# save_to_dict(
# res,
# self,
Expand Down Expand Up @@ -580,6 +604,7 @@ def from_dict(cls, dict_):
res.switches_to_topovect_id = extract_from_dict(
dict_, "switches_to_topovect_id", lambda x: np.array(x).astype(dt_int)
)
res._from_ieee_grid = bool(dict_["_from_ieee_grid"])

if "switches_to_shunt_id" in dict_:
res.switches_to_shunt_id = extract_from_dict(
Expand Down
Loading

0 comments on commit 449a4e5

Please sign in to comment.