Skip to content

Commit

Permalink
Merge pull request #10 from Grid2op/bd-dev
Browse files Browse the repository at this point in the history
Ability to support detachment
  • Loading branch information
BDonnot authored Feb 14, 2025
2 parents 6db5edd + 4efed89 commit 64242de
Show file tree
Hide file tree
Showing 3 changed files with 249 additions and 12 deletions.
72 changes: 62 additions & 10 deletions pypowsybl2grid/pypowsybl_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import os
import time
from typing import Optional, Tuple, Union
import warnings

import grid2op
from grid2op.dtypes import dt_float, dt_int
Expand All @@ -27,15 +28,22 @@
class PyPowSyBlBackend(Backend):

def __init__(self,
detailed_infos_for_cascading_failures=False,
check_isolated_and_disconnected_injections = True,
consider_open_branch_reactive_flow = False,
n_busbar_per_sub = DEFAULT_N_BUSBAR_PER_SUB,
connect_all_elements_to_first_bus = False,
detailed_infos_for_cascading_failures:bool=False,
check_isolated_and_disconnected_injections:Optional[bool]=None,
consider_open_branch_reactive_flow:bool = False,
n_busbar_per_sub:int = DEFAULT_N_BUSBAR_PER_SUB,
connect_all_elements_to_first_bus:bool = False,
lf_parameters: pp.loadflow.Parameters = None):
Backend.__init__(self,
detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures,
can_be_copied=True)
can_be_copied=True,
# save this kwargs (might be needed)
check_isolated_and_disconnected_injections=check_isolated_and_disconnected_injections,
consider_open_branch_reactive_flow=consider_open_branch_reactive_flow,
connect_all_elements_to_first_bus=connect_all_elements_to_first_bus,
lf_parameters=lf_parameters
)

self._check_isolated_and_disconnected_injections = check_isolated_and_disconnected_injections
self._consider_open_branch_reactive_flow = consider_open_branch_reactive_flow
self.n_busbar_per_sub = n_busbar_per_sub
Expand Down Expand Up @@ -83,7 +91,20 @@ def network(self) -> pp.network.Network:
def load_grid(self,
path: Union[os.PathLike, str],
filename: Optional[Union[os.PathLike, str]] = None) -> None:
start_time = time.time()
start_time = time.perf_counter()
cls = type(self)
if hasattr(cls, "can_handle_more_than_2_busbar"):
# grid2op version >= 1.10.0 then we use this
self.can_handle_more_than_2_busbar()

if hasattr(cls, "can_handle_detachment"):
# grid2op version >= 1.11.0 then we use this
self.can_handle_detachment()
self.check_detachment_coherent()
else:
if self._check_isolated_and_disconnected_injections is None:
# default behaviour in grid2op before detachment is allowed
self._check_isolated_and_disconnected_injections = True

# load network
full_path = self.make_complete_path(path, filename)
Expand All @@ -98,10 +119,43 @@ def load_grid(self,

self.load_grid_from_iidm(network)

end_time = time.time()
end_time = time.perf_counter()
elapsed_time = (end_time - start_time) * 1000
logger.info(f"Network '{network.id}' loaded in {elapsed_time:.2f} ms")

def check_detachment_coherent(self):
if self._check_isolated_and_disconnected_injections is None:
# user does not provide anything to the backend
if self.detachment_is_allowed:
self._check_isolated_and_disconnected_injections = False
else:
self._check_isolated_and_disconnected_injections = True
else:
# user provided something, I check if it's consistent
if self._check_isolated_and_disconnected_injections:
if self.detachment_is_allowed:
msg_ = ("You initialized the pypowsybl backend with \"check_isolated_and_disconnected_injections=True\" "
"and the environment with \"allow_detachment=True\" which is not consistent. "
"Discarding the call to env.make, the detachement is NOT supported for this env. "
"If you want to support detachment, either initialize the pypowsybl backend with "
"\"check_isolated_and_disconnected_injections=False\" or (preferably) with "
"\"check_isolated_and_disconnected_injections=None\"")
warnings.warn(msg_)
logger.warning(msg_)
type(self).detachment_is_allowed = False
self.detachment_is_allowed = False
else:
if not self.detachment_is_allowed:
msg_ = ("You initialized the pypowsybl backend with \"check_isolated_and_disconnected_injections=False\" "
"and the environment with \"allow_detachment=False\" which is not consistent. "
"The setting of \"check_isolated_and_disconnected_injections=False\" will have no effect. "
"Detachment will NOT be supported. If you want so, please consider initializing pypowsybl backend with "
"\"check_isolated_and_disconnected_injections=True\" or (preferably) with "
"\"check_isolated_and_disconnected_injections=None\"")
warnings.warn(msg_)
logger.warning(msg_)


def load_grid_from_iidm(self, network: pp.network.Network) -> None:
if self._grid:
self._grid.close()
Expand All @@ -116,8 +170,6 @@ def load_grid_from_iidm(self, network: pp.network.Network) -> None:
self.name_sub = self._grid.get_string_value(pp.grid2op.StringValueType.VOLTAGE_LEVEL_NAME)
self.n_sub = len(self.name_sub)

self.can_handle_more_than_2_busbar()

logger.info(f"{self.n_busbar_per_sub} busbars per substation")

# loads
Expand Down
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
[tool.poetry]
name = "pypowsybl2grid"
version = "0.2.0"
version = "0.2.1"
description = "An integration between Grid2op and PyPowSybl"
authors = ["Geoffroy Jamgotchian <[email protected]>"]
authors = ["Geoffroy Jamgotchian <[email protected]>",
"Benjamin Donnot <[email protected]>"]
readme = "README.md"
license = "MPL-2.0"

Expand Down
184 changes: 184 additions & 0 deletions tests/test_detachment_support_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# Copyright (c) 2025, RTE (http://www.rte-france.com)
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# SPDX-License-Identifier: MPL-2.0

import logging
from typing import Dict

import grid2op
import grid2op.Space

import pytest

from pypowsybl2grid.pypowsybl_backend import PyPowSyBlBackend

TOLERANCE = 1e-3

@pytest.fixture(autouse=True)
def setup():
logging.basicConfig()
logging.getLogger('powsybl').setLevel(logging.WARN)


def create_env(check_isolated_and_disconnected_injections=None, allow_detachment=None):
if not hasattr(grid2op.Space, "DEFAULT_ALLOW_DETACHMENT"):
return None
backend = create_backend(check_isolated_and_disconnected_injections=check_isolated_and_disconnected_injections)
if allow_detachment is not None:
env = grid2op.make("l2rpn_case14_sandbox", test=True, allow_detachment=allow_detachment, backend=backend)
else:
env = grid2op.make("l2rpn_case14_sandbox", test=True, backend=backend)
return env


def create_backend(check_isolated_and_disconnected_injections=None):
return PyPowSyBlBackend(check_isolated_and_disconnected_injections=check_isolated_and_disconnected_injections,
consider_open_branch_reactive_flow=True,
connect_all_elements_to_first_bus=False,
)


def test_None_False():
env = create_env(check_isolated_and_disconnected_injections=None, allow_detachment=False)
if env is None:
# wrong grid2Op version
return
try:
assert not type(env.backend).detachment_is_allowed
assert env.backend._check_isolated_and_disconnected_injections
assert not type(env).detachment_is_allowed
obs = env.reset()
obs, reward, done, info = env.step(env.action_space({'set_bus': {"loads_id": [(0, -1)]}}))
assert done
finally:
env.close()


def test_None_default():
env = create_env(check_isolated_and_disconnected_injections=None)
if env is None:
# wrong grid2Op version
return
try:
assert not type(env.backend).detachment_is_allowed
assert env.backend._check_isolated_and_disconnected_injections
assert not type(env).detachment_is_allowed
obs = env.reset()
obs, reward, done, info = env.step(env.action_space({'set_bus': {"loads_id": [(0, -1)]}}))
assert done
finally:
env.close()


def test_None_True():
env = create_env(check_isolated_and_disconnected_injections=None, allow_detachment=True)
if env is None:
# wrong grid2Op version
return
try:
assert type(env.backend).detachment_is_allowed
assert not env.backend._check_isolated_and_disconnected_injections
assert type(env).detachment_is_allowed
obs = env.reset()
obs, reward, done, info = env.step(env.action_space({'set_bus': {"loads_id": [(0, -1)]}}))
assert not done
finally:
env.close()


def test_False_False():
env = create_env(check_isolated_and_disconnected_injections=False, allow_detachment=False)
if env is None:
# wrong grid2Op version
return
try:
assert not type(env.backend).detachment_is_allowed
assert not env.backend._check_isolated_and_disconnected_injections
assert not type(env).detachment_is_allowed
obs = env.reset()
obs, reward, done, info = env.step(env.action_space({'set_bus': {"loads_id": [(0, -1)]}}))
assert done
finally:
env.close()


def test_False_default():
env = create_env(check_isolated_and_disconnected_injections=False)
if env is None:
# wrong grid2Op version
return
try:
assert not type(env.backend).detachment_is_allowed
assert not env.backend._check_isolated_and_disconnected_injections
assert not type(env).detachment_is_allowed
obs = env.reset()
obs, reward, done, info = env.step(env.action_space({'set_bus': {"loads_id": [(0, -1)]}}))
assert done
finally:
env.close()


def test_False_True():
env = create_env(check_isolated_and_disconnected_injections=False, allow_detachment=True)
if env is None:
# wrong grid2Op version
return
try:
assert type(env.backend).detachment_is_allowed
assert not env.backend._check_isolated_and_disconnected_injections
assert type(env).detachment_is_allowed
obs = env.reset()
obs, reward, done, info = env.step(env.action_space({'set_bus': {"loads_id": [(0, -1)]}}))
assert not done
finally:
env.close()


def test_True_False():
env = create_env(check_isolated_and_disconnected_injections=True, allow_detachment=False)
if env is None:
# wrong grid2Op version
return
try:
assert not type(env.backend).detachment_is_allowed
assert env.backend._check_isolated_and_disconnected_injections
assert not type(env).detachment_is_allowed
obs = env.reset()
obs, reward, done, info = env.step(env.action_space({'set_bus': {"loads_id": [(0, -1)]}}))
assert done
finally:
env.close()


def test_True_default():
env = create_env(check_isolated_and_disconnected_injections=True)
if env is None:
# wrong grid2Op version
return
try:
assert not type(env.backend).detachment_is_allowed
assert env.backend._check_isolated_and_disconnected_injections
assert not type(env).detachment_is_allowed
obs = env.reset()
obs, reward, done, info = env.step(env.action_space({'set_bus': {"loads_id": [(0, -1)]}}))
assert done
finally:
env.close()


def test_True_True():
env = create_env(check_isolated_and_disconnected_injections=True, allow_detachment=True)
if env is None:
# wrong grid2Op version
return
try:
assert not type(env.backend).detachment_is_allowed
assert env.backend._check_isolated_and_disconnected_injections
assert not type(env).detachment_is_allowed
obs = env.reset()
obs, reward, done, info = env.step(env.action_space({'set_bus': {"loads_id": [(0, -1)]}}))
assert done
finally:
env.close()

0 comments on commit 64242de

Please sign in to comment.