LightSim2Grid 0.12.0__cp313-cp313-win_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lightsim2grid/__init__.py +57 -0
- lightsim2grid/_utils/__init__.py +136 -0
- lightsim2grid/compilation_options/__init__.py +37 -0
- lightsim2grid/contingencyAnalysis.py +425 -0
- lightsim2grid/elements/__init__.py +38 -0
- lightsim2grid/gridmodel/__init__.py +26 -0
- lightsim2grid/gridmodel/compare_gridmodel.py +323 -0
- lightsim2grid/gridmodel/from_pandapower/__init__.py +11 -0
- lightsim2grid/gridmodel/from_pandapower/_aux_add_dc_line.py +82 -0
- lightsim2grid/gridmodel/from_pandapower/_aux_add_gen.py +35 -0
- lightsim2grid/gridmodel/from_pandapower/_aux_add_line.py +73 -0
- lightsim2grid/gridmodel/from_pandapower/_aux_add_load.py +42 -0
- lightsim2grid/gridmodel/from_pandapower/_aux_add_sgen.py +85 -0
- lightsim2grid/gridmodel/from_pandapower/_aux_add_shunt.py +41 -0
- lightsim2grid/gridmodel/from_pandapower/_aux_add_slack.py +131 -0
- lightsim2grid/gridmodel/from_pandapower/_aux_add_storage.py +38 -0
- lightsim2grid/gridmodel/from_pandapower/_aux_add_trafo.py +149 -0
- lightsim2grid/gridmodel/from_pandapower/_aux_check_legit.py +59 -0
- lightsim2grid/gridmodel/from_pandapower/_my_const.py +15 -0
- lightsim2grid/gridmodel/from_pandapower/_pp_bus_to_ls_bus.py +16 -0
- lightsim2grid/gridmodel/from_pandapower/initGridModel.py +174 -0
- lightsim2grid/gridmodel/from_pypowsybl/__init__.py +11 -0
- lightsim2grid/gridmodel/from_pypowsybl/_aux_handle_slack.py +62 -0
- lightsim2grid/gridmodel/from_pypowsybl/_from_pypowsybl.py +552 -0
- lightsim2grid/lightSimBackend.py +2119 -0
- lightsim2grid/newtonpf/__init__.py +11 -0
- lightsim2grid/pandapower_compat/__init__.py +12 -0
- lightsim2grid/pandapower_compat/dcpf/__init__.py +11 -0
- lightsim2grid/pandapower_compat/dcpf/_dcpf.py +65 -0
- lightsim2grid/pandapower_compat/newtonpf/__init__.py +11 -0
- lightsim2grid/pandapower_compat/newtonpf/_newtonpf.py +351 -0
- lightsim2grid/physical_law_checker.py +126 -0
- lightsim2grid/rewards/__init__.py +11 -0
- lightsim2grid/rewards/n1ContingencyReward.py +190 -0
- lightsim2grid/securityAnalysis.py +31 -0
- lightsim2grid/solver/__init__.py +75 -0
- lightsim2grid/timeSerie.py +284 -0
- lightsim2grid-0.12.0.dist-info/METADATA +382 -0
- lightsim2grid-0.12.0.dist-info/RECORD +45 -0
- lightsim2grid-0.12.0.dist-info/WHEEL +5 -0
- lightsim2grid-0.12.0.dist-info/licenses/AUTHORS.txt +9 -0
- lightsim2grid-0.12.0.dist-info/licenses/LICENSE +373 -0
- lightsim2grid-0.12.0.dist-info/licenses/LICENSE.md +373 -0
- lightsim2grid-0.12.0.dist-info/top_level.txt +2 -0
- lightsim2grid_cpp.cp313-win_arm64.pyd +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Copyright (c) 2020-2024, 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 LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
|
|
8
|
+
|
|
9
|
+
__version__ = "0.12.0"
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"newtonpf",
|
|
13
|
+
"SolverType",
|
|
14
|
+
"ErrorType",
|
|
15
|
+
"solver",
|
|
16
|
+
"compilation_options"]
|
|
17
|
+
|
|
18
|
+
# import directly from c++ module
|
|
19
|
+
from lightsim2grid.solver import SolverType
|
|
20
|
+
from lightsim2grid.solver import ErrorType
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
from lightsim2grid.lightSimBackend import LightSimBackend # noqa: F401
|
|
24
|
+
__all__.append("LightSimBackend")
|
|
25
|
+
except ImportError as exc_: # noqa: F841
|
|
26
|
+
# grid2op is not installed, the Backend will not be available
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
from lightsim2grid.physical_law_checker import PhysicalLawChecker # noqa: F401
|
|
31
|
+
__all__.append("PhysicalLawChecker")
|
|
32
|
+
except ImportError as exc_: # noqa: F841
|
|
33
|
+
# grid2op is not installed, the PhysicalLawChecker will not be available
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
from lightsim2grid.timeSerie import TimeSerie # noqa: F401
|
|
38
|
+
__all__.append("TimeSerie")
|
|
39
|
+
__all__.append("timeSerie")
|
|
40
|
+
except ImportError as exc_: # noqa: F841
|
|
41
|
+
# grid2op is not installed, the TimeSeries module will not be available
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
from lightsim2grid.contingencyAnalysis import ContingencyAnalysis # noqa: F401
|
|
46
|
+
__all__.append("contingencyAnalysis")
|
|
47
|
+
__all__.append("ContingencyAnalysis")
|
|
48
|
+
except ImportError as exc_: # noqa: F841
|
|
49
|
+
# grid2op is not installed, the SecurtiyAnalysis module will not be available
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
from lightsim2grid.rewards import N1ContingencyReward # noqa: F401
|
|
54
|
+
__all__.append("rewards")
|
|
55
|
+
except ImportError as exc_: # noqa: F841
|
|
56
|
+
# grid2op is not installed, the SecurtiyAnalysis module will not be available
|
|
57
|
+
pass
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# this class is defined to avoid circular references when using grid2op -> pandapower -> lightsim2grid -> grid2op
|
|
2
|
+
# this module is lazily imported by the LightSimBackend and should not be used anywhere else,
|
|
3
|
+
# as the name states
|
|
4
|
+
|
|
5
|
+
import warnings
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
import grid2op
|
|
9
|
+
from grid2op.Backend import PandaPowerBackend
|
|
10
|
+
class _DoNotUseAnywherePandaPowerBackend(PandaPowerBackend):
|
|
11
|
+
"""used to duplicate the class attributes of PandaPowerBackend"""
|
|
12
|
+
shunts_data_available = True
|
|
13
|
+
|
|
14
|
+
if not hasattr(PandaPowerBackend, "_clear_grid_dependant_class_attributes"):
|
|
15
|
+
# not available in python 3.7
|
|
16
|
+
warnings.warn("This is a compatibility mode, please avoid using it and migrate to python >=3.10 ideally.")
|
|
17
|
+
def f(cls):
|
|
18
|
+
cls._INIT_GRID_CLS = None # do not modify that, this is handled by grid2op automatically
|
|
19
|
+
cls._PATH_GRID_CLASSES = None # especially do not modify that
|
|
20
|
+
|
|
21
|
+
cls.glop_version = grid2op.__version__
|
|
22
|
+
|
|
23
|
+
cls.SUB_COL = 0
|
|
24
|
+
cls.LOA_COL = 1
|
|
25
|
+
cls.GEN_COL = 2
|
|
26
|
+
cls.LOR_COL = 3
|
|
27
|
+
cls.LEX_COL = 4
|
|
28
|
+
cls.STORAGE_COL = 5
|
|
29
|
+
|
|
30
|
+
cls.attr_list_vect = None
|
|
31
|
+
cls.attr_list_set = {}
|
|
32
|
+
cls.attr_list_json = []
|
|
33
|
+
cls.attr_nan_list_set = set()
|
|
34
|
+
|
|
35
|
+
# class been init
|
|
36
|
+
cls._IS_INIT = False
|
|
37
|
+
|
|
38
|
+
# name of the objects
|
|
39
|
+
cls.env_name = "unknown"
|
|
40
|
+
cls.name_load = None
|
|
41
|
+
cls.name_gen = None
|
|
42
|
+
cls.name_line = None
|
|
43
|
+
cls.name_sub = None
|
|
44
|
+
cls.name_storage = None
|
|
45
|
+
|
|
46
|
+
cls.n_gen = -1
|
|
47
|
+
cls.n_load = -1
|
|
48
|
+
cls.n_line = -1
|
|
49
|
+
cls.n_sub = -1
|
|
50
|
+
cls.n_storage = -1
|
|
51
|
+
|
|
52
|
+
cls.sub_info = None
|
|
53
|
+
cls.dim_topo = -1
|
|
54
|
+
|
|
55
|
+
# to which substation is connected each element
|
|
56
|
+
cls.load_to_subid = None
|
|
57
|
+
cls.gen_to_subid = None
|
|
58
|
+
cls.line_or_to_subid = None
|
|
59
|
+
cls.line_ex_to_subid = None
|
|
60
|
+
cls.storage_to_subid = None
|
|
61
|
+
|
|
62
|
+
# which index has this element in the substation vector
|
|
63
|
+
cls.load_to_sub_pos = None
|
|
64
|
+
cls.gen_to_sub_pos = None
|
|
65
|
+
cls.line_or_to_sub_pos = None
|
|
66
|
+
cls.line_ex_to_sub_pos = None
|
|
67
|
+
cls.storage_to_sub_pos = None
|
|
68
|
+
|
|
69
|
+
# which index has this element in the topology vector
|
|
70
|
+
cls.load_pos_topo_vect = None
|
|
71
|
+
cls.gen_pos_topo_vect = None
|
|
72
|
+
cls.line_or_pos_topo_vect = None
|
|
73
|
+
cls.line_ex_pos_topo_vect = None
|
|
74
|
+
cls.storage_pos_topo_vect = None
|
|
75
|
+
|
|
76
|
+
# "convenient" way to retrieve information of the grid
|
|
77
|
+
cls.grid_objects_types = None
|
|
78
|
+
# to which substation each element of the topovect is connected
|
|
79
|
+
cls._topo_vect_to_sub = None
|
|
80
|
+
|
|
81
|
+
# list of attribute to convert it from/to a vector
|
|
82
|
+
cls._vectorized = None
|
|
83
|
+
|
|
84
|
+
# redispatch data, not available in all environment
|
|
85
|
+
cls.redispatching_unit_commitment_availble = False
|
|
86
|
+
cls.gen_type = None
|
|
87
|
+
cls.gen_pmin = None
|
|
88
|
+
cls.gen_pmax = None
|
|
89
|
+
cls.gen_redispatchable = None
|
|
90
|
+
cls.gen_max_ramp_up = None
|
|
91
|
+
cls.gen_max_ramp_down = None
|
|
92
|
+
cls.gen_min_uptime = None
|
|
93
|
+
cls.gen_min_downtime = None
|
|
94
|
+
cls.gen_cost_per_MW = None # marginal cost (in currency / (power.step) and not in $/(MW.h) it would be $ / (MW.5mins) )
|
|
95
|
+
cls.gen_startup_cost = None # start cost (in currency)
|
|
96
|
+
cls.gen_shutdown_cost = None # shutdown cost (in currency)
|
|
97
|
+
cls.gen_renewable = None
|
|
98
|
+
|
|
99
|
+
# storage unit static data
|
|
100
|
+
cls.storage_type = None
|
|
101
|
+
cls.storage_Emax = None
|
|
102
|
+
cls.storage_Emin = None
|
|
103
|
+
cls.storage_max_p_prod = None
|
|
104
|
+
cls.storage_max_p_absorb = None
|
|
105
|
+
cls.storage_marginal_cost = None
|
|
106
|
+
cls.storage_loss = None
|
|
107
|
+
cls.storage_charging_efficiency = None
|
|
108
|
+
cls.storage_discharging_efficiency = None
|
|
109
|
+
|
|
110
|
+
# grid layout
|
|
111
|
+
cls.grid_layout = None
|
|
112
|
+
|
|
113
|
+
# shunt data, not available in every backend
|
|
114
|
+
cls.n_shunt = None
|
|
115
|
+
cls.name_shunt = None
|
|
116
|
+
cls.shunt_to_subid = None
|
|
117
|
+
|
|
118
|
+
# alarm / alert
|
|
119
|
+
cls.assistant_warning_type = None
|
|
120
|
+
|
|
121
|
+
# alarms
|
|
122
|
+
cls.dim_alarms = 0
|
|
123
|
+
cls.alarms_area_names = []
|
|
124
|
+
cls.alarms_lines_area = {}
|
|
125
|
+
cls.alarms_area_lines = []
|
|
126
|
+
|
|
127
|
+
# alerts
|
|
128
|
+
cls.dim_alerts = 0
|
|
129
|
+
cls.alertable_line_names = []
|
|
130
|
+
cls.alertable_line_ids = []
|
|
131
|
+
setattr(_DoNotUseAnywherePandaPowerBackend, "_clear_grid_dependant_class_attributes", classmethod(f))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
except ImportError as exc_:
|
|
135
|
+
# grid2op is not installed, we do not use it.
|
|
136
|
+
pass
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Copyright (c) 2024, 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 LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
|
|
8
|
+
|
|
9
|
+
__all__ = ["klu_solver_available",
|
|
10
|
+
"nicslu_solver_available",
|
|
11
|
+
"cktso_solver_available",
|
|
12
|
+
"compiled_march_native",
|
|
13
|
+
"compiled_o3_optim",
|
|
14
|
+
"version",
|
|
15
|
+
"lightsim2grid_lib"]
|
|
16
|
+
|
|
17
|
+
from lightsim2grid_cpp import klu_solver_available
|
|
18
|
+
from lightsim2grid_cpp import nicslu_solver_available
|
|
19
|
+
from lightsim2grid_cpp import cktso_solver_available
|
|
20
|
+
from lightsim2grid_cpp import compiled_march_native
|
|
21
|
+
from lightsim2grid_cpp import compiled_o3_optim
|
|
22
|
+
from lightsim2grid_cpp import version
|
|
23
|
+
from lightsim2grid_cpp import __file__ as lightsim2grid_lib
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
from lightsim2grid_cpp import nicslu_lib
|
|
27
|
+
__all__.append("nicslu_lib")
|
|
28
|
+
except ImportError :
|
|
29
|
+
# NICSLU linear solver is not available
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
from lightsim2grid_cpp import cktso_lib
|
|
34
|
+
__all__.append("cktso_lib")
|
|
35
|
+
except ImportError :
|
|
36
|
+
# CKTSO linear solver is not available
|
|
37
|
+
pass
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
# Copyright (c) 2020, 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 LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
|
|
8
|
+
|
|
9
|
+
__all__ = ["ContingencyAnalysisCPP"]
|
|
10
|
+
|
|
11
|
+
import copy
|
|
12
|
+
import numpy as np
|
|
13
|
+
from collections.abc import Iterable
|
|
14
|
+
|
|
15
|
+
from lightsim2grid.solver import SolverType
|
|
16
|
+
from lightsim2grid_cpp import ContingencyAnalysisCPP
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
from lightsim2grid.lightSimBackend import LightSimBackend
|
|
20
|
+
__all__.append("ContingencyAnalysis")
|
|
21
|
+
GRID2OP_INSTALLED = True
|
|
22
|
+
except ImportError as exc_:
|
|
23
|
+
# grid2op is not installed
|
|
24
|
+
GRID2OP_INSTALLED = False
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class __ContingencyAnalysis(object):
|
|
28
|
+
"""
|
|
29
|
+
This class allows to perform a "security analysis" from a given grid state.
|
|
30
|
+
|
|
31
|
+
For now, you cannot change the grid state, and it only computes the security analysis with
|
|
32
|
+
current flows at origin of powerlines.
|
|
33
|
+
|
|
34
|
+
Feel free to post a feature request if you want to extend it.
|
|
35
|
+
|
|
36
|
+
This class is used in 4 phases:
|
|
37
|
+
|
|
38
|
+
0) you create it from a grid2op environment (the grid topology will not be modified from this environment)
|
|
39
|
+
1) you add some contingencies to simulate
|
|
40
|
+
2) you start the simulation
|
|
41
|
+
3) you read back the results
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
Examples
|
|
45
|
+
--------
|
|
46
|
+
An example is given here
|
|
47
|
+
|
|
48
|
+
.. code-block:: python
|
|
49
|
+
|
|
50
|
+
import grid2op
|
|
51
|
+
from lightsim2grid import SecurityAnalysis
|
|
52
|
+
from lightsim2grid import LightSimBackend
|
|
53
|
+
env_name = ...
|
|
54
|
+
env = grid2op.make(env_name, backend=LightSimBackend())
|
|
55
|
+
|
|
56
|
+
0) you create
|
|
57
|
+
security_analysis = SecurityAnalysis(env)
|
|
58
|
+
|
|
59
|
+
1) you add some contingencies to simulate
|
|
60
|
+
security_analysis.add_multiple_contingencies(...) # or security_analysis.add_single_contingency(...)
|
|
61
|
+
|
|
62
|
+
2) you start the simulation (done automatically)
|
|
63
|
+
3) you read back the results
|
|
64
|
+
res_p, res_a, res_v = security_analysis.get_flows()
|
|
65
|
+
|
|
66
|
+
# in this results, then
|
|
67
|
+
# res_a[row_id] will be the flows, on all powerline corresponding to the `row_id` contingency.
|
|
68
|
+
# you can retrieve it with `security_analysis.contingency_order[row_id]`
|
|
69
|
+
|
|
70
|
+
Notes
|
|
71
|
+
------
|
|
72
|
+
|
|
73
|
+
Sometimes, the behaviour might differ from grid2op. For example, if simulating a contingency
|
|
74
|
+
leads to a non connected grid, then this function will return "Nan" for the flows and 0. for
|
|
75
|
+
the voltages.
|
|
76
|
+
|
|
77
|
+
In grid2op, it would be, in this case, 0. for the flows and 0. for the voltages.
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
STR_TYPES = (str, np.str_) # np.str deprecated in numpy 1.20 and earlier versions not supported anyway
|
|
81
|
+
|
|
82
|
+
def __init__(self, grid2op_env):
|
|
83
|
+
if not GRID2OP_INSTALLED:
|
|
84
|
+
raise RuntimeError("Impossible to use the python wrapper `ContingencyAnalysis` "
|
|
85
|
+
"when grid2op is not installed. Please fall back to the "
|
|
86
|
+
"c++ version (available in python) with:\n"
|
|
87
|
+
"\tfrom lightsim2grid.contingencyAnalysis import ContingencyAnalysisCPP\n"
|
|
88
|
+
"and refer to the appropriate documentation.")
|
|
89
|
+
from grid2op.Environment import Environment
|
|
90
|
+
self.__is_closed = False
|
|
91
|
+
if isinstance(grid2op_env, Environment):
|
|
92
|
+
if not isinstance(grid2op_env.backend, LightSimBackend):
|
|
93
|
+
raise RuntimeError("This class only works with LightSimBackend")
|
|
94
|
+
self._ls_backend = grid2op_env.backend.copy()
|
|
95
|
+
elif isinstance(grid2op_env, LightSimBackend):
|
|
96
|
+
if hasattr(grid2op_env, "_is_loaded") and not grid2op_env._is_loaded:
|
|
97
|
+
raise RuntimeError("Impossible to init a `ContingencyAnalysis` "
|
|
98
|
+
"with a backend that has not been initialized.")
|
|
99
|
+
self._ls_backend = grid2op_env.copy()
|
|
100
|
+
else:
|
|
101
|
+
raise RuntimeError("`ContingencyAnalysis` can only be created "
|
|
102
|
+
"with a grid2op `Environment` or a `LightSimBackend`")
|
|
103
|
+
self.computer = ContingencyAnalysisCPP(self._ls_backend._grid)
|
|
104
|
+
self._contingency_order = {} # key: contingency (as tuple), value: order in which it is entered
|
|
105
|
+
self._all_contingencies = []
|
|
106
|
+
self.__computed = False
|
|
107
|
+
self._vs = None
|
|
108
|
+
self._ampss = None
|
|
109
|
+
self._mws = None
|
|
110
|
+
|
|
111
|
+
self.available_solvers = self.computer.available_solvers()
|
|
112
|
+
if SolverType.KLU in self.available_solvers:
|
|
113
|
+
# use the faster KLU if available
|
|
114
|
+
self.computer.change_solver(SolverType.KLU)
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def all_contingencies(self):
|
|
118
|
+
return copy.deepcopy(self._all_contingencies)
|
|
119
|
+
|
|
120
|
+
@all_contingencies.setter
|
|
121
|
+
def all_contingencies(self, val):
|
|
122
|
+
raise RuntimeError("Impossible to add new topologies like this. Please use `add_single_contingency` "
|
|
123
|
+
"or `add_multiple_contingencies`.")
|
|
124
|
+
|
|
125
|
+
# TODO implement that !
|
|
126
|
+
def __update_grid(self, backend_act):
|
|
127
|
+
raise NotImplementedError("TODO !")
|
|
128
|
+
self.clear(with_contlist=False)
|
|
129
|
+
self._ls_backend.apply_action(backend_act)
|
|
130
|
+
# run the powerflow
|
|
131
|
+
self._ls_backend.runpf()
|
|
132
|
+
# update the computer
|
|
133
|
+
# self.computer = ContingencyAnalysisCPP(self._ls_backend._grid)
|
|
134
|
+
# self.computer.update_grid(...) # not implemented
|
|
135
|
+
|
|
136
|
+
def clear(self, with_contlist=True):
|
|
137
|
+
"""
|
|
138
|
+
Clear the list of contingencies to simulate
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
if self.__is_closed:
|
|
142
|
+
raise RuntimeError("This is closed, you cannot use it.")
|
|
143
|
+
|
|
144
|
+
self.__computed = False
|
|
145
|
+
self._vs = None
|
|
146
|
+
self._ampss = None
|
|
147
|
+
self._mws = None
|
|
148
|
+
if with_contlist:
|
|
149
|
+
self.computer.clear()
|
|
150
|
+
self._contingency_order = {}
|
|
151
|
+
self._all_contingencies = []
|
|
152
|
+
else:
|
|
153
|
+
self.computer.clear_results_only()
|
|
154
|
+
|
|
155
|
+
def _single_cont_to_li_int(self, single_cont):
|
|
156
|
+
li_disc = []
|
|
157
|
+
if isinstance(single_cont, int):
|
|
158
|
+
single_cont = [single_cont]
|
|
159
|
+
|
|
160
|
+
for stuff in single_cont:
|
|
161
|
+
if isinstance(stuff, type(self).STR_TYPES):
|
|
162
|
+
stuff = (type(self._ls_backend).name_line == stuff).nonzero()
|
|
163
|
+
stuff = stuff[0]
|
|
164
|
+
if stuff.size == 0:
|
|
165
|
+
# name is not found
|
|
166
|
+
raise RuntimeError(f"Impossible to find a powerline named \"{stuff}\" in the environment")
|
|
167
|
+
stuff = int(stuff[0])
|
|
168
|
+
else:
|
|
169
|
+
stuff = int(stuff)
|
|
170
|
+
li_disc.append(stuff)
|
|
171
|
+
return li_disc
|
|
172
|
+
|
|
173
|
+
def add_single_contingency(self, *args):
|
|
174
|
+
"""
|
|
175
|
+
This function allows to add a single contingency specified by either the powerlines names
|
|
176
|
+
(which should match env.name_line) or by their ID.
|
|
177
|
+
|
|
178
|
+
The contingency added can be a "n-1" which will simulate a single powerline disconnection
|
|
179
|
+
or a "n-k" which will simulate the disconnection of multiple powerlines.
|
|
180
|
+
|
|
181
|
+
It does not accept any keword arguments.
|
|
182
|
+
|
|
183
|
+
Examples
|
|
184
|
+
--------
|
|
185
|
+
|
|
186
|
+
.. code-block:: python
|
|
187
|
+
|
|
188
|
+
import grid2op
|
|
189
|
+
from lightsim2grid import SecurityAnalysis
|
|
190
|
+
from lightsim2grid import LightSimBackend
|
|
191
|
+
env_name = ...
|
|
192
|
+
env = grid2op.make(env_name, backend=LightSimBackend())
|
|
193
|
+
|
|
194
|
+
security_anlysis = SecurityAnalysis(env)
|
|
195
|
+
# the single (n-1) contingency "disconnect powerline 0" is added
|
|
196
|
+
security_anlysis.add_single_contingency(0)
|
|
197
|
+
|
|
198
|
+
# add the single (n-1) contingency "disconnect line 1
|
|
199
|
+
security_anlysis.add_single_contingency(env.name_line[1])
|
|
200
|
+
|
|
201
|
+
# add a single contingency that disconnect powerline 2 and 3 at the same time
|
|
202
|
+
security_anlysis.add_single_contingency(env.name_line[2], 3)
|
|
203
|
+
|
|
204
|
+
Notes
|
|
205
|
+
-----
|
|
206
|
+
If it raises an error for a given contingency, the object might be not properly initialized.
|
|
207
|
+
In this case, we recommend you to clear it (using the `clear()` method and to attempt to
|
|
208
|
+
add contingencies again.)
|
|
209
|
+
|
|
210
|
+
"""
|
|
211
|
+
if self.__is_closed:
|
|
212
|
+
raise RuntimeError("This is closed, you cannot use it.")
|
|
213
|
+
|
|
214
|
+
li_disc = self._single_cont_to_li_int(args)
|
|
215
|
+
li_disc_tup = tuple(li_disc)
|
|
216
|
+
if li_disc_tup not in self._contingency_order:
|
|
217
|
+
# this is really the first time this contingency is seen
|
|
218
|
+
try:
|
|
219
|
+
self.computer.add_nk(li_disc)
|
|
220
|
+
my_id = len(self._contingency_order)
|
|
221
|
+
self._contingency_order[li_disc_tup] = my_id
|
|
222
|
+
self._all_contingencies.append(li_disc_tup)
|
|
223
|
+
except Exception as exc_:
|
|
224
|
+
raise RuntimeError(f"Impossible to add the contingency {args}. The most likely cause "
|
|
225
|
+
f"is that you try to disconnect a powerline that is not present "
|
|
226
|
+
f"on the grid") from exc_
|
|
227
|
+
|
|
228
|
+
def add_multiple_contingencies(self, *args):
|
|
229
|
+
"""
|
|
230
|
+
This function will add multiple contingencies at the same time.
|
|
231
|
+
|
|
232
|
+
This code is equivalent to:
|
|
233
|
+
|
|
234
|
+
.. code-block:: python
|
|
235
|
+
|
|
236
|
+
for single_cont in args:
|
|
237
|
+
self.add_single_contingency(single_cont)
|
|
238
|
+
|
|
239
|
+
It does not accept any keword arguments.
|
|
240
|
+
|
|
241
|
+
Examples
|
|
242
|
+
--------
|
|
243
|
+
|
|
244
|
+
.. code-block:: python
|
|
245
|
+
|
|
246
|
+
import grid2op
|
|
247
|
+
from lightsim2grid import SecurityAnalysis
|
|
248
|
+
from lightsim2grid import LightSimBackend
|
|
249
|
+
env_name = ...
|
|
250
|
+
env = grid2op.make(env_name, backend=LightSimBackend())
|
|
251
|
+
|
|
252
|
+
security_anlysis = SecurityAnalysis(env)
|
|
253
|
+
|
|
254
|
+
# add a single contingency that disconnect powerline 2 and 3 at the same time
|
|
255
|
+
security_anlysis.add_single_contingency(env.name_line[2], 3)
|
|
256
|
+
|
|
257
|
+
# add a multiple contingencies the first one disconnect powerline 2 and
|
|
258
|
+
# and the second one disconnect powerline 3
|
|
259
|
+
security_anlysis.add_multiple_contingencies(env.name_line[2], 3)
|
|
260
|
+
"""
|
|
261
|
+
if self.__is_closed:
|
|
262
|
+
raise RuntimeError("This is closed, you cannot use it.")
|
|
263
|
+
|
|
264
|
+
for single_cont in args:
|
|
265
|
+
if isinstance(single_cont, Iterable) and not isinstance(single_cont, type(self).STR_TYPES):
|
|
266
|
+
# this is a contingency consisting in cutting multiple powerlines
|
|
267
|
+
self.add_single_contingency(*single_cont)
|
|
268
|
+
else:
|
|
269
|
+
# this is likely an int or a string representing a contingency
|
|
270
|
+
self.add_single_contingency(single_cont)
|
|
271
|
+
|
|
272
|
+
def add_all_n1_contingencies(self):
|
|
273
|
+
"""
|
|
274
|
+
This method registers as the contingencies that will be computed all the contingencies that disconnects 1 powerline
|
|
275
|
+
|
|
276
|
+
This is equivalent to:
|
|
277
|
+
|
|
278
|
+
.. code-block:: python
|
|
279
|
+
|
|
280
|
+
for single_cont_id in range(env.n_line):
|
|
281
|
+
self.add_single_contingency(single_cont_id)
|
|
282
|
+
"""
|
|
283
|
+
if self.__is_closed:
|
|
284
|
+
raise RuntimeError("This is closed, you cannot use it.")
|
|
285
|
+
|
|
286
|
+
for single_cont_id in range(type(self._ls_backend).n_line):
|
|
287
|
+
self.add_single_contingency(single_cont_id)
|
|
288
|
+
|
|
289
|
+
def get_flows(self, *args):
|
|
290
|
+
"""
|
|
291
|
+
Retrieve the flows after each contingencies has been simulated.
|
|
292
|
+
|
|
293
|
+
Each row of the resulting flow matrix will correspond to a contingency simulated in the arguments.
|
|
294
|
+
|
|
295
|
+
You can require only the result on some contingencies with the `args` argument, but in each case, all the results will
|
|
296
|
+
be computed. If you don't specify anything, the results will be returned for all contingencies (which we recommend to do)
|
|
297
|
+
|
|
298
|
+
Examples
|
|
299
|
+
--------
|
|
300
|
+
|
|
301
|
+
.. code-block:: python
|
|
302
|
+
|
|
303
|
+
import grid2op
|
|
304
|
+
from lightsim2grid import SecurityAnalysis
|
|
305
|
+
from lightsim2grid import LightSimBackend
|
|
306
|
+
env_name = ...
|
|
307
|
+
env = grid2op.make(env_name, backend=LightSimBackend())
|
|
308
|
+
|
|
309
|
+
security_analysis = SecurityAnalysis(env)
|
|
310
|
+
security_analysis.add_multiple_contingencies(...) # or security_analysis.add_single_contingency(...)
|
|
311
|
+
res_p, res_a, res_v = security_analysis.get_flows()
|
|
312
|
+
|
|
313
|
+
# in this results, then
|
|
314
|
+
# res_a[row_id] will be the flows, on all powerline corresponding to the `row_id` contingency.
|
|
315
|
+
# you can retrieve it with `security_analysis.contingency_order[row_id]`
|
|
316
|
+
"""
|
|
317
|
+
if self.__is_closed:
|
|
318
|
+
raise RuntimeError("This is closed, you cannot use it.")
|
|
319
|
+
|
|
320
|
+
all_defaults = self.computer.my_defaults()
|
|
321
|
+
if len(args) == 0:
|
|
322
|
+
# default: i consider all contingencies
|
|
323
|
+
orders_ = np.zeros(len(all_defaults), dtype=int)
|
|
324
|
+
for id_cpp, cont_ in enumerate(all_defaults):
|
|
325
|
+
tup_ = tuple(cont_)
|
|
326
|
+
orders_[self._contingency_order[tup_]] = id_cpp
|
|
327
|
+
else:
|
|
328
|
+
# a list of interesting contingencies has been provided
|
|
329
|
+
orders_ = np.zeros(len(args), dtype=int)
|
|
330
|
+
all_defaults = [tuple(cont) for cont in all_defaults]
|
|
331
|
+
for id_me, cont_ in enumerate(args):
|
|
332
|
+
cont_li = self._single_cont_to_li_int(cont_)
|
|
333
|
+
tup_ = tuple(cont_li)
|
|
334
|
+
if tup_ not in self._contingency_order:
|
|
335
|
+
raise RuntimeError(f"Contingency {cont_} is not simulated by this class. Have you called "
|
|
336
|
+
f"`add_single_contingency` or `add_multiple_contingencies` ?")
|
|
337
|
+
id_cpp = all_defaults.index(tup_)
|
|
338
|
+
orders_[id_me] = id_cpp
|
|
339
|
+
|
|
340
|
+
if not self.__computed:
|
|
341
|
+
self.compute_V()
|
|
342
|
+
self.compute_A()
|
|
343
|
+
self.compute_P()
|
|
344
|
+
|
|
345
|
+
return self._mws[orders_], self._ampss[orders_], self._vs[orders_]
|
|
346
|
+
|
|
347
|
+
def compute_V(self):
|
|
348
|
+
"""
|
|
349
|
+
This function allows to retrieve the complex voltage at each bus of the grid for each contingency.
|
|
350
|
+
|
|
351
|
+
.. warning:: Order of the results
|
|
352
|
+
|
|
353
|
+
The order in which the results are returned is NOT necessarily the order in which the contingencies have
|
|
354
|
+
been entered. Please use `get_flows()` method for easier reading back of the results
|
|
355
|
+
|
|
356
|
+
"""
|
|
357
|
+
if self.__is_closed:
|
|
358
|
+
raise RuntimeError("This is closed, you cannot use it.")
|
|
359
|
+
if self._ls_backend._debug_Vdc is not None:
|
|
360
|
+
v_init = self._ls_backend._debug_Vdc.copy()
|
|
361
|
+
else:
|
|
362
|
+
v_init = self._ls_backend.V.copy()
|
|
363
|
+
self.computer.compute(v_init,
|
|
364
|
+
self._ls_backend.max_it,
|
|
365
|
+
self._ls_backend.tol)
|
|
366
|
+
self._vs = self.computer.get_voltages().copy()
|
|
367
|
+
self.__computed = True
|
|
368
|
+
return self._vs
|
|
369
|
+
|
|
370
|
+
def compute_A(self):
|
|
371
|
+
"""
|
|
372
|
+
This function returns the current flows (in Amps, A) at the origin / high voltage side
|
|
373
|
+
|
|
374
|
+
.. warning:: Order of the results
|
|
375
|
+
|
|
376
|
+
The order in which the results are returned is NOT necessarily the order in which the contingencies have
|
|
377
|
+
been entered. Please use `get_flows()` method for easier reading back of the results !
|
|
378
|
+
|
|
379
|
+
"""
|
|
380
|
+
if self.__is_closed:
|
|
381
|
+
raise RuntimeError("This is closed, you cannot use it.")
|
|
382
|
+
|
|
383
|
+
if not self.__computed:
|
|
384
|
+
raise RuntimeError("This function can only be used if compute_V has been sucessfully called")
|
|
385
|
+
self.computer.compute_flows()
|
|
386
|
+
self._ampss = 1e3 * self.computer.get_flows()
|
|
387
|
+
return self._ampss
|
|
388
|
+
|
|
389
|
+
def compute_P(self):
|
|
390
|
+
"""
|
|
391
|
+
This function returns the active power flows (in MW) at the origin / high voltage side
|
|
392
|
+
|
|
393
|
+
.. warning:: Order of the results
|
|
394
|
+
|
|
395
|
+
The order in which the results are returned is NOT necessarily the order in which the contingencies have
|
|
396
|
+
been entered. Please use `get_flows()` method for easier reading back of the results !
|
|
397
|
+
|
|
398
|
+
"""
|
|
399
|
+
if self.__is_closed:
|
|
400
|
+
raise RuntimeError("This is closed, you cannot use it.")
|
|
401
|
+
|
|
402
|
+
if not self.__computed:
|
|
403
|
+
raise RuntimeError("This function can only be used if compute_V has been sucessfully called")
|
|
404
|
+
self.computer.compute_power_flows()
|
|
405
|
+
self._mws = self.computer.get_power_flows().copy()
|
|
406
|
+
return self._mws
|
|
407
|
+
|
|
408
|
+
def close(self):
|
|
409
|
+
"""permanently close the object"""
|
|
410
|
+
if self.__is_closed:
|
|
411
|
+
return
|
|
412
|
+
self.clear()
|
|
413
|
+
self.computer.close()
|
|
414
|
+
self._ls_backend.close()
|
|
415
|
+
self.__is_closed = True
|
|
416
|
+
|
|
417
|
+
def change_solver(self, solver_type):
|
|
418
|
+
if self.__is_closed:
|
|
419
|
+
raise RuntimeError("This is closed, you cannot use it.")
|
|
420
|
+
self.computer.change_solver(solver_type)
|
|
421
|
+
self.clear()
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
if GRID2OP_INSTALLED:
|
|
425
|
+
ContingencyAnalysis = __ContingencyAnalysis
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Copyright (c) 2020, 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 LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
|
|
8
|
+
|
|
9
|
+
__all__ = ["GeneratorContainer",
|
|
10
|
+
"GenInfo",
|
|
11
|
+
"SGenContainer",
|
|
12
|
+
"SGenInfo",
|
|
13
|
+
"LoadContainer",
|
|
14
|
+
"LoadInfo",
|
|
15
|
+
"ShuntContainer",
|
|
16
|
+
"ShuntInfo",
|
|
17
|
+
"TrafoContainer",
|
|
18
|
+
"TrafoInfo",
|
|
19
|
+
"LineContainer",
|
|
20
|
+
"LineInfo",
|
|
21
|
+
"DCLineContainer",
|
|
22
|
+
"DCLineInfo",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
from lightsim2grid_cpp import GeneratorContainer
|
|
26
|
+
from lightsim2grid_cpp import GenInfo
|
|
27
|
+
from lightsim2grid_cpp import SGenContainer
|
|
28
|
+
from lightsim2grid_cpp import SGenInfo
|
|
29
|
+
from lightsim2grid_cpp import LoadContainer
|
|
30
|
+
from lightsim2grid_cpp import LoadInfo
|
|
31
|
+
from lightsim2grid_cpp import ShuntContainer
|
|
32
|
+
from lightsim2grid_cpp import ShuntInfo
|
|
33
|
+
from lightsim2grid_cpp import TrafoContainer
|
|
34
|
+
from lightsim2grid_cpp import TrafoInfo
|
|
35
|
+
from lightsim2grid_cpp import LineContainer
|
|
36
|
+
from lightsim2grid_cpp import LineInfo
|
|
37
|
+
from lightsim2grid_cpp import DCLineContainer
|
|
38
|
+
from lightsim2grid_cpp import DCLineInfo
|