diff --git a/.circleci/config.yml b/.circleci/config.yml
index f62aefea..cc6fe65e 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -5,6 +5,9 @@ orbs:
executors:
+ gcc_13:
+ docker:
+ - image: gcc:13
gcc_12:
docker:
- image: gcc:12
@@ -20,6 +23,18 @@ executors:
gcc_8:
docker:
- image: gcc:8
+ # clang18:
+ # docker:
+ # - image: silkeh/clang:18
+ clang17:
+ docker:
+ - image: silkeh/clang:17
+ clang16:
+ docker:
+ - image: silkeh/clang:16
+ clang15:
+ docker:
+ - image: silkeh/clang:15
clang14:
docker:
- image: silkeh/clang:14
@@ -40,17 +55,36 @@ executors:
- image: silkeh/clang:9 # no c++ 11, does not work
jobs:
+ compile_gcc12:
+ executor: gcc_12
+ resource_class: small
+ steps:
+ - checkout
+ - run: apt-get update && apt-get install python3-full python3-dev python3-pip python3-virtualenv git -y
+ - run: python3 -m virtualenv venv_test
+ - run:
+ command: |
+ source venv_test/bin/activate
+ pip install --upgrade pip setuptools wheel
+ pip install -U grid2op
+ pip install -U pybind11
+ git submodule init
+ git submodule update
+ make
+ CC=gcc python setup.py build
+ python -m pip install -U .
compile_gcc11:
executor: gcc_11
resource_class: small
steps:
- checkout
- - run: apt-get update && apt-get install python3-pip -y
+ - run: apt-get update && apt-get install python3-pip python3-full -y
- run: python3 -m pip install virtualenv
- run: python3 -m virtualenv venv_test
- run:
command: |
source venv_test/bin/activate
+ pip install --upgrade pip setuptools wheel
pip install -U grid2op
pip install -U pybind11
git submodule init
@@ -63,12 +97,13 @@ jobs:
resource_class: small
steps:
- checkout
- - run: apt-get update && apt-get install python3-pip -y
+ - run: apt-get update && apt-get install python3-pip python3-full -y
- run: python3 -m pip install virtualenv
- run: python3 -m virtualenv venv_test
- run:
command: |
source venv_test/bin/activate
+ pip install --upgrade pip setuptools wheel
pip install -U grid2op
pip install -U pybind11
git submodule init
@@ -81,12 +116,13 @@ jobs:
resource_class: small
steps:
- checkout
- - run: apt-get update && apt-get install python3-pip -y
+ - run: apt-get update && apt-get install python3-pip python3-full -y
- run: python3 -m pip install virtualenv
- run: python3 -m virtualenv venv_test
- run:
command: |
source venv_test/bin/activate
+ pip install --upgrade pip setuptools wheel
pip install -U grid2op
pip install -U pybind11
git submodule init
@@ -105,6 +141,7 @@ jobs:
- run:
command: |
source venv_test/bin/activate
+ pip install --upgrade pip setuptools wheel
pip install -U grid2op
pip install -U pybind11
git submodule init
@@ -112,18 +149,19 @@ jobs:
make
CC=gcc python setup.py build
python -m pip install -U .
- compile_gcc12:
- executor: gcc_12
+ compile_gcc13:
+ executor: gcc_13
resource_class: small
steps:
- checkout
- - run: apt-get update && apt-get install python3-pip python3-virtualenv -y
+ - run: apt-get update && apt-get install python3-full python3-dev python3-pip python3-virtualenv git -y
# - run: python3 -m pip install virtualenv
- run: python3 -m virtualenv venv_test
- run:
name: "Install grid2op from source"
command: |
source venv_test/bin/activate
+ pip install --upgrade pip setuptools wheel
git clone https://github.com/rte-france/grid2op.git _grid2op
pip install -e _grid2op
- run:
@@ -147,12 +185,13 @@ jobs:
resource_class: small
steps:
- checkout
- - run: apt-get update && apt-get install python3-pip -y
+ - run: apt-get update && apt-get install python3-pip python3-full git -y
- run: python3 -m pip install virtualenv
- run: python3 -m virtualenv venv_test
- run:
command: |
source venv_test/bin/activate
+ pip install --upgrade pip setuptools wheel
pip install -U pybind11
git submodule init
git submodule update
@@ -164,7 +203,7 @@ jobs:
resource_class: small
steps:
- checkout
- - run: apt-get update && apt-get install python3-pip git -y
+ - run: apt-get update && apt-get install python3-pip python3-full git -y
- run:
command: |
git submodule init
@@ -175,6 +214,7 @@ jobs:
- run:
command: |
source venv_test/bin/activate
+ pip install --upgrade pip setuptools wheel
pip install -U pybind11
CC=clang python setup.py build
python -m pip install .
@@ -183,12 +223,13 @@ jobs:
resource_class: small
steps:
- checkout
- - run: apt-get update && apt-get install python3-pip git -y
+ - run: apt-get update && apt-get install python3-pip python3-full git -y
- run: python3 -m pip install virtualenv
- run: python3 -m virtualenv venv_test
- run:
command: |
source venv_test/bin/activate
+ pip install --upgrade pip setuptools wheel
pip install -U grid2op
pip install -U pybind11
git submodule init
@@ -201,12 +242,13 @@ jobs:
resource_class: small
steps:
- checkout
- - run: apt-get update && apt-get install python3-pip git -y
+ - run: apt-get update && apt-get install python3-pip python3-full git -y
- run: python3 -m pip install virtualenv
- run: python3 -m virtualenv venv_test
- run:
command: |
source venv_test/bin/activate
+ pip install --upgrade pip setuptools wheel
pip install -U grid2op
pip install -U pybind11
git submodule init
@@ -219,12 +261,13 @@ jobs:
resource_class: small
steps:
- checkout
- - run: apt-get update && apt-get install python3-pip git -y
+ - run: apt-get update && apt-get install python3-pip python3-full git -y
- run: python3 -m pip install virtualenv
- run: python3 -m virtualenv venv_test
- run:
command: |
source venv_test/bin/activate
+ pip install --upgrade pip setuptools wheel
pip install -U grid2op
pip install -U pybind11
git submodule init
@@ -237,13 +280,69 @@ jobs:
resource_class: small
steps:
- checkout
- - run: apt-get update && apt-get install python3-pip git -y
+ - run: apt-get update && apt-get install python3-pip python3-full git -y
+ - run: python3 -m pip install virtualenv
+ - run: python3 -m virtualenv venv_test
+ - run:
+ command: |
+ source venv_test/bin/activate
+ pip install --upgrade pip setuptools wheel
+ pip install -U grid2op
+ pip install -U pybind11
+ git submodule init
+ git submodule update
+ make
+ CC=clang python setup.py build
+ CC=clang python -m pip install -U .
+ compile_clang15:
+ executor: clang15
+ resource_class: small
+ steps:
+ - checkout
+ - run: apt-get update && apt-get install python3-pip python3-full git -y
- run: python3 -m pip install virtualenv
- run: python3 -m virtualenv venv_test
+ - run:
+ command: |
+ source venv_test/bin/activate
+ pip install --upgrade pip setuptools wheel
+ pip install -U grid2op
+ pip install -U pybind11
+ git submodule init
+ git submodule update
+ make
+ CC=clang python setup.py build
+ CC=clang python -m pip install -U .
+ compile_clang16:
+ executor: clang16
+ resource_class: small
+ steps:
+ - checkout
+ - run: apt-get update && apt-get install python3-full python3-dev python3-pip python3-virtualenv git -y
+ - run: python3 -m virtualenv venv_test
+ - run:
+ command: |
+ source venv_test/bin/activate
+ pip install --upgrade pip setuptools wheel
+ pip install -U grid2op
+ pip install -U pybind11
+ git submodule init
+ git submodule update
+ make
+ CC=clang python setup.py build
+ CC=clang python -m pip install -U .
+ compile_clang17:
+ executor: clang17
+ resource_class: small
+ steps:
+ - checkout
+ - run: apt-get update && apt-get install python3-full python3-dev python3-pip python3-virtualenv git -y
+ - run: python3 -m virtualenv venv_test
- run:
name: "Install grid2op from source"
command: |
source venv_test/bin/activate
+ pip install --upgrade pip setuptools wheel
git clone https://github.com/rte-france/grid2op.git _grid2op
pip install -e _grid2op
- run:
@@ -301,17 +400,20 @@ jobs:
cd lightsim2grid\tests
python -m unittest discover -v
-
workflows:
version: 2.1
compile:
jobs:
- compile_gcc8
- - compile_gcc10
- - compile_gcc11
+ # - compile_gcc10
+ # - compile_gcc11
- compile_gcc12
+ - compile_gcc13
# - compile_clang10 # does not work I don't know why, too lazy to check
- compile_clang11
- - compile_clang13
- - compile_clang14
+ # - compile_clang13
+ # - compile_clang14
+ # - compile_clang15
+ - compile_clang16
+ - compile_clang17
- compile_windows
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index cc48a5a5..8ce98074 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -195,12 +195,16 @@ jobs:
- name: Check package can be imported
run: |
+ mkdir tmp_for_import_checking
+ cd tmp_for_import_checking
python -c "import lightsim2grid"
python -c "from lightsim2grid import *"
python -c "from lightsim2grid.newtonpf import newtonpf"
+ cd ..
- name: Check LightSimBackend can be imported
run: |
+ cd tmp_for_import_checking
python -m pip install grid2op
python -c "from lightsim2grid import LightSimBackend"
python -c "from lightsim2grid import LightSimBackend; import grid2op; env = grid2op.make('l2rpn_case14_sandbox', test=True, backend=LightSimBackend())"
@@ -253,16 +257,20 @@ jobs:
- name: Check package can be imported
run: |
+ mkdir tmp_for_import_checking
+ cd tmp_for_import_checking
python -c "import lightsim2grid"
python -c "from lightsim2grid import *"
python -c "from lightsim2grid.newtonpf import newtonpf"
+ cd ..
- name: Check LightSimBackend can be imported
run: |
+ cd tmp_for_import_checking
python -m pip install grid2op
python -c "from lightsim2grid import LightSimBackend"
python -c "from lightsim2grid import LightSimBackend; import grid2op; env = grid2op.make('l2rpn_case14_sandbox', test=True, backend=LightSimBackend())"
-
+
- name: Upload wheel
uses: actions/upload-artifact@v3
with:
diff --git a/.gitignore b/.gitignore
index fac4b540..5272312b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -269,3 +269,14 @@ test_issue_58.py
test_issue_58_old.py
lightsim2grid/gridmodel/pypowsybl.ipynb
lightsim2grid/tests/test.mat
+output_test.txt
+req_test.txt
+test.txt
+test_output.txt
+test_pypower_fdpf.py
+lightsim2grid/tests/_grid2op_for_test/
+bug_sparselu
+bug_sparselu_eigen.cpp
+test_segfault.sh
+nohup.out
+test_rte/
\ No newline at end of file
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 60a23df5..63f0aab9 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -5,6 +5,7 @@ Change Log
--------
- [refacto] have a structure in cpp for the buses
- [refacto] have the id_grid_to_solver and id_solver_to_grid etc. directly in the solver and NOT in the gridmodel.
+- [refacto] put some method in the DataGeneric as well as some attribute (_status for example)
- support 3w trafo (as modeled in pandapower)
- improve speed by not performing internal checks
(keep check for boundaries and all for python API instead) [see `TODO DEBUG MODE` in c++ code]
@@ -13,12 +14,65 @@ Change Log
- a mode to do both `Computer` and `SecurityAnalysisCPP`
- use the "multi slack hack" (see issue #50) for SecurityAnalysis or Computer for example
- code `helm` powerflow method
-- possibility to read CGMES files
-- possibility to read XIIDM files
- interface with gridpack (to enforce q limits for example)
- maybe have a look at suitesparse "sliplu" tools ?
- easier building (get rid of the "make" part)
+[0.8.0] 2023-03-18
+--------------------
+- [BREAKING] now able to retrieve `dcSbus` with a dedicated method (and not with the old `get_Sbus`).
+ If you previously used `gridmodel.get_Sbus()` to retrieve the Sbus used for DC powerflow, please use
+ `gridmodel.get_dcSbus()` instead.
+- [DEPRECATED] in the cpp class: the old `SecurityAnalysisCPP` has been renamed `ContingencyAnalysisCPP`
+ (you should not import it, but it you do you can `from lightsim2grid.securityAnalysis import ContingencyAnalysisCPP` now)
+- [DEPRECATED] in the cpp class: the old `Computers` has been renamed `TimeSerieCPP`
+ (you should not import it, but it you do you can `from lightsim2grid.time_serie import TimeSerieCPP` now)
+- [FIXED] now voltage is properly set to 0. when shunts are disconnected
+- [FIXED] now voltage is properly set to 0. when storage units are disconnected
+- [FIXED] a bug where non connected grid were not spotted in DC
+- [FIXED] a bug when trying to set the slack for a non existing genererator
+- [FIXED] a bug in init from pypowsybl when some object were disconnected. It raises
+ an error (because they are not connected to a bus): now this function properly handles
+ these cases.
+- [FIXED] a bug leading to not propagate correctly the "compute_results" flag when the
+ environment was copied (for example)
+- [FIXED] a bug where copying a lightsim2grid `GridModel` did not fully copy it
+- [FIXED] a bug in the "topo_vect" comprehension cpp side (sometimes some buses
+ might not be activated / deactivated correctly)
+- [FIXED] a bug when reading a grid initialize from pypowsybl (trafo names where put in place
+ of shunt names)
+- [FIXED] read the docs was broken
+- [FIXED] a bug when reading a grid from pandapower for multiple slacks when slack
+ are given by the "ext_grid" information.
+- [FIXED] a bug in "gridmodel.assign_slack_to_most_connected()" that could throw an error if a
+ generator with "target_p" == 0. was connected to the most connected bus on the grid
+- [FIXED] backward compat with "future" grid2op version with a
+ better way to copy `LightSimBackend`
+- [ADDED] sets of methods to extract the main component of a grid and perform powerflow only on this
+ one.
+- [ADDED] possibility to set / retrieve the names of each elements of the grid.
+- [ADDED] embed in the generator models the "non pv" behaviour. (TODO need to be able to change Q from python side)
+- [ADDED] computation of PTPF (Power Transfer Distribution Factor) is now possible
+- [ADDED] (not tested) support for more than 2 busbars per substation
+- [ADDED] a timer to get the time spent in the gridmodel for the powerflow (env.backend.timer_gridmodel_xx_pf)
+ which also include the time
+- [ADDED] support for more than 2 busbars per substation (requires grid2op >= 1.10.0)
+- [ADDED] possibility to retrieve the bus id of the original iidm when initializing from pypowsybl
+ (`return_sub_id` kwargs). This is a "beta" feature and will be adressed in a better way
+ in a near future.
+- [ADDED] possibility to continue the grid2op 'step' when the solver converges but a load or a
+ generator is disconnected from the grid.
+- [IMPROVED] now performing the new grid2op `create_test_suite`
+- [IMPROVED] now lightsim2grid properly throw `BackendError`
+- [IMPROVED] clean ce cpp side by refactoring: making clearer the difference (linear) solver
+ vs powerflow algorithm and move same type of files in the same directory. This change
+ does not really affect python side at the moment (but will in future versions)
+- [IMPROVED] CI to test on gcc 13 and clang 18 (latest versions to date)
+- [IMPROVED] computation speed: grid is not read another time in some cases.
+ For example, if load and generators do not change, then Sbus is not
+ recomputed. Likewise, if the topology does not change, then the Ybus
+ is not recomputed either see https://github.com/BDonnot/lightsim2grid/issues/72
+
[0.7.5.post1] 2024-03-14
-------------------------
- [FIXED] backward compat with "future" grid2op version with a
diff --git a/README.md b/README.md
index 303ef73f..cf1c078d 100644
--- a/README.md
+++ b/README.md
@@ -268,9 +268,10 @@ cd ..
Some tests are performed automatically on standard platform each time modifications are made in the lightsim2grid code.
-These tests include, for now, compilation on gcc (version 8, 10, 11 and 12) and clang (version 11, 13 and 14).
+These tests include, for now, compilation on gcc (version 8, 12 and 13) and clang (version 11, 16 and 17).
-**NB** Intermediate versions of clang and gcc (*eg* gcc 9 or clang 12) are not tested regularly, but lightsim2grid used to work on these. We suppose that if it works on *eg* clang 10 and clang 14 then it compiles also on all intermediate versions.
+**NB** Intermediate versions of clang and gcc (*eg* gcc 9 or clang 12) are not tested regularly, but lightsim2grid used to work on these.
+We suppose that if it works on *eg* clang 10 and clang 14 then it compiles also on all intermediate versions.
**NB** Package might work (we never tested it) on earlier version of these compilers.
The only "real" requirement for lightsim2grid is to have a compiler supporting c++11
diff --git a/benchmarks/benchmark_grid_size.py b/benchmarks/benchmark_grid_size.py
index b7a15adb..a2e26683 100644
--- a/benchmarks/benchmark_grid_size.py
+++ b/benchmarks/benchmark_grid_size.py
@@ -7,13 +7,21 @@
# This file is part of LightSim2grid, LightSim2grid a implements a c++ backend targeting the Grid2Op platform.
import warnings
+import copy
import pandapower as pp
-import numpy as np
+import numpy as np
+import hashlib
from scipy.interpolate import interp1d
import matplotlib.pyplot as plt
from grid2op import make, Parameters
from grid2op.Chronics import FromNPY
-from lightsim2grid import LightSimBackend, TimeSerie, SecurityAnalysis
+from grid2op.Backend import PandaPowerBackend
+from lightsim2grid import LightSimBackend, TimeSerie
+try:
+ from lightsim2grid import ContingencyAnalysis
+except ImportError:
+ from lightsim2grid import SecurityAnalysis as ContingencyAnalysis
+
from tqdm import tqdm
import os
from utils_benchmark import print_configuration, get_env_name_displayed
@@ -28,6 +36,8 @@
VERBOSE = False
MAKE_PLOT = False
+WITH_PP = False
+DEBUG = False
case_names = [
"case14.json",
@@ -64,7 +74,28 @@ def make_grid2op_env(pp_case, casse_name, load_p, load_q, gen_p, sgen_p):
)
return env_lightsim
-def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init):
+
+def make_grid2op_env_pp(pp_case, casse_name, load_p, load_q, gen_p, sgen_p):
+ param = Parameters.Parameters()
+ param.init_from_dict({"NO_OVERFLOW_DISCONNECTION": True})
+
+ with warnings.catch_warnings():
+ warnings.filterwarnings("ignore")
+ env_pp = make("blank",
+ param=param, test=True,
+ backend=PandaPowerBackend(lightsim2grid=False),
+ chronics_class=FromNPY,
+ data_feeding_kwargs={"load_p": load_p,
+ "load_q": load_q,
+ "prod_p": gen_p
+ },
+ grid_path=case_name,
+ _add_to_name=f"{case_name}",
+ )
+ return env_pp
+
+
+def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init, prng):
# scale loads
# use some French time series data for loads
@@ -132,31 +163,36 @@ def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init):
vals = np.array(vals) * coeffs["month"]["oct"] * coeffs["day"]["mon"]
x_interp = 12 * np.arange(len(vals))
coeffs = interp1d(x=x_interp, y=vals, kind="cubic")
- all_vals = coeffs(x_final)
+ all_vals = coeffs(x_final).reshape(-1, 1)
+ if DEBUG:
+ all_vals[:] = 1
# compute the "smooth" loads matrix
- load_p_smooth = all_vals.reshape(-1, 1) * load_p_init.reshape(1, -1)
- load_q_smooth = all_vals.reshape(-1, 1) * load_q_init.reshape(1, -1)
+ load_p_smooth = all_vals * load_p_init.reshape(1, -1)
+ load_q_smooth = all_vals * load_q_init.reshape(1, -1)
# add a bit of noise to it to get the "final" loads matrix
- load_p = load_p_smooth * np.random.lognormal(mean=0., sigma=0.003, size=load_p_smooth.shape)
- load_q = load_q_smooth * np.random.lognormal(mean=0., sigma=0.003, size=load_q_smooth.shape)
-
+ load_p = load_p_smooth * prng.lognormal(mean=0., sigma=0.003, size=load_p_smooth.shape)
+ load_q = load_q_smooth * prng.lognormal(mean=0., sigma=0.003, size=load_q_smooth.shape)
+ if DEBUG:
+ load_p[:] = load_p_smooth
+ load_q[:] = load_q_smooth
+
# scale generators accordingly
gen_p = load_p.sum(axis=1).reshape(-1, 1) / load_p_init.sum() * gen_p_init.reshape(1, -1)
sgen_p = load_p.sum(axis=1).reshape(-1, 1) / load_p_init.sum() * sgen_p_init.reshape(1, -1)
-
return load_p, load_q, gen_p, sgen_p
if __name__ == "__main__":
- np.random.seed(42)
-
+ prng = np.random.default_rng(42)
case_names_displayed = [get_env_name_displayed(el) for el in case_names]
- g2op_times = []
+ solver_preproc_solver_time = []
g2op_speeds = []
g2op_sizes = []
g2op_step_time = []
+ ls_solver_time = []
+ ls_gridmodel_time = []
ts_times = []
ts_speeds = []
@@ -190,8 +226,14 @@ def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init):
res_unit = "ms"
# simulate the data
- load_p, load_q, gen_p, sgen_p = get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init)
-
+ load_p, load_q, gen_p, sgen_p = get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init, prng)
+ if DEBUG:
+ hash_fun = hashlib.blake2b(digest_size=16)
+ hash_fun.update(load_p.tobytes())
+ hash_fun.update(load_q.tobytes())
+ hash_fun.update(gen_p.tobytes())
+ hash_fun.update(sgen_p.tobytes())
+ print(hash_fun.hexdigest())
# create the grid2op env
nb_ts = gen_p.shape[0]
# add slack !
@@ -199,15 +241,33 @@ def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init):
if "res_ext_grid" in case:
slack_gens += np.tile(case.res_ext_grid["p_mw"].values.reshape(1,-1), (nb_ts, 1))
gen_p_g2op = np.concatenate((gen_p, slack_gens), axis=1)
- # get the env
+ # get the env
+ if WITH_PP:
+ env_pp = make_grid2op_env_pp(case,
+ case_name,
+ load_p,
+ load_q,
+ gen_p_g2op,
+ sgen_p)
+ _ = env_pp.reset()
+ done = False
+ nb_step_pp = 0
+ changed_sgen = case.sgen["in_service"].values
+ while not done:
+ # hack for static gen...
+ env_pp.backend._grid.sgen["p_mw"] = sgen_p[nb_step_pp, :]
+ obs, reward, done, info = env_pp.step(env_pp.action_space())
+ nb_step_pp += 1
+ if nb_step_pp != nb_ts:
+ warnings.warn("Divergence even with pandapower !")
+ print("Pandapower stops, lightsim starts")
+
env_lightsim = make_grid2op_env(case,
case_name,
load_p,
load_q,
gen_p_g2op,
sgen_p)
-
-
# Perform the computation using grid2op
_ = env_lightsim.reset()
done = False
@@ -215,25 +275,34 @@ def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init):
changed_sgen = case.sgen["in_service"].values
while not done:
# hack for static gen...
- env_lightsim.backend._grid.update_sgens_p(changed_sgen,
- sgen_p[nb_step, changed_sgen].astype(np.float32))
+ changed_sgen = copy.deepcopy(case.sgen["in_service"].values)
+ this_sgen = sgen_p[nb_step, :].astype(np.float32)
+ # this_sgen = sgen_p_init[changed_sgen].astype(np.float32)
+ env_lightsim.backend._grid.update_sgens_p(changed_sgen, this_sgen)
obs, reward, done, info = env_lightsim.step(env_lightsim.action_space())
nb_step += 1
# NB lightsim2grid does not handle "static gen" because I cannot set "p" in gen in grid2op
# so results will vary between TimeSeries and grid2op !
-
+ # env_lightsim.backend._grid.tell_solver_need_reset()
+ # env_lightsim.backend._grid.dc_pf(env_lightsim.backend.V, 1, 1e-7)
+ # env_lightsim.backend._grid.get_bus_status()
if nb_step != nb_ts:
warnings.warn(f"only able to make {nb_step} (out of {nb_ts}) for {case_name} in grid2op. Results will not be availabe for grid2op step")
- g2op_times.append(None)
+ solver_preproc_solver_time.append(None)
g2op_speeds.append(None)
g2op_step_time.append(None)
+ ls_solver_time.append(None)
+ ls_gridmodel_time.append(None)
g2op_sizes.append(env_lightsim.n_sub)
else:
total_time = env_lightsim.backend._timer_preproc + env_lightsim.backend._timer_solver # + env_lightsim.backend._timer_postproc
- g2op_times.append(total_time)
+ # total_time = env_lightsim._time_step
+ solver_preproc_solver_time.append(total_time)
g2op_speeds.append(1.0 * nb_step / total_time)
g2op_step_time.append(1.0 * env_lightsim._time_step / nb_step)
- g2op_sizes.append(env_lightsim.n_sub)
+ ls_solver_time.append(env_lightsim.backend.comp_time)
+ ls_gridmodel_time.append(env_lightsim.backend.timer_gridmodel_xx_pf)
+ g2op_sizes.append(env_lightsim.n_sub)
# Perform the computation using TimeSerie
env_lightsim.reset()
@@ -250,7 +319,7 @@ def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init):
env_lightsim.backend.tol)
time_serie._TimeSerie__computed = True
a_or = time_serie.compute_A()
- assert status, f"some powerflow diverge for {case_name}: {computer.nb_solved()} "
+ assert status, f"some powerflow diverge for Time Series for {case_name}: {computer.nb_solved()} "
if VERBOSE:
# print detailed results if needed
@@ -274,7 +343,7 @@ def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init):
# Perform a securtiy analysis (up to 1000 contingencies)
env_lightsim.reset()
- sa = SecurityAnalysis(env_lightsim)
+ sa = ContingencyAnalysis(env_lightsim)
for i in range(env_lightsim.n_line):
sa.add_single_contingency(i)
if i >= 1000:
@@ -297,11 +366,24 @@ def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init):
print("Results using grid2op.steps (288 consecutive steps, only measuring 'dc pf [init] + ac pf')")
tab_g2op = []
for i, nm_ in enumerate(case_names_displayed):
- tab_g2op.append((nm_, ts_sizes[i], 1000. / g2op_speeds[i] if g2op_speeds[i] else None, g2op_speeds[i],
- 1000. * g2op_step_time[i] if g2op_step_time[i] else None))
+ tab_g2op.append((nm_,
+ ts_sizes[i],
+ 1000. * g2op_step_time[i] if g2op_step_time[i] else None,
+ 1000. / g2op_speeds[i] if g2op_speeds[i] else None,
+ g2op_speeds[i],
+ 1000. * ls_gridmodel_time[i] / nb_step if ls_gridmodel_time[i] else None,
+ 1000. * ls_solver_time[i] / nb_step if ls_solver_time[i] else None,
+ ))
if TABULATE_AVAIL:
res_use_with_grid2op_2 = tabulate(tab_g2op,
- headers=["grid", "size (nb bus)", "time (ms / pf)", "speed (pf / s)", "avg step duration (ms)"],
+ headers=["grid",
+ "size (nb bus)",
+ "avg step duration (ms)",
+ "time [DC + AC] (ms / pf)",
+ "speed (pf / s)",
+ "time in 'gridmodel' (ms / pf)",
+ "time in 'pf algo' (ms / pf)",
+ ],
tablefmt="rst")
print(res_use_with_grid2op_2)
else:
@@ -337,7 +419,7 @@ def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init):
if MAKE_PLOT:
# make the plot summarizing all results
- plt.plot(g2op_sizes, g2op_times, linestyle='solid', marker='+', markersize=8)
+ plt.plot(g2op_sizes, solver_preproc_solver_time, linestyle='solid', marker='+', markersize=8)
plt.xlabel("Size (number of substation)")
plt.ylabel("Time taken (s)")
plt.title(f"Time to compute {g2op_sizes[0]} powerflows using Grid2Op.step (dc pf [init] + ac pf)")
@@ -349,6 +431,20 @@ def get_loads_gens(load_p_init, load_q_init, gen_p_init, sgen_p_init):
plt.title(f"Computation speed using Grid2Op.step (dc pf [init] + ac pf)")
plt.yscale("log")
plt.show()
+
+ plt.plot(g2op_sizes, ls_solver_time, linestyle='solid', marker='+', markersize=8)
+ plt.xlabel("Size (number of substation)")
+ plt.ylabel("Speed (solver time)")
+ plt.title(f"Computation speed for solving the powerflow only")
+ plt.yscale("log")
+ plt.show()
+
+ plt.plot(g2op_sizes, ls_gridmodel_time, linestyle='solid', marker='+', markersize=8)
+ plt.xlabel("Size (number of substation)")
+ plt.ylabel("Speed (solver time)")
+ plt.title(f"Computation speed for solving the powerflow only")
+ plt.yscale("log")
+ plt.show()
# make the plot summarizing all results
plt.plot(ts_sizes, ts_times, linestyle='solid', marker='+', markersize=8)
diff --git a/benchmarks/benchmark_solvers.py b/benchmarks/benchmark_solvers.py
index 7281e06e..55dfa24a 100644
--- a/benchmarks/benchmark_solvers.py
+++ b/benchmarks/benchmark_solvers.py
@@ -43,10 +43,10 @@
lightsim2grid.SolverType.SparseLU: "NR (SLU)",
lightsim2grid.SolverType.KLU: "NR (KLU)",
lightsim2grid.SolverType.NICSLU: "NR (NICSLU *)",
+ lightsim2grid.SolverType.CKTSO: "NR (CKTSO *)",
lightsim2grid.SolverType.SparseLUSingleSlack: "NR single (SLU)",
lightsim2grid.SolverType.KLUSingleSlack: "NR single (KLU)",
lightsim2grid.SolverType.NICSLUSingleSlack: "NR single (NICSLU *)",
- lightsim2grid.SolverType.CKTSO: "NR (CKTSO *)",
lightsim2grid.SolverType.CKTSOSingleSlack: "NR single (CKTSO *)",
lightsim2grid.SolverType.FDPF_XB_SparseLU: "FDPF XB (SLU)",
lightsim2grid.SolverType.FDPF_BX_SparseLU: "FDPF BX (SLU)",
diff --git a/docs/benchmarks.rst b/docs/benchmarks.rst
index a49c428a..4ad4aa2c 100644
--- a/docs/benchmarks.rst
+++ b/docs/benchmarks.rst
@@ -39,7 +39,8 @@ To run the benchmark `cd` in the [benchmark](./benchmarks) folder and type:
(we remind that these simulations correspond to simulation on one core of the CPU. Of course it is possible to
make use of all the available cores, which would increase the number of steps that can be performed)
-We compare up to 19 different solvers:
+We compare up to 19 different "solvers" (combination of "linear solver used" (*eg* Eigen, KLU, CKTSO, NICSLU)
+and powerflow algorithm (*eg* "Newton Raphson", or "Fast Decoupled")):
- **PP**: PandaPowerBackend (default grid2op backend) which is the reference in our benchmarks (uses the numba
acceleration). It is our reference solver.
@@ -102,16 +103,24 @@ Computation time
In this first subsection we compare the computation times:
- **grid2op speed** from a grid2op point of view
- (this include the time to compute the powerflow, plus the time to modify the powergrid plus the
- time to read back the data once the powerflow has run plus the time to update the environment and
- the observations etc.). It is reported in "iteration per second" (`it/s`) and represents the number of grid2op "step"
+ (this include the time to compute the powerflow, plus the time to modify
+ the powergrid plus the
+ time to read back the data once the powerflow has run plus the time to update
+ the environment and
+ the observations etc.). It is reported in "iteration per second" (`it/s`) and
+ represents the number of grid2op "step"
that can be computed per second.
-- **grid2op 'backend.runpf' time** corresponds to the time the solver take to perform a powerflow
- as seen from grid2op (counting the resolution time and some time to check the validity of the results but
- not the time to update the grid nor the grid2op environment), for lightsim2grid it includes the time to read back the data
+- **grid2op 'backend.runpf' time** corresponds to the time the solver take
+ to perform a powerflow
+ as seen from grid2op (counting the resolution time and some time to check
+ the validity of the results but
+ not the time to update the grid nor the grid2op environment), for lightsim2grid
+ it includes the time to read back the data
from c++ to python. It is reported in milli seconds (ms).
-- **solver powerflow time** corresponds only to the time spent in the solver itself. It does not take into
- account any of the checking, nor the transfer of the data python side etc. It is reported in milli seconds (ms) as well.
+- **solver powerflow time** corresponds only to the time spent in the solver
+ itself. It does not take into
+ account any of the checking, nor the transfer of the data python side etc.
+ It is reported in milli seconds (ms) as well.
There are two major differences between **grid2op 'backend.runpf' time** and **solver powerflow time**. In **grid2op 'backend.runpf' time**
the time to initialize the solver (usually with the DC approximation) is counted (it is not in **solver powerflow time**). Secondly,
diff --git a/docs/conf.py b/docs/conf.py
index 796a1335..c50cbef0 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -22,8 +22,8 @@
author = 'Benjamin DONNOT'
# The full version, including alpha/beta/rc tags
-release = "0.7.5.dev0.post1"
-version = '0.7'
+release = "0.8.0"
+version = '0.8'
# -- General configuration ---------------------------------------------------
diff --git a/examples/computers_with_grid2op.py b/examples/computers_with_grid2op.py
index fbb930a3..ceff6cf7 100644
--- a/examples/computers_with_grid2op.py
+++ b/examples/computers_with_grid2op.py
@@ -7,7 +7,7 @@
# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
# ADVANCED USAGE
-# This files explains how to use the Computers cpp class, for easier use
+# This files explains how to use the TimeSeriesCPP cpp class, for easier use
# please consult the documentation of TimeSeries or the
# time_serie.py file !
@@ -16,7 +16,7 @@
import warnings
import numpy as np
from lightsim2grid import LightSimBackend
-from lightsim2grid.timeSerie import Computers
+from lightsim2grid.timeSerie import TimeSeriesCPP
env_name = "l2rpn_neurips_2020_track2"
test = True
@@ -36,7 +36,7 @@
nb_sim = prod_p.shape[0]
# now perform the computation
-computer = Computers(grid)
+computer = TimeSeriesCPP(grid)
# print("start the computation")
status = computer.compute_Vs(prod_p,
np.zeros((nb_sim, 0)), # no static generators for now !
diff --git a/examples/computers_with_grid2op_multithreading.py b/examples/computers_with_grid2op_multithreading.py
index ef9d80b8..8307056b 100644
--- a/examples/computers_with_grid2op_multithreading.py
+++ b/examples/computers_with_grid2op_multithreading.py
@@ -7,7 +7,7 @@
# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
# ADVANCED USAGE
-# This files explains how to use the Computers cpp class, for easier use
+# This files explains how to use the TimeSeriesCPP cpp class, for easier use
# please consult the documentation of TimeSeries or the
# time_serie.py file !
@@ -22,7 +22,7 @@
import grid2op
from grid2op.Parameters import Parameters
from lightsim2grid import LightSimBackend
-from lightsim2grid.timeSerie import Computers
+from lightsim2grid.timeSerie import TimeSeriesCPP
NB_THREAD = 4
ENV_NAME = "l2rpn_neurips_2020_track2_small"
@@ -51,7 +51,7 @@ def get_injs(env):
def get_flows(grid, Vinit, prod_p, load_p, load_q, max_it=10, tol=1e-8):
# now perform the computation
- computer = Computers(grid)
+ computer = TimeSeriesCPP(grid)
# print("start the computation")
status = computer.compute_Vs(prod_p,
np.zeros((prod_p.shape[0], 0)), # no static generators for now !
diff --git a/examples/security_analysis.py b/examples/security_analysis.py
index dbdd5e67..ff137fa7 100644
--- a/examples/security_analysis.py
+++ b/examples/security_analysis.py
@@ -14,7 +14,7 @@
from grid2op.Action import BaseAction
from grid2op.Chronics import ChangeNothing
import warnings
-from lightsim2grid import LightSimBackend, SecurityAnalysis
+from lightsim2grid import LightSimBackend, ContingencyAnalysis
env_name = "l2rpn_neurips_2020_track2_small"
test = False
@@ -43,7 +43,7 @@
env_pp = multi_mix_env_pp[key_env]
# Run the environment on a scenario using the TimeSerie module
-security_analysis = SecurityAnalysis(env)
+security_analysis = ContingencyAnalysis(env)
security_analysis.add_all_n1_contingencies()
p_or, a_or, voltages = security_analysis.get_flows()
# the 3 lines above are the only lines you need to do to perform a security analysis !
diff --git a/lightsim2grid/__init__.py b/lightsim2grid/__init__.py
index abc421d9..ec429e7d 100644
--- a/lightsim2grid/__init__.py
+++ b/lightsim2grid/__init__.py
@@ -5,7 +5,7 @@
# you can obtain one at http://mozilla.org/MPL/2.0/.
# SPDX-License-Identifier: MPL-2.0
# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
-__version__ = "0.7.5.post1"
+__version__ = "0.8.0"
__all__ = ["newtonpf", "SolverType", "ErrorType", "solver"]
@@ -39,10 +39,10 @@
print(f"TimeSerie import error: {exc_}")
try:
- from lightsim2grid.securityAnalysis import SecurityAnalysis
- __all__.append("SecurityAnalysis")
- __all__.append("securityAnalysis")
+ from lightsim2grid.contingencyAnalysis import ContingencyAnalysis
+ __all__.append("contingencyAnalysis")
+ __all__.append("ContingencyAnalysis")
except ImportError as exc_:
# grid2op is not installed, the SecurtiyAnalysis module will not be available
pass
- print(f"SecurityAnalysis import error: {exc_}")
+ print(f"ContingencyAnalysis import error: {exc_}")
diff --git a/lightsim2grid/contingencyAnalysis.py b/lightsim2grid/contingencyAnalysis.py
new file mode 100644
index 00000000..f16a504c
--- /dev/null
+++ b/lightsim2grid/contingencyAnalysis.py
@@ -0,0 +1,347 @@
+# Copyright (c) 2020, RTE (https://www.rte-france.com)
+# See AUTHORS.txt
+# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
+# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
+# you can obtain one at http://mozilla.org/MPL/2.0/.
+# SPDX-License-Identifier: MPL-2.0
+# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
+
+__all__ = ["ContingencyAnalysisCPP", "ContingencyAnalysis",
+ # deprecated
+ "SecurityAnalysisCPP", "SecurityAnalysis",
+ ]
+
+import copy
+import numpy as np
+from collections.abc import Iterable
+
+from lightsim2grid.lightSimBackend import LightSimBackend
+from lightsim2grid.solver import SolverType
+from lightsim2grid_cpp import ContingencyAnalysisCPP
+
+
+class ContingencyAnalysis(object):
+ """
+ This class allows to perform a "security analysis" from a given grid state.
+
+ For now, you cannot change the grid state, and it only computes the security analysis with
+ current flows at origin of powerlines.
+
+ Feel free to post a feature request if you want to extend it.
+
+ This class is used in 4 phases:
+
+ 0) you create it from a grid2op environment (the grid topology will not be modified from this environment)
+ 1) you add some contingencies to simulate
+ 2) you start the simulation
+ 3) you read back the results
+
+
+ Examples
+ --------
+ An example is given here
+
+ .. code-block:: python
+
+ import grid2op
+ from lightsim2grid import SecurityAnalysis
+ from lightsim2grid import LightSimBackend
+ env_name = ...
+ env = grid2op.make(env_name, backend=LightSimBackend())
+
+ 0) you create
+ security_analysis = SecurityAnalysis(env)
+
+ 1) you add some contingencies to simulate
+ security_analysis.add_multiple_contingencies(...) # or security_analysis.add_single_contingency(...)
+
+ 2) you start the simulation (done automatically)
+ 3) you read back the results
+ res_p, res_a, res_v = security_analysis.get_flows()
+
+ # in this results, then
+ # res_a[row_id] will be the flows, on all powerline corresponding to the `row_id` contingency.
+ # you can retrieve it with `security_analysis.contingency_order[row_id]`
+
+ Notes
+ ------
+
+ Sometimes, the behaviour might differ from grid2op. For example, if simulating a contingency
+ leads to a non connected grid, then this function will return "Nan" for the flows and 0. for
+ the voltages.
+
+ In grid2op, it would be, in this case, 0. for the flows and 0. for the voltages.
+
+ """
+ STR_TYPES = (str, np.str_) # np.str deprecated in numpy 1.20 and earlier versions not supported anyway
+
+ def __init__(self, grid2op_env):
+ if not isinstance(grid2op_env.backend, LightSimBackend):
+ raise RuntimeError("This class only works with LightSimBackend")
+ self.grid2op_env = grid2op_env.copy()
+ self.computer = ContingencyAnalysisCPP(self.grid2op_env.backend._grid)
+ self._contingency_order = {} # key: contingency (as tuple), value: order in which it is entered
+ self._all_contingencies = []
+ self.__computed = False
+ self._vs = None
+ self._ampss = None
+
+ self.available_solvers = self.computer.available_solvers()
+ if SolverType.KLU in self.available_solvers:
+ # use the faster KLU if available
+ self.computer.change_solver(SolverType.KLU)
+
+ @property
+ def all_contingencies(self):
+ return copy.deepcopy(self._all_contingencies)
+
+ @all_contingencies.setter
+ def all_contingencies(self, val):
+ raise RuntimeError("Impossible to add new topologies like this. Please use `add_single_contingency` "
+ "or `add_multiple_contingencies`.")
+
+ def clear(self):
+ """
+ Clear the list of contingencies to simulate
+ """
+ self.computer.clear()
+ self._contingency_order = {}
+ self.__computed = False
+ self._vs = None
+ self._ampss = None
+ self._all_contingencies = []
+
+ def _single_cont_to_li_int(self, single_cont):
+ li_disc = []
+ if isinstance(single_cont, int):
+ single_cont = [single_cont]
+
+ for stuff in single_cont:
+ if isinstance(stuff, type(self).STR_TYPES):
+ stuff = np.where(self.grid2op_env.name_line == stuff)
+ stuff = stuff[0]
+ if stuff.size == 0:
+ # name is not found
+ raise RuntimeError(f"Impossible to find a powerline named \"{stuff}\" in the environment")
+ stuff = int(stuff[0])
+ else:
+ stuff = int(stuff)
+ li_disc.append(stuff)
+ return li_disc
+
+ def add_single_contingency(self, *args):
+ """
+ This function allows to add a single contingency specified by either the powerlines names
+ (which should match env.name_line) or by their ID.
+
+ The contingency added can be a "n-1" which will simulate a single powerline disconnection
+ or a "n-k" which will simulate the disconnection of multiple powerlines.
+
+ It does not accept any keword arguments.
+
+ Examples
+ --------
+
+ .. code-block:: python
+
+ import grid2op
+ from lightsim2grid import SecurityAnalysis
+ from lightsim2grid import LightSimBackend
+ env_name = ...
+ env = grid2op.make(env_name, backend=LightSimBackend())
+
+ security_anlysis = SecurityAnalysis(env)
+ # the single (n-1) contingency "disconnect powerline 0" is added
+ security_anlysis.add_single_contingency(0)
+
+ # add the single (n-1) contingency "disconnect line 1
+ security_anlysis.add_single_contingency(env.name_line[1])
+
+ # add a single contingency that disconnect powerline 2 and 3 at the same time
+ security_anlysis.add_single_contingency(env.name_line[2], 3)
+
+ Notes
+ -----
+ If it raises an error for a given contingency, the object might be not properly initialized.
+ In this case, we recommend you to clear it (using the `clear()` method and to attempt to
+ add contingencies again.)
+
+ """
+ li_disc = self._single_cont_to_li_int(args)
+ li_disc_tup = tuple(li_disc)
+ if li_disc_tup not in self._contingency_order:
+ # this is really the first time this contingency is seen
+ try:
+ self.computer.add_nk(li_disc)
+ my_id = len(self._contingency_order)
+ self._contingency_order[li_disc_tup] = my_id
+ self._all_contingencies.append(li_disc_tup)
+ except Exception as exc_:
+ raise RuntimeError(f"Impossible to add the contingency {args}. The most likely cause "
+ f"is that you try to disconnect a powerline that is not present "
+ f"on the grid") from exc_
+
+ def add_multiple_contingencies(self, *args):
+ """
+ This function will add multiple contingencies at the same time.
+
+ This code is equivalent to:
+
+ .. code-block:: python
+
+ for single_cont in args:
+ self.add_single_contingency(single_cont)
+
+ It does not accept any keword arguments.
+
+ Examples
+ --------
+
+ .. code-block:: python
+
+ import grid2op
+ from lightsim2grid import SecurityAnalysis
+ from lightsim2grid import LightSimBackend
+ env_name = ...
+ env = grid2op.make(env_name, backend=LightSimBackend())
+
+ security_anlysis = SecurityAnalysis(env)
+
+ # add a single contingency that disconnect powerline 2 and 3 at the same time
+ security_anlysis.add_single_contingency(env.name_line[2], 3)
+
+ # add a multiple contingencies the first one disconnect powerline 2 and
+ # and the second one disconnect powerline 3
+ security_anlysis.add_multiple_contingencies(env.name_line[2], 3)
+ """
+ for single_cont in args:
+ if isinstance(single_cont, Iterable) and not isinstance(single_cont, type(self).STR_TYPES):
+ # this is a contingency consisting in cutting multiple powerlines
+ self.add_single_contingency(*single_cont)
+ else:
+ # this is likely an int or a string representing a contingency
+ self.add_single_contingency(single_cont)
+
+ def add_all_n1_contingencies(self):
+ """
+ This method registers as the contingencies that will be computed all the contingencies that disconnects 1 powerline
+
+ This is equivalent to:
+
+ .. code-block:: python
+
+ for single_cont_id in range(env.n_line):
+ self.add_single_contingency(single_cont_id)
+ """
+ for single_cont_id in range(self.grid2op_env.n_line):
+ self.add_single_contingency(single_cont_id)
+
+ def get_flows(self, *args):
+ """
+ Retrieve the flows after each contingencies has been simulated.
+
+ Each row of the resulting flow matrix will correspond to a contingency simulated in the arguments.
+
+ You can require only the result on some contingencies with the `args` argument, but in each case, all the results will
+ be computed. If you don't specify anything, the results will be returned for all contingencies (which we recommend to do)
+
+ Examples
+ --------
+
+ .. code-block:: python
+
+ import grid2op
+ from lightsim2grid import SecurityAnalysis
+ from lightsim2grid import LightSimBackend
+ env_name = ...
+ env = grid2op.make(env_name, backend=LightSimBackend())
+
+ security_analysis = SecurityAnalysis(env)
+ security_analysis.add_multiple_contingencies(...) # or security_analysis.add_single_contingency(...)
+ res_p, res_a, res_v = security_analysis.get_flows()
+
+ # in this results, then
+ # res_a[row_id] will be the flows, on all powerline corresponding to the `row_id` contingency.
+ # you can retrieve it with `security_analysis.contingency_order[row_id]`
+ """
+
+ all_defaults = self.computer.my_defaults()
+ if len(args) == 0:
+ # default: i consider all contingencies
+ orders_ = np.zeros(len(all_defaults), dtype=int)
+ for id_cpp, cont_ in enumerate(all_defaults):
+ tup_ = tuple(cont_)
+ orders_[self._contingency_order[tup_]] = id_cpp
+ else:
+ # a list of interesting contingencies has been provided
+ orders_ = np.zeros(len(args), dtype=int)
+ all_defaults = [tuple(cont) for cont in all_defaults]
+ for id_me, cont_ in enumerate(args):
+ cont_li = self._single_cont_to_li_int(cont_)
+ tup_ = tuple(cont_li)
+ if tup_ not in self._contingency_order:
+ raise RuntimeError(f"Contingency {cont_} is not simulated by this class. Have you called "
+ f"`add_single_contingency` or `add_multiple_contingencies` ?")
+ id_cpp = all_defaults.index(tup_)
+ orders_[id_me] = id_cpp
+
+ if not self.__computed:
+ self.compute_V()
+ self.compute_A()
+ self.compute_P()
+
+ return self._mws[orders_], self._ampss[orders_], self._vs[orders_]
+
+ def compute_V(self):
+ """
+ This function allows to retrieve the complex voltage at each bus of the grid for each contingency.
+
+ .. warning:: Order of the results
+
+ The order in which the results are returned is NOT necessarily the order in which the contingencies have
+ been entered. Please use `get_flows()` method for easier reading back of the results
+
+ """
+ v_init = self.grid2op_env.backend.V
+ self.computer.compute(v_init,
+ self.grid2op_env.backend.max_it,
+ self.grid2op_env.backend.tol)
+ self._vs = self.computer.get_voltages()
+ self.__computed = True
+ return self._vs
+
+ def compute_A(self):
+ """
+ This function returns the current flows (in Amps, A) at the origin / high voltage side
+
+ .. warning:: Order of the results
+
+ The order in which the results are returned is NOT necessarily the order in which the contingencies have
+ been entered. Please use `get_flows()` method for easier reading back of the results !
+
+ """
+ if not self.__computed:
+ raise RuntimeError("This function can only be used if compute_V has been sucessfully called")
+ self._ampss = 1e3 * self.computer.compute_flows()
+ return self._ampss
+
+ def compute_P(self):
+ """
+ This function returns the active power flows (in MW) at the origin / high voltage side
+
+ .. warning:: Order of the results
+
+ The order in which the results are returned is NOT necessarily the order in which the contingencies have
+ been entered. Please use `get_flows()` method for easier reading back of the results !
+
+ """
+ if not self.__computed:
+ raise RuntimeError("This function can only be used if compute_V has been sucessfully called")
+ self._mws = 1.0 * self.computer.compute_power_flows()
+ return self._mws
+
+ def close(self):
+ """permanently close the object"""
+ self.grid2op_env.close()
+ self.clear()
+ self.computer.close()
diff --git a/lightsim2grid/gridmodel/_aux_add_slack.py b/lightsim2grid/gridmodel/_aux_add_slack.py
index 17b9c838..cfa66f08 100644
--- a/lightsim2grid/gridmodel/_aux_add_slack.py
+++ b/lightsim2grid/gridmodel/_aux_add_slack.py
@@ -103,8 +103,8 @@ def _aux_add_slack(model, pp_net, pp_to_ls):
gen_p = np.concatenate((pp_net.gen["p_mw"].values, slack_contrib))
gen_v = np.concatenate((pp_net.gen["vm_pu"].values, vm_pu))
gen_bus = np.concatenate((pp_bus_to_ls(pp_net.gen["bus"].values, pp_to_ls), slack_bus_ids))
- gen_min_q = np.concatenate((pp_net.gen["min_q_mvar"].values, [-999999.]))
- gen_max_q = np.concatenate((pp_net.gen["max_q_mvar"].values, [+99999.]))
+ gen_min_q = np.concatenate((pp_net.gen["min_q_mvar"].values, [-999999. for _ in range(nb_slack)]))
+ gen_max_q = np.concatenate((pp_net.gen["max_q_mvar"].values, [+99999. for _ in range(nb_slack)]))
model.init_generators(gen_p, gen_v, gen_min_q, gen_max_q, gen_bus)
# handle the possible distributed slack bus
diff --git a/lightsim2grid/gridmodel/from_pypowsybl.py b/lightsim2grid/gridmodel/from_pypowsybl.py
index 3afc031a..6501ead8 100644
--- a/lightsim2grid/gridmodel/from_pypowsybl.py
+++ b/lightsim2grid/gridmodel/from_pypowsybl.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2023, RTE (https://www.rte-france.com)
+# Copyright (c) 2023-2024, RTE (https://www.rte-france.com)
# See AUTHORS.txt
# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
@@ -6,63 +6,120 @@
# SPDX-License-Identifier: MPL-2.0
# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
+import warnings
import numpy as np
+import pandas as pd
import pypowsybl as pypo
+
from lightsim2grid_cpp import GridModel
-def init(net : pypo.network, gen_slack_id: int = None):
+def _aux_get_bus(bus_df, df, conn_key="connected", bus_key="bus_id"):
+ if df.shape[0] == 0:
+ # no element of this type so no problem
+ return np.zeros(0, dtype=int), np.ones(0, dtype=bool)
+ # retrieve which elements are disconnected
+ mask_disco = ~df[conn_key]
+ if mask_disco.all():
+ raise RuntimeError("All element of the same type are disconnected, the init will not work.")
+ first_el_co = np.where(~mask_disco.values)[0][0]
+ # retrieve the bus where the element are
+ tmp_bus_id = df[bus_key].copy()
+ tmp_bus_id[mask_disco] = df.iloc[first_el_co][bus_key] # assign a "random" bus to disco element
+ bus_id = bus_df.loc[tmp_bus_id.values]["bus_id"].values
+ # deactivate the element not on the main component
+ # wrong_component = bus_df.loc[tmp_bus_id.values]["connected_component"].values != 0
+ # mask_disco[wrong_component] = True
+ # assign bus -1 to disconnected elements
+ bus_id[mask_disco] = -1
+ return bus_id, mask_disco.values
+
+
+def init(net : pypo.network,
+ gen_slack_id: int = None,
+ slack_bus_id: int = None,
+ sn_mva = 100.,
+ sort_index=True,
+ f_hz = 50., # unused
+ only_main_component=True,
+ return_sub_id=False):
model = GridModel()
+ # model.set_f_hz(f_hz)
+
# for substation
# network.get_voltage_levels()["substation_id"]
# network.get_substations()
# network.get_busbar_sections()
-
- # initialize and use converters
- sn_mva_ = 100. # TODO read from net
- f_hz = 50. # TODO read from net
+ if gen_slack_id is not None and slack_bus_id is not None:
+ raise RuntimeError("Impossible to intialize a grid with both gen_slack_id and slack_bus_id")
# assign unique id to the buses
- bus_df = net.get_buses().copy()
+ bus_df_orig = net.get_buses()
+ if sort_index:
+ bus_df = bus_df_orig.sort_index()
+ else:
+ bus_df = bus_df_orig
bus_df["bus_id"] = np.arange(bus_df.shape[0])
- model.set_sn_mva(sn_mva_)
+ bus_df_orig["bus_id"] = bus_df.loc[bus_df_orig.index]["bus_id"]
+ voltage_levels = net.get_voltage_levels()
+ model.set_sn_mva(sn_mva)
model.set_init_vm_pu(1.06)
- model.init_bus(net.get_voltage_levels().loc[bus_df["voltage_level_id"].values]["nominal_v"].values,
+ model.init_bus(voltage_levels.loc[bus_df["voltage_level_id"].values]["nominal_v"].values,
0, 0 # unused
)
-
+ model._orig_to_ls = 1 * bus_df_orig["bus_id"].values
+
# do the generators
- df_gen = net.get_generators()
- model.init_generators(df_gen["target_p"].values,
- df_gen["target_v"].values / net.get_voltage_levels().loc[df_gen["voltage_level_id"].values]["nominal_v"].values,
- df_gen["min_q"].values,
- df_gen["max_q"].values,
- 1 * bus_df.loc[df_gen["bus_id"].values]["bus_id"].values
- )
- # TODO dist slack
- if gen_slack_id is None:
- model.add_gen_slackbus(0, 1.)
+ if sort_index:
+ df_gen = net.get_generators().sort_index()
else:
- model.add_gen_slackbus(gen_slack_id, 1.)
+ df_gen = net.get_generators()
+ # to handle encoding in 32 bits and overflow when "splitting" the Q values among
+ min_q = df_gen["min_q"].values.astype(np.float32)
+ max_q = df_gen["max_q"].values.astype(np.float32)
+ min_q[~np.isfinite(min_q)] = np.finfo(np.float32).min * 0.5 + 1.
+ max_q[~np.isfinite(max_q)] = np.finfo(np.float32).max * 0.5 - 1.
+ gen_bus, gen_disco = _aux_get_bus(bus_df, df_gen)
+ model.init_generators_full(df_gen["target_p"].values,
+ df_gen["target_v"].values / voltage_levels.loc[df_gen["voltage_level_id"].values]["nominal_v"].values,
+ df_gen["target_q"].values,
+ df_gen["voltage_regulator_on"].values,
+ min_q,
+ max_q,
+ gen_bus
+ )
+ for gen_id, is_disco in enumerate(gen_disco):
+ if is_disco:
+ model.deactivate_gen(gen_id)
+ model.set_gen_names(df_gen.index)
# for loads
- df_load = net.get_loads()
+ if sort_index:
+ df_load = net.get_loads().sort_index()
+ else:
+ df_load = net.get_loads()
+ load_bus, load_disco = _aux_get_bus(bus_df, df_load)
model.init_loads(df_load["p0"].values,
df_load["q0"].values,
- 1 * bus_df.loc[df_load["bus_id"].values]["bus_id"].values
+ load_bus
)
+ for load_id, is_disco in enumerate(load_disco):
+ if is_disco:
+ model.deactivate_load(load_id)
+ model.set_load_names(df_load.index)
# for lines
- df_line = net.get_lines()
- # TODO add g1 / b1 and g2 / b2 in lightsim2grid
- # line_h = (1j*df_line["g1"].values + df_line["b1"].values + 1j*df_line["g2"].values + df_line["b2"].values)
+ if sort_index:
+ df_line = net.get_lines().sort_index()
+ else:
+ df_line = net.get_lines()
# per unit
- branch_from_kv = net.get_voltage_levels().loc[df_line["voltage_level1_id"].values]["nominal_v"].values
- branch_to_kv = net.get_voltage_levels().loc[df_line["voltage_level2_id"].values]["nominal_v"].values
+ branch_from_kv = voltage_levels.loc[df_line["voltage_level1_id"].values]["nominal_v"].values
+ branch_to_kv = voltage_levels.loc[df_line["voltage_level2_id"].values]["nominal_v"].values
# only valid for lines with same voltages at both side...
- # branch_from_pu = branch_from_kv * branch_from_kv / sn_mva_
+ # branch_from_pu = branch_from_kv * branch_from_kv / sn_mva
# line_r = df_line["r"].values / branch_from_pu
# line_x = df_line["x"].values / branch_from_pu
# line_h_or = (1j*df_line["g1"].values + df_line["b1"].values) * branch_from_pu
@@ -73,25 +130,34 @@ def init(net : pypo.network, gen_slack_id: int = None):
# for right formula
v1 = branch_from_kv
v2 = branch_to_kv
- line_r = sn_mva_ * df_line["r"].values / v1 / v2
- line_x = sn_mva_ * df_line["x"].values / v1 / v2
+ line_r = sn_mva * df_line["r"].values / v1 / v2
+ line_x = sn_mva * df_line["x"].values / v1 / v2
tmp_ = np.reciprocal(df_line["r"].values + 1j*df_line["x"].values)
- b1 = df_line["b1"].values * v1*v1/sn_mva_ + (v1-v2)*tmp_.imag*v1/sn_mva_
- b2 = df_line["b2"].values * v2*v2/sn_mva_ + (v2-v1)*tmp_.imag*v2/sn_mva_
- g1 = df_line["g1"].values * v1*v1/sn_mva_ + (v1-v2)*tmp_.real*v1/sn_mva_
- g2 = df_line["g2"].values * v2*v2/sn_mva_ + (v2-v1)*tmp_.real*v2/sn_mva_
+ b1 = df_line["b1"].values * v1*v1/sn_mva + (v1-v2)*tmp_.imag*v1/sn_mva
+ b2 = df_line["b2"].values * v2*v2/sn_mva + (v2-v1)*tmp_.imag*v2/sn_mva
+ g1 = df_line["g1"].values * v1*v1/sn_mva + (v1-v2)*tmp_.real*v1/sn_mva
+ g2 = df_line["g2"].values * v2*v2/sn_mva + (v2-v1)*tmp_.real*v2/sn_mva
line_h_or = (b1 + 1j * g1)
line_h_ex = (b2 + 1j * g2)
+ lor_bus, lor_disco = _aux_get_bus(bus_df, df_line, conn_key="connected1", bus_key="bus1_id")
+ lex_bus, lex_disco = _aux_get_bus(bus_df, df_line, conn_key="connected2", bus_key="bus2_id")
model.init_powerlines_full(line_r,
line_x,
line_h_or,
line_h_ex,
- 1 * bus_df.loc[df_line["bus1_id"].values]["bus_id"].values,
- 1 * bus_df.loc[df_line["bus2_id"].values]["bus_id"].values
+ lor_bus,
+ lex_bus
)
+ for line_id, (is_or_disc, is_ex_disc) in enumerate(zip(lor_disco, lex_disco)):
+ if is_or_disc or is_ex_disc:
+ model.deactivate_powerline(line_id)
+ model.set_line_names(df_line.index)
# for trafo
- df_trafo = net.get_2_windings_transformers()
+ if sort_index:
+ df_trafo = net.get_2_windings_transformers().sort_index()
+ else:
+ df_trafo = net.get_2_windings_transformers()
# TODO net.get_ratio_tap_changers()
# TODO net.get_phase_tap_changers()
shift_ = np.zeros(df_trafo.shape[0])
@@ -99,14 +165,16 @@ def init(net : pypo.network, gen_slack_id: int = None):
is_tap_hv_side = np.ones(df_trafo.shape[0], dtype=bool) # TODO
# per unit
- trafo_from_kv = net.get_voltage_levels().loc[df_trafo["voltage_level1_id"].values]["nominal_v"].values
- trafo_to_kv = net.get_voltage_levels().loc[df_trafo["voltage_level2_id"].values]["nominal_v"].values
- trafo_to_pu = trafo_to_kv * trafo_to_kv / sn_mva_
+ trafo_from_kv = voltage_levels.loc[df_trafo["voltage_level1_id"].values]["nominal_v"].values
+ trafo_to_kv = voltage_levels.loc[df_trafo["voltage_level2_id"].values]["nominal_v"].values
+ trafo_to_pu = trafo_to_kv * trafo_to_kv / sn_mva
# tap
tap_step_pct = (df_trafo["rated_u1"] / trafo_from_kv - 1.) * 100.
has_tap = tap_step_pct != 0.
tap_pos[has_tap] += 1
tap_step_pct[~has_tap] = 1.0 # or any other values...
+ tor_bus, tor_disco = _aux_get_bus(bus_df, df_trafo, conn_key="connected1", bus_key="bus1_id")
+ tex_bus, tex_disco = _aux_get_bus(bus_df, df_trafo, conn_key="connected2", bus_key="bus2_id")
model.init_trafo(df_trafo["r"].values / trafo_to_pu,
df_trafo["x"].values / trafo_to_pu,
2.*(1j*df_trafo["g"].values + df_trafo["b"].values) * trafo_to_pu,
@@ -114,47 +182,129 @@ def init(net : pypo.network, gen_slack_id: int = None):
tap_pos,
shift_,
is_tap_hv_side,
- 1 * bus_df.loc[df_trafo["bus1_id"].values]["bus_id"].values, # TODO do I need to change hv / lv
- 1 * bus_df.loc[df_trafo["bus2_id"].values]["bus_id"].values)
+ tor_bus, # TODO do I need to change hv / lv
+ tex_bus)
+ for t_id, (is_or_disc, is_ex_disc) in enumerate(zip(tor_disco, tex_disco)):
+ if is_or_disc or is_ex_disc:
+ model.deactivate_trafo(t_id)
+ model.set_trafo_names(df_trafo.index)
# for shunt
- df_shunt = net.get_shunt_compensators()
- shunt_kv = net.get_voltage_levels().loc[df_shunt["voltage_level_id"].values]["nominal_v"].values
+ if sort_index:
+ df_shunt = net.get_shunt_compensators().sort_index()
+ else:
+ df_shunt = net.get_shunt_compensators()
+
+ sh_bus, sh_disco = _aux_get_bus(bus_df, df_shunt)
+ shunt_kv = voltage_levels.loc[df_shunt["voltage_level_id"].values]["nominal_v"].values
model.init_shunt(-df_shunt["g"].values * shunt_kv**2,
-df_shunt["b"].values * shunt_kv**2,
- 1 * bus_df.loc[df_shunt["bus_id"].values]["bus_id"].values
+ sh_bus
)
-
+ for shunt_id, disco in enumerate(sh_disco):
+ if disco:
+ model.deactivate_shunt(shunt_id)
+ model.set_shunt_names(df_shunt.index)
+
# for hvdc (TODO not tested yet)
- df_dc = net.get_hvdc_lines()
- df_sations = net.get_vsc_converter_stations()
- bus_from_id = df_sations.loc[df_dc["converter_station1_id"].values]["bus_id"].values
- bus_to_id = df_sations.loc[df_dc["converter_station2_id"].values]["bus_id"].values
+ if sort_index:
+ df_dc = net.get_hvdc_lines().sort_index()
+ df_sations = net.get_vsc_converter_stations().sort_index()
+ else:
+ df_dc = net.get_hvdc_lines()
+ df_sations = net.get_vsc_converter_stations()
+ # bus_from_id = df_sations.loc[df_dc["converter_station1_id"].values]["bus_id"].values
+ # bus_to_id = df_sations.loc[df_dc["converter_station2_id"].values]["bus_id"].values
+ hvdc_bus_from_id, hvdc_from_disco = _aux_get_bus(bus_df, df_sations.loc[df_dc["converter_station1_id"].values])
+ hvdc_bus_to_id, hvdc_to_disco = _aux_get_bus(bus_df, df_sations.loc[df_dc["converter_station2_id"].values])
loss_percent = np.zeros(df_dc.shape[0]) # TODO
loss_mw = np.zeros(df_dc.shape[0]) # TODO
- model.init_dclines(bus_df.loc[bus_from_id]["bus_id"].values,
- bus_df.loc[bus_to_id]["bus_id"].values,
+ model.init_dclines(hvdc_bus_from_id,
+ hvdc_bus_to_id,
df_dc["target_p"].values,
loss_percent,
loss_mw,
- net.get_voltage_levels().loc[df_sations.loc[df_dc["converter_station1_id"].values]["voltage_level_id"].values]["nominal_v"].values,
- net.get_voltage_levels().loc[df_sations.loc[df_dc["converter_station2_id"].values]["voltage_level_id"].values]["nominal_v"].values,
+ voltage_levels.loc[df_sations.loc[df_dc["converter_station1_id"].values]["voltage_level_id"].values]["nominal_v"].values,
+ voltage_levels.loc[df_sations.loc[df_dc["converter_station2_id"].values]["voltage_level_id"].values]["nominal_v"].values,
df_sations.loc[df_dc["converter_station1_id"].values]["min_q"].values,
df_sations.loc[df_dc["converter_station1_id"].values]["max_q"].values,
df_sations.loc[df_dc["converter_station2_id"].values]["min_q"].values,
df_sations.loc[df_dc["converter_station2_id"].values]["max_q"].values
)
-
+ # TODO will probably not work !
+ for hvdc_id, (is_or_disc, is_ex_disc) in enumerate(zip(hvdc_from_disco, hvdc_to_disco)):
+ if is_or_disc or is_ex_disc:
+ model.deactivate_hvdc(hvdc_id)
+ model.set_dcline_names(df_sations.index)
+
# storage units (TODO not tested yet)
- df_batt = net.get_batteries()
+ if sort_index:
+ df_batt = net.get_batteries().sort_index()
+ else:
+ df_batt = net.get_batteries()
+ batt_bus, batt_disco = _aux_get_bus(bus_df, df_batt)
model.init_storages(df_batt["target_p"].values,
df_batt["target_q"].values,
- 1 * bus_df.loc[df_batt["bus_id"].values]["bus_id"].values
+ batt_bus
)
+ for batt_id, disco in enumerate(batt_disco):
+ if disco:
+ model.deactivate_storage(batt_id)
+ model.set_storage_names(df_batt.index)
+
+ # TODO dist slack
+ if gen_slack_id is None and slack_bus_id is None:
+ # if nothing is given, by default I assign a slack bus to a bus where a lot of lines are connected
+ # quite central in the grid
+ bus_id, gen_id = model.assign_slack_to_most_connected()
+ elif gen_slack_id is not None:
+ if slack_bus_id is not None:
+ raise RuntimeError(f"You provided both gen_slack_id and slack_bus_id which is not possible.")
+ model.add_gen_slackbus(gen_slack_id, 1.)
+ elif slack_bus_id is not None:
+ gen_bus = np.array([el.bus_id for el in model.get_generators()])
+ gen_is_conn_slack = gen_bus == model._orig_to_ls[slack_bus_id]
+ nb_conn = gen_is_conn_slack.sum()
+ if nb_conn == 0:
+ raise RuntimeError(f"There is no generator connected to bus {slack_bus_id}. It cannot be the slack")
+ for gen_id, is_slack in enumerate(gen_is_conn_slack):
+ if is_slack:
+ model.add_gen_slackbus(gen_id, 1. / nb_conn)
# TODO
- # sgen
+ # sgen => regular gen (from net.get_generators()) with voltage_regulator off TODO
# TODO checks
# no 3windings trafo and other exotic stuff
- return model
+ if net.get_phase_tap_changers().shape[0] > 0:
+ warnings.warn("There are tap changers in the iidm grid which are not taken "
+ "into account in the lightsim2grid at the moment. "
+ "NB: lightsim2grid gridmodel can handle tap changer, it is just not "
+ "handled by the 'from_pypowsybl` function at the moment.")
+
+ # and now deactivate all elements and nodes not in the main component
+ if only_main_component:
+ model.consider_only_main_component()
+ if not return_sub_id:
+ # for backward compatibility
+ return model
+ else:
+ # voltage_level_id is kind of what I call "substation" in grid2op
+ vl_unique = bus_df["voltage_level_id"].unique()
+ sub_df = pd.DataFrame(index=np.sort(vl_unique), data={"sub_id": np.arange(vl_unique.size)})
+ buses_sub_id = pd.merge(left=bus_df, right=sub_df, how="left", left_on="voltage_level_id", right_index=True)[["bus_id", "sub_id"]]
+ gen_sub = pd.merge(left=df_gen, right=sub_df, how="left", left_on="voltage_level_id", right_index=True)[["sub_id"]]
+ load_sub = pd.merge(left=df_load, right=sub_df, how="left", left_on="voltage_level_id", right_index=True)[["sub_id"]]
+ lor_sub = pd.merge(left=df_line, right=sub_df, how="left", left_on="voltage_level1_id", right_index=True)[["sub_id"]]
+ lex_sub = pd.merge(left=df_line, right=sub_df, how="left", left_on="voltage_level2_id", right_index=True)[["sub_id"]]
+ tor_sub = pd.merge(left=df_trafo, right=sub_df, how="left", left_on="voltage_level1_id", right_index=True)[["sub_id"]]
+ tex_sub = pd.merge(left=df_trafo, right=sub_df, how="left", left_on="voltage_level2_id", right_index=True)[["sub_id"]]
+ batt_sub = pd.merge(left=df_batt, right=sub_df, how="left", left_on="voltage_level_id", right_index=True)[["sub_id"]]
+ sh_sub = pd.merge(left=df_shunt, right=sub_df, how="left", left_on="voltage_level_id", right_index=True)[["sub_id"]]
+ hvdc_vl_info = pd.DataFrame(index=df_dc.index,
+ data={"voltage_level1_id": df_sations.loc[df_dc["converter_station1_id"].values]["voltage_level_id"].values,
+ "voltage_level2_id": df_sations.loc[df_dc["converter_station2_id"].values]["voltage_level_id"].values
+ })
+ hvdc_sub_from_id = pd.merge(left=hvdc_vl_info, right=sub_df, how="left", left_on="voltage_level1_id", right_index=True)[["sub_id"]]
+ hvdc_sub_to_id = pd.merge(left=hvdc_vl_info, right=sub_df, how="left", left_on="voltage_level2_id", right_index=True)[["sub_id"]]
+ return model, (buses_sub_id, gen_sub, load_sub, (lor_sub, tor_sub), (lex_sub, tex_sub), batt_sub, sh_sub, hvdc_sub_from_id, hvdc_sub_to_id)
diff --git a/lightsim2grid/gridmodel/initGridModel.py b/lightsim2grid/gridmodel/initGridModel.py
index 8a6840c2..78293cdc 100644
--- a/lightsim2grid/gridmodel/initGridModel.py
+++ b/lightsim2grid/gridmodel/initGridModel.py
@@ -80,15 +80,14 @@ def init(pp_net):
model.set_sn_mva(pp_net.sn_mva)
tmp_bus_ind = np.argsort(pp_net.bus.index)
+ model.init_bus(pp_net.bus.iloc[tmp_bus_ind]["vn_kv"].values,
+ pp_net.line.shape[0],
+ pp_net.trafo.shape[0])
if np.any(np.sort(pp_net.bus.index) != np.arange(pp_net.bus.shape[0])):
- model._ls_to_pp = 1 * pp_net.bus.index.values.astype(int)
+ model._ls_to_orig = 1 * pp_net.bus.index.values.astype(int)
pp_to_ls = {pp_bus: ls_bus for pp_bus, ls_bus in zip(pp_net.bus.index, tmp_bus_ind)}
else:
pp_to_ls = None
- model.init_bus(pp_net.bus.iloc[tmp_bus_ind]["vn_kv"].values,
- pp_net.line.shape[0],
- pp_net.trafo.shape[0])
-
# deactivate in lightsim the deactivated bus in pandapower
for bus_id in range(pp_net.bus.shape[0]):
if not pp_net.bus["in_service"].values[bus_id]:
diff --git a/lightsim2grid/lightSimBackend.py b/lightsim2grid/lightSimBackend.py
index 8a737e87..a7c63196 100644
--- a/lightsim2grid/lightSimBackend.py
+++ b/lightsim2grid/lightSimBackend.py
@@ -10,17 +10,23 @@
from typing import Optional, Union
import warnings
import numpy as np
+import pandas as pd
import time
from grid2op.Action import CompleteAction
from grid2op.Backend import Backend
-from grid2op.Exceptions import BackendError, DivergingPowerFlow
+from grid2op.Exceptions import BackendError
from grid2op.dtypes import dt_float, dt_int, dt_bool
try:
from grid2op.Action._backendAction import _BackendAction
except ImportError as exc_:
from grid2op.Action._BackendAction import _BackendAction
+try:
+ from typing import Literal
+except ImportError:
+ from typing_extensions import Literal
+
from lightsim2grid.gridmodel import init
from lightsim2grid.solver import SolverType
@@ -30,6 +36,9 @@ class LightSimBackend(Backend):
This is a specialization of the grid2op Backend class to use the lightsim2grid solver,
coded in c++, aiming at speeding up the computations.
"""
+
+ shunts_data_available = True
+
def __init__(self,
detailed_infos_for_cascading_failures: bool=False,
can_be_copied: bool=True,
@@ -39,6 +48,10 @@ def __init__(self,
turned_off_pv : bool=True, # are gen turned off (or with p=0) contributing to voltage or not
dist_slack_non_renew: bool=False, # distribute the slack on non renewable turned on (and with P>0) generators
use_static_gen: bool=False, # add the static generators as generator gri2dop side
+ loader_method: Literal["pandapower", "pypowsybl"] = "pandapower",
+ loader_kwargs : Optional[dict] = None,
+ stop_if_load_disco : Optional[bool] = True,
+ stop_if_gen_disco : Optional[bool] = True,
):
self.max_it = max_iter
self.tol = tol # tolerance for the solver
@@ -54,7 +67,21 @@ def __init__(self,
# add the static gen to the list of controlable gen in grid2Op
self._use_static_gen = use_static_gen # TODO implement it
-
+
+ self._loader_method = loader_method
+
+ self._loader_kwargs = loader_kwargs
+
+ #: .. versionadded:: 0.8.0
+ #: if set to `True` (default) then the backend will raise a
+ #: BackendError in case of disconnected load
+ self._stop_if_load_disco = stop_if_load_disco
+
+ #: .. versionadded:: 0.8.0
+ #: if set to `True` (default) then the backend will raise a
+ #: BackendError in case of disconnected generator
+ self._stop_if_gen_disco = stop_if_gen_disco
+
self._aux_init_super(detailed_infos_for_cascading_failures,
can_be_copied,
solver_type,
@@ -62,8 +89,24 @@ def __init__(self,
tol,
turned_off_pv,
dist_slack_non_renew,
- use_static_gen)
-
+ use_static_gen,
+ loader_method,
+ loader_kwargs,
+ stop_if_load_disco,
+ stop_if_gen_disco)
+
+ #: .. versionadded:: 0.8.0
+ #: Which type of grid format can be read by your backend.
+ #: It is "json" if loaded from pandapower or
+ #: "xiidm" if loaded from pypowsybl.
+ self.supported_grid_format = None
+ if loader_method == "pandapower":
+ self.supported_grid_format = ("json", ) # new in 0.8.0
+ elif loader_method == "pypowsybl":
+ self.supported_grid_format = ("xiidm", ) # new in 0.8.0
+ else:
+ raise BackendError(f"Uknown loader_metho : '{loader_method}'")
+
# lazy loading because it crashes...
from lightsim2grid._utils import _DoNotUseAnywherePandaPowerBackend
from grid2op.Space import GridObjects # lazy import
@@ -177,7 +220,7 @@ def __init__(self,
# TODO and should rather be handled in pandapower backend
# backend SHOULD not do these kind of stuff
self._idx_hack_storage = []
-
+
def _aux_init_super(self,
detailed_infos_for_cascading_failures,
can_be_copied,
@@ -186,7 +229,11 @@ def _aux_init_super(self,
tol,
turned_off_pv,
dist_slack_non_renew,
- use_static_gen):
+ use_static_gen,
+ loader_method,
+ loader_kwargs,
+ stop_if_load_disco,
+ stop_if_gen_disco):
try:
# for grid2Op >= 1.7.1
Backend.__init__(self,
@@ -197,14 +244,18 @@ def _aux_init_super(self,
tol=tol,
turned_off_pv=turned_off_pv,
dist_slack_non_renew=dist_slack_non_renew,
- use_static_gen=use_static_gen
+ use_static_gen=use_static_gen,
+ loader_method=loader_method,
+ loader_kwargs=loader_kwargs,
+ stop_if_load_disco=stop_if_load_disco,
+ stop_if_gen_disco=stop_if_gen_disco,
)
except TypeError as exc_:
warnings.warn("Please use grid2op >= 1.7.1: with older grid2op versions, "
"you cannot set max_iter, tol nor solver_type arguments.")
Backend.__init__(self,
detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures)
-
+
def turnedoff_no_pv(self):
self._turned_off_pv = False
self._grid.turnedoff_no_pv()
@@ -402,14 +453,191 @@ def _assign_right_solver(self):
self._grid.change_solver(SolverType.KLU)
else:
self._grid.change_solver(SolverType.SparseLU)
+
+ def load_grid(self, path=None, filename=None):
+ if hasattr(type(self), "can_handle_more_than_2_busbar"):
+ # grid2op version >= 1.10.0 then we use this
+ self.can_handle_more_than_2_busbar()
- def load_grid(self, path=None, filename=None):
- # if self.init_pp_backend is None:
- self.init_pp_backend.load_grid(path, filename)
- self.can_output_theta = True # i can compute the "theta" and output it to grid2op
-
- self._grid = init(self.init_pp_backend._grid)
+ if self._loader_method == "pandapower":
+ self._load_grid_pandapower(path, filename)
+ elif self._loader_method == "pypowsybl":
+ self._load_grid_pypowsybl(path, filename)
+ else:
+ raise BackendError(f"Impossible to initialize the backend with '{self._loader_method}'")
+ self._grid.tell_solver_need_reset()
+
+ def _should_not_have_to_do_this(self, path=None, filename=None):
+ # included in grid2op now !
+ # but before `make_complete_path` was introduced we need to still
+ # be able to use lightsim2grid
+ import os
+ from grid2op.Exceptions import Grid2OpException
+ if path is None and filename is None:
+ raise Grid2OpException(
+ "You must provide at least one of path or file to load a powergrid."
+ )
+ if path is None:
+ full_path = filename
+ elif filename is None:
+ full_path = path
+ else:
+ full_path = os.path.join(path, filename)
+ if not os.path.exists(full_path):
+ raise Grid2OpException('There is no powergrid at "{}"'.format(full_path))
+ return full_path
+
+ def _aux_pypowsybl_init_substations(self, loader_kwargs):
+ # now handle the legacy "make as if there are 2 busbars per substation"
+ # as it is done with grid2Op simulated environment
+ if (("double_bus_per_sub" in loader_kwargs and loader_kwargs["double_bus_per_sub"]) or
+ ("n_busbar_per_sub" in loader_kwargs and loader_kwargs["n_busbar_per_sub"])):
+ bus_init = self._grid.get_bus_vn_kv()
+ orig_to_ls = np.array(self._grid._orig_to_ls)
+ bus_doubled = np.concatenate([bus_init for _ in range(self.n_busbar_per_sub)])
+ self._grid.init_bus(bus_doubled, 0, 0)
+ if hasattr(type(self), "can_handle_more_than_2_busbar"):
+ for i in range(self.__nb_bus_before * (self.n_busbar_per_sub - 1)):
+ self._grid.deactivate_bus(i + self.__nb_bus_before)
+ else:
+ for i in range(self.__nb_bus_before):
+ self._grid.deactivate_bus(i + self.__nb_bus_before)
+ new_orig_to_ls = np.concatenate([orig_to_ls + i * self.__nb_bus_before
+ for i in range(self.n_busbar_per_sub)]
+ )
+ self._grid._orig_to_ls = new_orig_to_ls
+
+ def _get_subid_from_buses_legacy(self, buses_sub_id, el_sub_df):
+ # buses_sub_id is the first element as returned by from_pypowsybl / init function
+ # el_sub_df is an element dataframe returned by the same function
+ tmp = pd.merge(el_sub_df.reset_index(), buses_sub_id, how="left", right_on="sub_id", left_on="sub_id")
+ res = tmp.drop_duplicates(subset='id').set_index("id").sort_index()["bus_id"].values
+ return res
+
+ def _load_grid_pypowsybl(self, path=None, filename=None):
+ from lightsim2grid.gridmodel.from_pypowsybl import init as init_pypow
+ import pypowsybl.network as pypow_net
+ loader_kwargs = {}
+ if self._loader_kwargs is not None:
+ loader_kwargs = self._loader_kwargs
+
+ try:
+ full_path = self.make_complete_path(path, filename)
+ except AttributeError as exc_:
+ warnings.warn("Please upgrade your grid2op version")
+ full_path = self._should_not_have_to_do_this(path, filename)
+
+ grid_tmp = pypow_net.load(full_path)
+ gen_slack_id = None
+ if "gen_slack_id" in loader_kwargs:
+ gen_slack_id = int(loader_kwargs["gen_slack_id"])
+ self._grid, subs_id = init_pypow(grid_tmp, gen_slack_id=gen_slack_id, sort_index=True, return_sub_id=True)
+ (buses_sub_id, gen_sub, load_sub, (lor_sub, tor_sub), (lex_sub, tex_sub), batt_sub, sh_sub, hvdc_sub_from_id, hvdc_sub_to_id) = subs_id
+ self.__nb_bus_before = len(self._grid.get_bus_vn_kv())
+ self._aux_pypowsybl_init_substations(loader_kwargs)
+ self._aux_setup_right_after_grid_init()
+
+ # mandatory for the backend
+ self.n_line = len(self._grid.get_lines()) + len(self._grid.get_trafos())
+ self.n_gen = len(self._grid.get_generators())
+ self.n_load = len(self._grid.get_loads())
+ self.n_storage = len(self._grid.get_storages())
+ self.n_shunt = len(self._grid.get_shunts())
+
+ df = grid_tmp.get_substations()
+ from_sub = True
+ if "use_buses_for_sub" in loader_kwargs and loader_kwargs["use_buses_for_sub"]:
+ df = grid_tmp.get_buses()
+ from_sub = False
+ self.n_sub = df.shape[0]
+ self.name_sub = ["sub_{}".format(i) for i, _ in enumerate(df.iterrows())]
+
+ if not from_sub:
+ # consider that each "bus" in the powsybl grid is a substation
+ # this is the "standard" behaviour for IEEE grid in grid2op
+ # but can be considered "legacy" behaviour for more realistic grid
+ this_load_sub = self._get_subid_from_buses_legacy(buses_sub_id, load_sub)
+ this_gen_sub = self._get_subid_from_buses_legacy(buses_sub_id, gen_sub)
+ this_lor_sub = self._get_subid_from_buses_legacy(buses_sub_id, lor_sub)
+ this_tor_sub = self._get_subid_from_buses_legacy(buses_sub_id, tor_sub)
+ this_lex_sub = self._get_subid_from_buses_legacy(buses_sub_id, lex_sub)
+ this_tex_sub = self._get_subid_from_buses_legacy(buses_sub_id, tex_sub)
+ this_batt_sub = self._get_subid_from_buses_legacy(buses_sub_id, batt_sub)
+ this_sh_sub = self._get_subid_from_buses_legacy(buses_sub_id, sh_sub)
+
+ self.load_to_subid = np.array(this_load_sub, dtype=dt_int)
+ self.gen_to_subid = np.array(this_gen_sub, dtype=dt_int)
+ self.line_or_to_subid = np.concatenate((this_lor_sub, this_tor_sub)).astype(dt_int)
+ self.line_ex_to_subid = np.concatenate((this_lex_sub, this_tex_sub)).astype(dt_int)
+ self.storage_to_subid = np.array(this_batt_sub, dtype=dt_int)
+ self.shunt_to_subid = np.array(this_sh_sub, dtype=dt_int)
+ else:
+ # TODO get back the sub id from the grid_tmp.get_substations()
+ # need to work on that grid2op side: different make sure the labelling of the buses are correct !
+ (buses_sub_id, gen_sub, load_sub, (lor_sub, tor_sub), (lex_sub, tex_sub), batt_sub, sh_sub, hvdc_sub_from_id, hvdc_sub_to_id) = subs_id
+ raise NotImplementedError("Today the only supported behaviour is to consider the 'buses' of the powsybl grid "
+ "are the 'substation' of this backend. "
+ "This will change in the future, but in the meantime please add "
+ "'use_buses_for_sub' = True in the `loader_kwargs` when loading "
+ "a lightsim2grid grid.")
+
+ # the names
+ use_grid2op_default_names = True
+ if "use_grid2op_default_names" in loader_kwargs and not loader_kwargs["use_grid2op_default_names"]:
+ use_grid2op_default_names = False
+
+ if use_grid2op_default_names:
+ self.name_load = np.array([f"load_{el.bus_id}_{id_obj}" for id_obj, el in enumerate(self._grid.get_loads())])
+ self.name_gen = np.array([f"gen_{el.bus_id}_{id_obj}" for id_obj, el in enumerate(self._grid.get_generators())])
+ self.name_line = np.array([f"{el.bus_or_id}_{el.bus_ex_id}_{id_obj}" for id_obj, el in enumerate(self._grid.get_lines())] +
+ [f"{el.bus_hv_id}_{el.bus_lv_id}_{id_obj}" for id_obj, el in enumerate(self._grid.get_trafos())])
+ self.name_storage = np.array([f"storage_{el.bus_id}_{id_obj}" for id_obj, el in enumerate(self._grid.get_storages())])
+ self.name_shunt = np.array([f"shunt_{el.bus_id}_{id_obj}" for id_obj, el in enumerate(self._grid.get_shunts())])
+ else:
+ self.name_load = np.array(load_sub.index)
+ self.name_gen = np.array(gen_sub.index)
+ self.name_line = np.concatenate((lor_sub.index, tor_sub.index))
+ self.name_storage = np.array(batt_sub.index)
+ self.name_shunt = np.array(sh_sub.index)
+
+ if "reconnect_disco_gen" in loader_kwargs and loader_kwargs["reconnect_disco_gen"]:
+ for el in self._grid.get_generators():
+ if not el.connected:
+ self._grid.reactivate_gen(el.id)
+ self._grid.change_bus_gen(el.id, self.gen_to_subid[el.id])
+
+ if "reconnect_disco_load" in loader_kwargs and loader_kwargs["reconnect_disco_load"]:
+ for el in self._grid.get_loads():
+ if not el.connected:
+ self._grid.reactivate_load(el.id)
+ self._grid.change_bus_load(el.id, self.load_to_subid[el.id])
+
+ # complete the other vectors
+ self._compute_pos_big_topo()
+
+ self.__nb_powerline = len(self._grid.get_lines())
+
+ # init this
+ self.prod_p = np.array([el.target_p_mw for el in self._grid.get_generators()], dtype=dt_float)
+ self.next_prod_p = np.array([el.target_p_mw for el in self._grid.get_generators()], dtype=dt_float)
+ self.nb_bus_total = len(self._grid.get_bus_vn_kv())
+
+ # and now things needed by the backend (legacy)
+ self._big_topo_to_obj = [(None, None) for _ in range(type(self).dim_topo)]
+ self.prod_pu_to_kv = 1.0 * self._grid.get_bus_vn_kv()[[el.bus_id for el in self._grid.get_generators()]]
+ self.prod_pu_to_kv = self.prod_pu_to_kv.astype(dt_float)
+
+ # TODO
+ max_not_too_max = (np.finfo(dt_float).max * 0.5 - 1.)
+ self.thermal_limit_a = max_not_too_max * np.ones(self.n_line, dtype=dt_float)
+ bus_vn_kv = np.array(self._grid.get_bus_vn_kv())
+ # shunt_bus_id = np.array([el.bus_id for el in self._grid.get_shunts()])
+ self._sh_vnkv = bus_vn_kv[self.shunt_to_subid]
+ self._aux_finish_setup_after_reading()
+
+ def _aux_setup_right_after_grid_init(self):
+ self._grid.set_n_sub(self.__nb_bus_before)
self._handle_turnedoff_pv()
self.available_solvers = self._grid.available_solvers()
@@ -428,7 +656,31 @@ def load_grid(self, path=None, filename=None):
# check that the solver type provided is installed with lightsim2grid
self._check_suitable_solver_type(self.__current_solver_type)
self._grid.change_solver(self.__current_solver_type)
+
+ # handle multiple busbar per substations
+ if hasattr(type(self), "can_handle_more_than_2_busbar"):
+ # grid2op version >= 1.10.0 then we use this
+ self._grid._max_nb_bus_per_sub = self.n_busbar_per_sub
+ self._grid.tell_solver_need_reset()
+
+ def _load_grid_pandapower(self, path=None, filename=None):
+ if hasattr(type(self), "can_handle_more_than_2_busbar"):
+ type(self.init_pp_backend).n_busbar_per_sub = self.n_busbar_per_sub
+ self.init_pp_backend.load_grid(path, filename)
+ self.can_output_theta = True # i can compute the "theta" and output it to grid2op
+
+ self._grid = init(self.init_pp_backend._grid)
+ self.__nb_bus_before = self.init_pp_backend.get_nb_active_bus()
+ self._aux_setup_right_after_grid_init()
+
+ # deactive the buses that have been added
+ for bus_id, bus_status in enumerate(self.init_pp_backend._grid.bus["in_service"].values):
+ if bus_status:
+ self._grid.reactivate_bus(bus_id)
+ else:
+ self._grid.deactivate_bus(bus_id)
+
self.n_line = self.init_pp_backend.n_line
self.n_gen = self.init_pp_backend.n_gen
self.n_load = self.init_pp_backend.n_load
@@ -475,11 +727,6 @@ def load_grid(self, path=None, filename=None):
self.thermal_limit_a = copy.deepcopy(self.init_pp_backend.thermal_limit_a)
- # deactive the buses that have been added
- nb_bus_init = self.init_pp_backend._grid.bus.shape[0] // 2
- for i in range(nb_bus_init):
- self._grid.deactivate_bus(i + nb_bus_init)
-
self.__nb_powerline = self.init_pp_backend._grid.line.shape[0]
self.__nb_bus_before = self.init_pp_backend.get_nb_active_bus()
self._init_bus_load = 1.0 * self.init_pp_backend._grid.load["bus"].values
@@ -508,108 +755,107 @@ def load_grid(self, path=None, filename=None):
tmp.reshape(-1, 1)), axis=-1)
self._big_topo_to_obj = [(None, None) for _ in range(self.dim_topo)]
+ self.prod_p = 1.0 * self.init_pp_backend._grid.gen["p_mw"].values
+ self.next_prod_p = 1.0 * self.init_pp_backend._grid.gen["p_mw"].values
+ self.n_shunt = self.init_pp_backend.n_shunt
+ self.shunt_to_subid = self.init_pp_backend.shunt_to_subid
+ self.name_shunt = self.init_pp_backend.name_shunt
self._compute_pos_big_topo()
+ if hasattr(self.init_pp_backend, "_sh_vnkv"):
+ # attribute has been added in grid2op ~1.3 or 1.4
+ self._sh_vnkv = self.init_pp_backend._sh_vnkv
+
+ self._aux_finish_setup_after_reading()
+ def _aux_finish_setup_after_reading(self):
# set up the "lightsim grid" accordingly
- self._grid.set_n_sub(self.__nb_bus_before)
- self._grid.set_load_pos_topo_vect(self.load_pos_topo_vect)
- self._grid.set_gen_pos_topo_vect(self.gen_pos_topo_vect)
- self._grid.set_line_or_pos_topo_vect(self.line_or_pos_topo_vect[:self.__nb_powerline])
- self._grid.set_line_ex_pos_topo_vect(self.line_ex_pos_topo_vect[:self.__nb_powerline])
- self._grid.set_trafo_hv_pos_topo_vect(self.line_or_pos_topo_vect[self.__nb_powerline:])
- self._grid.set_trafo_lv_pos_topo_vect(self.line_ex_pos_topo_vect[self.__nb_powerline:])
-
- self._grid.set_load_to_subid(self.load_to_subid)
- self._grid.set_gen_to_subid(self.gen_to_subid)
- self._grid.set_line_or_to_subid(self.line_or_to_subid[:self.__nb_powerline])
- self._grid.set_line_ex_to_subid(self.line_ex_to_subid[:self.__nb_powerline])
- self._grid.set_trafo_hv_to_subid(self.line_or_to_subid[self.__nb_powerline:])
- self._grid.set_trafo_lv_to_subid(self.line_ex_to_subid[self.__nb_powerline:])
+ cls = type(self)
+
+ self._grid.set_load_pos_topo_vect(cls.load_pos_topo_vect)
+ self._grid.set_gen_pos_topo_vect(cls.gen_pos_topo_vect)
+ self._grid.set_line_or_pos_topo_vect(cls.line_or_pos_topo_vect[:self.__nb_powerline])
+ self._grid.set_line_ex_pos_topo_vect(cls.line_ex_pos_topo_vect[:self.__nb_powerline])
+ self._grid.set_trafo_hv_pos_topo_vect(cls.line_or_pos_topo_vect[self.__nb_powerline:])
+ self._grid.set_trafo_lv_pos_topo_vect(cls.line_ex_pos_topo_vect[self.__nb_powerline:])
+
+ self._grid.set_load_to_subid(cls.load_to_subid)
+ self._grid.set_gen_to_subid(cls.gen_to_subid)
+ self._grid.set_line_or_to_subid(cls.line_or_to_subid[:self.__nb_powerline])
+ self._grid.set_line_ex_to_subid(cls.line_ex_to_subid[:self.__nb_powerline])
+ self._grid.set_trafo_hv_to_subid(cls.line_or_to_subid[self.__nb_powerline:])
+ self._grid.set_trafo_lv_to_subid(cls.line_ex_to_subid[self.__nb_powerline:])
# TODO storage check grid2op version and see if storage is available !
if self.__has_storage:
- self._grid.set_storage_to_subid(self.storage_to_subid)
- self._grid.set_storage_pos_topo_vect(self.storage_pos_topo_vect)
+ self._grid.set_storage_to_subid(cls.storage_to_subid)
+ self._grid.set_storage_pos_topo_vect(cls.storage_pos_topo_vect)
nm_ = "load"
- for load_id, pos_big_topo in enumerate(self.load_pos_topo_vect):
+ for load_id, pos_big_topo in enumerate(cls.load_pos_topo_vect):
self._big_topo_to_obj[pos_big_topo] = (load_id, nm_)
nm_ = "gen"
- for gen_id, pos_big_topo in enumerate(self.gen_pos_topo_vect):
+ for gen_id, pos_big_topo in enumerate(cls.gen_pos_topo_vect):
self._big_topo_to_obj[pos_big_topo] = (gen_id, nm_)
nm_ = "lineor"
- for l_id, pos_big_topo in enumerate(self.line_or_pos_topo_vect):
+ for l_id, pos_big_topo in enumerate(cls.line_or_pos_topo_vect):
self._big_topo_to_obj[pos_big_topo] = (l_id, nm_)
nm_ = "lineex"
- for l_id, pos_big_topo in enumerate(self.line_ex_pos_topo_vect):
+ for l_id, pos_big_topo in enumerate(cls.line_ex_pos_topo_vect):
self._big_topo_to_obj[pos_big_topo] = (l_id, nm_)
# TODO storage check grid2op version and see if storage is available !
if self.__has_storage:
nm_ = "storage"
- for l_id, pos_big_topo in enumerate(self.storage_pos_topo_vect):
+ for l_id, pos_big_topo in enumerate(cls.storage_pos_topo_vect):
self._big_topo_to_obj[pos_big_topo] = (l_id, nm_)
- self.prod_p = 1.0 * self.init_pp_backend._grid.gen["p_mw"].values
- self.next_prod_p = 1.0 * self.init_pp_backend._grid.gen["p_mw"].values
-
- # for shunts
- self.n_shunt = self.init_pp_backend.n_shunt
- self.shunt_to_subid = self.init_pp_backend.shunt_to_subid
- self.name_shunt = self.init_pp_backend.name_shunt
-
- if hasattr(self.init_pp_backend, "_sh_vnkv"):
- # attribute has been added in grid2op ~1.3 or 1.4
- self._sh_vnkv = self.init_pp_backend._sh_vnkv
-
- self.shunts_data_available = self.init_pp_backend.shunts_data_available
-
# number of object per bus, to activate, deactivate them
- self.nb_obj_per_bus = np.zeros(2 * self.__nb_bus_before, dtype=dt_int)
+ self.nb_obj_per_bus = np.zeros(2 * self.__nb_bus_before, dtype=dt_int).reshape(-1)
- self.topo_vect = np.ones(self.dim_topo, dtype=dt_int)
- if self.shunts_data_available:
- self.shunt_topo_vect = np.ones(self.n_shunt, dtype=dt_int)
+ self.topo_vect = np.ones(cls.dim_topo, dtype=dt_int).reshape(-1)
+
+ if type(self).shunts_data_available:
+ self.shunt_topo_vect = np.ones(cls.n_shunt, dtype=dt_int)
# shunts
- self.sh_p = np.full(self.n_shunt, dtype=dt_float, fill_value=np.NaN)
- self.sh_q = np.full(self.n_shunt, dtype=dt_float, fill_value=np.NaN)
- self.sh_v = np.full(self.n_shunt, dtype=dt_float, fill_value=np.NaN)
- self.sh_bus = np.full(self.n_shunt, dtype=dt_int, fill_value=-1)
-
- self.p_or = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
- self.q_or = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
- self.v_or = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
- self.a_or = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
- self.p_ex = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
- self.q_ex = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
- self.v_ex = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
- self.a_ex = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
-
- self.load_p = np.full(self.n_load, dtype=dt_float, fill_value=np.NaN)
- self.load_q = np.full(self.n_load, dtype=dt_float, fill_value=np.NaN)
- self.load_v = np.full(self.n_load, dtype=dt_float, fill_value=np.NaN)
-
- self.prod_p = np.full(self.n_gen, dtype=dt_float, fill_value=np.NaN)
- self.prod_q = np.full(self.n_gen, dtype=dt_float, fill_value=np.NaN)
- self.prod_v = np.full(self.n_gen, dtype=dt_float, fill_value=np.NaN)
+ self.sh_p = np.full(cls.n_shunt, dtype=dt_float, fill_value=np.NaN).reshape(-1)
+ self.sh_q = np.full(cls.n_shunt, dtype=dt_float, fill_value=np.NaN).reshape(-1)
+ self.sh_v = np.full(cls.n_shunt, dtype=dt_float, fill_value=np.NaN).reshape(-1)
+ self.sh_bus = np.full(cls.n_shunt, dtype=dt_int, fill_value=-1).reshape(-1)
+
+ self.p_or = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN).reshape(-1)
+ self.q_or = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN).reshape(-1)
+ self.v_or = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN).reshape(-1)
+ self.a_or = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN).reshape(-1)
+ self.p_ex = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN).reshape(-1)
+ self.q_ex = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN).reshape(-1)
+ self.v_ex = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN).reshape(-1)
+ self.a_ex = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN).reshape(-1)
+
+ self.load_p = np.full(cls.n_load, dtype=dt_float, fill_value=np.NaN).reshape(-1)
+ self.load_q = np.full(cls.n_load, dtype=dt_float, fill_value=np.NaN).reshape(-1)
+ self.load_v = np.full(cls.n_load, dtype=dt_float, fill_value=np.NaN).reshape(-1)
+
+ self.prod_p = np.full(cls.n_gen, dtype=dt_float, fill_value=np.NaN).reshape(-1)
+ self.prod_q = np.full(cls.n_gen, dtype=dt_float, fill_value=np.NaN).reshape(-1)
+ self.prod_v = np.full(cls.n_gen, dtype=dt_float, fill_value=np.NaN).reshape(-1)
- self.line_or_theta = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
- self.line_ex_theta = np.full(self.n_line, dtype=dt_float, fill_value=np.NaN)
- self.load_theta = np.full(self.n_load, dtype=dt_float, fill_value=np.NaN)
- self.gen_theta = np.full(self.n_gen, dtype=dt_float, fill_value=np.NaN)
+ self.line_or_theta = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN).reshape(-1)
+ self.line_ex_theta = np.full(cls.n_line, dtype=dt_float, fill_value=np.NaN).reshape(-1)
+ self.load_theta = np.full(cls.n_load, dtype=dt_float, fill_value=np.NaN).reshape(-1)
+ self.gen_theta = np.full(cls.n_gen, dtype=dt_float, fill_value=np.NaN).reshape(-1)
# TODO storage check grid2op version and see if storage is available !
if self.__has_storage:
- self.storage_p = np.full(self.n_storage, dtype=dt_float, fill_value=np.NaN)
- self.storage_q = np.full(self.n_storage, dtype=dt_float, fill_value=np.NaN)
- self.storage_v = np.full(self.n_storage, dtype=dt_float, fill_value=np.NaN)
- self.storage_theta = np.full(self.n_storage, dtype=dt_float, fill_value=np.NaN)
+ self.storage_p = np.full(cls.n_storage, dtype=dt_float, fill_value=np.NaN).reshape(-1)
+ self.storage_q = np.full(cls.n_storage, dtype=dt_float, fill_value=np.NaN).reshape(-1)
+ self.storage_v = np.full(cls.n_storage, dtype=dt_float, fill_value=np.NaN).reshape(-1)
+ self.storage_theta = np.full(cls.n_storage, dtype=dt_float, fill_value=np.NaN).reshape(-1)
self._count_object_per_bus()
- self._grid.tell_topo_changed()
+ self._grid.tell_solver_need_reset()
self.__me_at_init = self._grid.copy()
- self.__init_topo_vect = np.ones(self.dim_topo, dtype=dt_int)
+ self.__init_topo_vect = np.ones(cls.dim_topo, dtype=dt_int)
self.__init_topo_vect[:] = self.topo_vect
def assert_grid_correct_after_powerflow(self):
@@ -628,7 +874,7 @@ def assert_grid_correct_after_powerflow(self):
try:
# feature added in grid2op 1.4 or 1.5
_init_action_to_set = self.get_action_to_set()
- except TypeError:
+ except TypeError as exc_:
_init_action_to_set = self._get_action_to_set_deprecated()
self._init_action_to_set += _init_action_to_set
@@ -674,7 +920,7 @@ def _count_object_per_bus(self):
arr_ = self.line_ex_to_subid[is_connected] + self.__nb_bus_before * (arr_[is_connected] -1)
self.nb_obj_per_bus[arr_] += 1
- if self.shunts_data_available:
+ if type(self).shunts_data_available:
arr_ = self.shunt_topo_vect
is_connected = arr_ > 0
arr_ = self.shunt_to_subid[is_connected] + self.__nb_bus_before * (arr_[is_connected] - 1)
@@ -696,24 +942,29 @@ def apply_action(self, backendAction):
self.topo_vect[chgt] = backendAction.current_topo.values[chgt]
# update the injections
- self._grid.update_gens_p(backendAction.prod_p.changed,
- backendAction.prod_p.values)
- self._grid.update_gens_v(backendAction.prod_v.changed,
- backendAction.prod_v.values / self.prod_pu_to_kv)
- self._grid.update_loads_p(backendAction.load_p.changed,
- backendAction.load_p.values)
- self._grid.update_loads_q(backendAction.load_q.changed,
- backendAction.load_q.values)
+ try:
+ self._grid.update_gens_p(backendAction.prod_p.changed,
+ backendAction.prod_p.values)
+ self._grid.update_gens_v(backendAction.prod_v.changed,
+ backendAction.prod_v.values / self.prod_pu_to_kv)
+ self._grid.update_loads_p(backendAction.load_p.changed,
+ backendAction.load_p.values)
+ self._grid.update_loads_q(backendAction.load_q.changed,
+ backendAction.load_q.values)
+ except RuntimeError as exc_:
+ # see https://github.com/BDonnot/lightsim2grid/issues/66 (even though it's not a "bug" and has not been replicated)
+ raise BackendError(f"{exc_}") from exc_
+
if self.__has_storage:
- # TODO
- # reactivate the storage that i deactivate because of the "hack". See
- # for stor_id in self._idx_hack_storage:
- # self._grid.reactivate_storage(stor_id)
- self._grid.update_storages_p(backendAction.storage_power.changed,
- backendAction.storage_power.values)
+ try:
+ self._grid.update_storages_p(backendAction.storage_power.changed,
+ backendAction.storage_power.values)
+ except RuntimeError as exc_:
+ # modification of power of disconnected storage has no effect in lightsim2grid
+ pass
# handle shunts
- if self.shunts_data_available:
+ if type(self).shunts_data_available:
shunt_p, shunt_q, shunt_bus = backendAction.shunt_p, backendAction.shunt_q, backendAction.shunt_bus
# shunt topology
# (need to be done before to avoid error like "impossible to set reactive value of a disconnected shunt")
@@ -730,24 +981,6 @@ def apply_action(self, backendAction):
self._grid.change_p_shunt(sh_id, new_p)
for sh_id, new_q in shunt_q:
self._grid.change_q_shunt(sh_id, new_q)
-
- # TODO hack for storage units: if 0. production i pretend they are disconnected on the
- # TODO c++ side
- # this is to deal with the test that "if a storage unit is alone on a bus, but produces 0, then it's fine)
- # if self.__has_storage and self.n_storage > 0:
- # chgt = copy.copy(backendAction.current_topo.changed)
- # my_val = 1 * backendAction.current_topo.values
- # self._idx_hack_storage = np.where((backendAction.storage_power.values == 0.))[0]
- # idx_storage_topo = self.storage_pos_topo_vect[self._idx_hack_storage]
- # changed[idx_storage_topo] = my_val[idx_storage_topo] != -1
- # my_val[idx_storage_topo] = -1
- # else:
- # self._idx_hack_storage = []
- # chgt = backendAction.current_topo.changed
- # my_val = backendAction.current_topo.values
- # self._grid.update_topo(changed, my_val)
- # TODO c++ side: have a check to be sure that the set_***_pos_topo_vect and set_***_to_sub_id
- # TODO have been correctly called before calling the function self._grid.update_topo
self._handle_dist_slack()
@@ -770,20 +1003,21 @@ def runpf(self, is_dc=False):
V = self._grid.dc_pf(self.V, self.max_it, self.tol)
self._timer_solver += time.perf_counter() - tick
if V.shape[0] == 0:
- raise DivergingPowerFlow(f"Divergence of DC powerflow (non connected grid). Detailed error: {self._grid.get_dc_solver().get_error()}")
+ raise BackendError(f"Divergence of DC powerflow (non connected grid). Detailed error: {self._grid.get_dc_solver().get_error()}")
else:
if (self.V is None) or (self.V.shape[0] == 0):
# create the vector V as it is not created
self.V = np.ones(self.nb_bus_total, dtype=np.complex_) * self._grid.get_init_vm_pu()
-
if self.initdc:
self._grid.deactivate_result_computation()
# if I init with dc values, it should depends on previous state
self.V[:] = self._grid.get_init_vm_pu() # see issue 30
+ # print(f"{self.V[:14] = }")
Vdc = self._grid.dc_pf(copy.deepcopy(self.V), self.max_it, self.tol)
+ # print(f"{Vdc[:14] = }")
self._grid.reactivate_result_computation()
if Vdc.shape[0] == 0:
- raise DivergingPowerFlow(f"Divergence of DC powerflow (non connected grid) at the initialization of AC powerflow. Detailed error: {self._grid.get_dc_solver().get_error()}")
+ raise BackendError(f"Divergence of DC powerflow (non connected grid) at the initialization of AC powerflow. Detailed error: {self._grid.get_dc_solver().get_error()}")
V_init = Vdc
else:
V_init = copy.deepcopy(self.V)
@@ -792,14 +1026,24 @@ def runpf(self, is_dc=False):
V = self._grid.ac_pf(V_init, self.max_it, self.tol)
self._timer_solver += time.perf_counter() - tick
if V.shape[0] == 0:
- raise DivergingPowerFlow(f"Divergence of AC powerflow. Detailed error: {self._grid.get_solver().get_error()}")
+ raise BackendError(f"Divergence of AC powerflow. Detailed error: {self._grid.get_solver().get_error()}")
beg_postroc = time.perf_counter()
if is_dc:
self.comp_time += self._grid.get_dc_computation_time()
+ self.timer_gridmodel_xx_pf += self._grid.timer_last_dc_pf
else:
self.comp_time += self._grid.get_computation_time()
+ # NB get_computation_time returns "time_total_nr", which is
+ # defined in the powerflow algorithm and not on the linear solver.
+ # it takes into account everything needed to solve the powerflow
+ # once everything is passed to the solver.
+ # It does not take into account the time to format the data in the
+ # from the GridModel
+ self.timer_gridmodel_xx_pf += self._grid.timer_last_ac_pf
+ # timer_gridmodel_xx_pf takes all the time within the gridmodel "ac_pf"
+
self.V[:] = V
(self.p_or[:self.__nb_powerline],
self.q_or[:self.__nb_powerline],
@@ -830,35 +1074,40 @@ def runpf(self, is_dc=False):
self.prod_p[:], self.prod_q[:], self.prod_v[:] = self._grid.get_gen_res()
if self.__has_storage:
self.storage_p[:], self.storage_q[:], self.storage_v[:] = self._grid.get_storages_res()
+ self.storage_v[self.storage_v == -1.] = 0. # voltage is 0. for disconnected elements in grid2op
+
self.next_prod_p[:] = self.prod_p
- if np.any(~np.isfinite(self.load_v)) or np.any(self.load_v <= 0.):
+ if self._stop_if_load_disco and ((~np.isfinite(self.load_v)).any() or (self.load_v <= 0.).any()):
disco = (~np.isfinite(self.load_v)) | (self.load_v <= 0.)
load_disco = np.where(disco)[0]
self._timer_postproc += time.perf_counter() - beg_postroc
- raise DivergingPowerFlow(f"At least one load is disconnected (check loads {load_disco})")
- if np.any(~np.isfinite(self.prod_v)) or np.any(self.prod_v <= 0.):
+ raise BackendError(f"At least one load is disconnected (check loads {load_disco})")
+ if self._stop_if_gen_disco and ((~np.isfinite(self.prod_v)).any() or (self.prod_v <= 0.).any()):
disco = (~np.isfinite(self.prod_v)) | (self.prod_v <= 0.)
gen_disco = np.where(disco)[0]
self._timer_postproc += time.perf_counter() - beg_postroc
- raise DivergingPowerFlow(f"At least one generator is disconnected (check loads {gen_disco})")
+ raise BackendError(f"At least one generator is disconnected (check gen {gen_disco})")
# TODO storage case of divergence !
- if self.shunts_data_available:
+ if type(self).shunts_data_available:
self._set_shunt_info()
self._fill_theta()
+ if (self.line_or_theta >= 1e6).any() or (self.line_ex_theta >= 1e6).any():
+ raise BackendError(f"Some theta are above 1e6 which should not be happening !")
res = True
- self._grid.unset_topo_changed()
+ self._grid.unset_changes()
self._timer_postproc += time.perf_counter() - beg_postroc
except Exception as exc_:
# of the powerflow has not converged, results are Nan
- self._grid.tell_topo_changed()
+ self._grid.unset_changes()
self._fill_nans()
res = False
my_exc_ = exc_
-
+ if not isinstance(my_exc_, BackendError):
+ my_exc_ = BackendError(f"Converted the error of type {type(my_exc_)}, message was: {my_exc_}")
if is_dc:
# set back the solver to its previous state
self._grid.change_solver(self.__current_solver_type)
@@ -890,7 +1139,7 @@ def _fill_nans(self):
self.load_theta[:] = np.NaN
self.gen_theta[:] = np.NaN
- if self.shunts_data_available:
+ if type(self).shunts_data_available:
self.sh_p[:] = np.NaN
self.sh_q[:] = np.NaN
self.sh_v[:] = np.NaN
@@ -934,7 +1183,11 @@ def copy(self):
self.tol,
self._turned_off_pv,
self._dist_slack_non_renew,
- self._use_static_gen)
+ self._use_static_gen,
+ self._loader_method,
+ self._loader_kwargs,
+ self._stop_if_load_disco,
+ self._stop_if_gen_disco)
res.comp_time = self.comp_time
res.timer_gridmodel_xx_pf = self.timer_gridmodel_xx_pf
@@ -951,7 +1204,8 @@ def copy(self):
"_timer_preproc", "_timer_postproc", "_timer_solver",
"supported_grid_format",
"max_it", "tol", "_turned_off_pv", "_dist_slack_non_renew",
- "_use_static_gen"
+ "_use_static_gen", "_loader_method", "_loader_kwargs",
+ "_stop_if_load_disco", "_stop_if_gen_disco"
]
for attr_nm in li_regular_attr:
if hasattr(self, attr_nm):
@@ -974,7 +1228,7 @@ def copy(self):
]
for attr_nm in li_attr_npy:
if hasattr(self, attr_nm):
- # this test is needed for backward compatibility with other grid2op version
+ # this test is needed for backward compatibility with older grid2op version
setattr(res, attr_nm, copy.deepcopy(getattr(self, attr_nm)))
# copy class attribute for older grid2op version (did not use the class attribute)
@@ -1003,7 +1257,6 @@ def copy(self):
setattr(res, attr_nm, copy.deepcopy(getattr(self, attr_nm)))
###############
-
# handle the most complicated
res._grid = mygrid.copy()
res.__me_at_init = __me_at_init.copy() # this is const
@@ -1062,11 +1315,12 @@ def shunt_info(self):
def _set_shunt_info(self):
self.sh_p[:], self.sh_q[:], self.sh_v[:] = self._grid.get_shunts_res()
- shunt_bus = np.array([self._grid.get_bus_shunt(i) for i in range(self.n_shunt)], dtype=dt_int)
+ shunt_bus = np.array([self._grid.get_bus_shunt(i) for i in range(type(self).n_shunt)], dtype=dt_int)
res_bus = np.ones(shunt_bus.shape[0], dtype=dt_int) # by default all shunts are on bus one
res_bus[shunt_bus >= self.__nb_bus_before] = 2 # except the one that are connected to bus 2
res_bus[shunt_bus == -1] = -1 # or the one that are disconnected
self.sh_bus[:] = res_bus
+ self.sh_v[self.sh_v == -1.] = 0. # in grid2op disco element have voltage of 0. and -1.
def _disconnect_line(self, id_):
self.topo_vect[self.line_ex_pos_topo_vect[id_]] = -1
@@ -1082,11 +1336,13 @@ def get_current_solver_type(self):
def reset(self, grid_path, grid_filename=None):
self._fill_nans()
self._grid = self.__me_at_init.copy()
- self._grid.tell_topo_changed()
+ self._grid.unset_changes()
self._grid.change_solver(self.__current_solver_type)
self._handle_turnedoff_pv()
self.topo_vect[:] = self.__init_topo_vect
self.comp_time = 0.
+ self.timer_gridmodel_xx_pf = 0.
self._timer_postproc = 0.
self._timer_preproc = 0.
self._timer_solver = 0.
+ self._grid.tell_solver_need_reset()
diff --git a/lightsim2grid/securityAnalysis.py b/lightsim2grid/securityAnalysis.py
index aa555ab8..f9db2b1a 100644
--- a/lightsim2grid/securityAnalysis.py
+++ b/lightsim2grid/securityAnalysis.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2020, RTE (https://www.rte-france.com)
+# Copyright (c) 2023, RTE (https://www.rte-france.com)
# See AUTHORS.txt
# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
@@ -6,341 +6,8 @@
# SPDX-License-Identifier: MPL-2.0
# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
-__all__ = ["SecurityAnalysisCPP", "SecurityAnalysis"]
+from lightsim2grid.contingencyAnalysis import ContingencyAnalysisCPP, ContingencyAnalysis
-import os
-import warnings
-import copy
-import numpy as np
-from collections.abc import Iterable
-
-from lightsim2grid.lightSimBackend import LightSimBackend
-from lightsim2grid.solver import SolverType
-from lightsim2grid_cpp import SecurityAnalysisCPP
-
-
-class SecurityAnalysis(object):
- """
- This class allows to perform a "security analysis" from a given grid state.
-
- For now, you cannot change the grid state, and it only computes the security analysis with
- current flows at origin of powerlines.
-
- Feel free to post a feature request if you want to extend it.
-
- This class is used in 4 phases:
-
- 0) you create it from a grid2op environment (the grid topology will not be modified from this environment)
- 1) you add some contingencies to simulate
- 2) you start the simulation
- 3) you read back the results
-
-
- Examples
- --------
- An example is given here
-
- .. code-block:: python
-
- import grid2op
- from lightsim2grid import SecurityAnalysis
- from lightsim2grid import LightSimBackend
- env_name = ...
- env = grid2op.make(env_name, backend=LightSimBackend())
-
- 0) you create
- security_analysis = SecurityAnalysis(env)
-
- 1) you add some contingencies to simulate
- security_analysis.add_multiple_contingencies(...) # or security_analysis.add_single_contingency(...)
-
- 2) you start the simulation (done automatically)
- 3) you read back the results
- res_p, res_a, res_v = security_analysis.get_flows()
-
- # in this results, then
- # res_a[row_id] will be the flows, on all powerline corresponding to the `row_id` contingency.
- # you can retrieve it with `security_analysis.contingency_order[row_id]`
-
- Notes
- ------
-
- Sometimes, the behaviour might differ from grid2op. For example, if simulating a contingency
- leads to a non connected grid, then this function will return "Nan" for the flows and 0. for
- the voltages.
-
- In grid2op, it would be, in this case, 0. for the flows and 0. for the voltages.
-
- """
- STR_TYPES = (str, np.str_) # np.str deprecated in numpy 1.20 and earlier versions not supported anyway
-
- def __init__(self, grid2op_env):
- if not isinstance(grid2op_env.backend, LightSimBackend):
- raise RuntimeError("This class only works with LightSimBackend")
- self.grid2op_env = grid2op_env.copy()
- self.computer = SecurityAnalysisCPP(self.grid2op_env.backend._grid)
- self._contingency_order = {} # key: contingency (as tuple), value: order in which it is entered
- self._all_contingencies = []
- self.__computed = False
- self._vs = None
- self._ampss = None
-
- self.available_solvers = self.computer.available_solvers()
- if SolverType.KLU in self.available_solvers:
- # use the faster KLU if available
- self.computer.change_solver(SolverType.KLU)
-
- @property
- def all_contingencies(self):
- return copy.deepcopy(self._all_contingencies)
-
- @all_contingencies.setter
- def all_contingencies(self, val):
- raise RuntimeError("Impossible to add new topologies like this. Please use `add_single_contingency` "
- "or `add_multiple_contingencies`.")
-
- def clear(self):
- """
- Clear the list of contingencies to simulate
- """
- self.computer.clear()
- self._contingency_order = {}
- self.__computed = False
- self._vs = None
- self._ampss = None
- self._all_contingencies = []
-
- def _single_cont_to_li_int(self, single_cont):
- li_disc = []
- if isinstance(single_cont, int):
- single_cont = [single_cont]
-
- for stuff in single_cont:
- if isinstance(stuff, type(self).STR_TYPES):
- stuff = np.where(self.grid2op_env.name_line == stuff)
- stuff = stuff[0]
- if stuff.size == 0:
- # name is not found
- raise RuntimeError(f"Impossible to find a powerline named \"{stuff}\" in the environment")
- stuff = int(stuff[0])
- else:
- stuff = int(stuff)
- li_disc.append(stuff)
- return li_disc
-
- def add_single_contingency(self, *args):
- """
- This function allows to add a single contingency specified by either the powerlines names
- (which should match env.name_line) or by their ID.
-
- The contingency added can be a "n-1" which will simulate a single powerline disconnection
- or a "n-k" which will simulate the disconnection of multiple powerlines.
-
- It does not accept any keword arguments.
-
- Examples
- --------
-
- .. code-block:: python
-
- import grid2op
- from lightsim2grid import SecurityAnalysis
- from lightsim2grid import LightSimBackend
- env_name = ...
- env = grid2op.make(env_name, backend=LightSimBackend())
-
- security_anlysis = SecurityAnalysis(env)
- # the single (n-1) contingency "disconnect powerline 0" is added
- security_anlysis.add_single_contingency(0)
-
- # add the single (n-1) contingency "disconnect line 1
- security_anlysis.add_single_contingency(env.name_line[1])
-
- # add a single contingency that disconnect powerline 2 and 3 at the same time
- security_anlysis.add_single_contingency(env.name_line[2], 3)
-
- Notes
- -----
- If it raises an error for a given contingency, the object might be not properly initialized.
- In this case, we recommend you to clear it (using the `clear()` method and to attempt to
- add contingencies again.)
-
- """
- li_disc = self._single_cont_to_li_int(args)
- li_disc_tup = tuple(li_disc)
- if li_disc_tup not in self._contingency_order:
- # this is really the first time this contingency is seen
- try:
- self.computer.add_nk(li_disc)
- my_id = len(self._contingency_order)
- self._contingency_order[li_disc_tup] = my_id
- self._all_contingencies.append(li_disc_tup)
- except Exception as exc_:
- raise RuntimeError(f"Impossible to add the contingency {args}. The most likely cause "
- f"is that you try to disconnect a powerline that is not present "
- f"on the grid") from exc_
-
- def add_multiple_contingencies(self, *args):
- """
- This function will add multiple contingencies at the same time.
-
- This code is equivalent to:
-
- .. code-block:: python
-
- for single_cont in args:
- self.add_single_contingency(single_cont)
-
- It does not accept any keword arguments.
-
- Examples
- --------
-
- .. code-block:: python
-
- import grid2op
- from lightsim2grid import SecurityAnalysis
- from lightsim2grid import LightSimBackend
- env_name = ...
- env = grid2op.make(env_name, backend=LightSimBackend())
-
- security_anlysis = SecurityAnalysis(env)
-
- # add a single contingency that disconnect powerline 2 and 3 at the same time
- security_anlysis.add_single_contingency(env.name_line[2], 3)
-
- # add a multiple contingencies the first one disconnect powerline 2 and
- # and the second one disconnect powerline 3
- security_anlysis.add_multiple_contingencies(env.name_line[2], 3)
- """
- for single_cont in args:
- if isinstance(single_cont, Iterable) and not isinstance(single_cont, type(self).STR_TYPES):
- # this is a contingency consisting in cutting multiple powerlines
- self.add_single_contingency(*single_cont)
- else:
- # this is likely an int or a string representing a contingency
- self.add_single_contingency(single_cont)
-
- def add_all_n1_contingencies(self):
- """
- This method registers as the contingencies that will be computed all the contingencies that disconnects 1 powerline
-
- This is equivalent to:
-
- .. code-block:: python
-
- for single_cont_id in range(env.n_line):
- self.add_single_contingency(single_cont_id)
- """
- for single_cont_id in range(self.grid2op_env.n_line):
- self.add_single_contingency(single_cont_id)
-
- def get_flows(self, *args):
- """
- Retrieve the flows after each contingencies has been simulated.
-
- Each row of the resulting flow matrix will correspond to a contingency simulated in the arguments.
-
- You can require only the result on some contingencies with the `args` argument, but in each case, all the results will
- be computed. If you don't specify anything, the results will be returned for all contingencies (which we recommend to do)
-
- Examples
- --------
-
- .. code-block:: python
-
- import grid2op
- from lightsim2grid import SecurityAnalysis
- from lightsim2grid import LightSimBackend
- env_name = ...
- env = grid2op.make(env_name, backend=LightSimBackend())
-
- security_analysis = SecurityAnalysis(env)
- security_analysis.add_multiple_contingencies(...) # or security_analysis.add_single_contingency(...)
- res_p, res_a, res_v = security_analysis.get_flows()
-
- # in this results, then
- # res_a[row_id] will be the flows, on all powerline corresponding to the `row_id` contingency.
- # you can retrieve it with `security_analysis.contingency_order[row_id]`
- """
-
- all_defaults = self.computer.my_defaults()
- if len(args) == 0:
- # default: i consider all contingencies
- orders_ = np.zeros(len(all_defaults), dtype=int)
- for id_cpp, cont_ in enumerate(all_defaults):
- tup_ = tuple(cont_)
- orders_[self._contingency_order[tup_]] = id_cpp
- else:
- # a list of interesting contingencies has been provided
- orders_ = np.zeros(len(args), dtype=int)
- all_defaults = [tuple(cont) for cont in all_defaults]
- for id_me, cont_ in enumerate(args):
- cont_li = self._single_cont_to_li_int(cont_)
- tup_ = tuple(cont_li)
- if tup_ not in self._contingency_order:
- raise RuntimeError(f"Contingency {cont_} is not simulated by this class. Have you called "
- f"`add_single_contingency` or `add_multiple_contingencies` ?")
- id_cpp = all_defaults.index(tup_)
- orders_[id_me] = id_cpp
-
- if not self.__computed:
- self.compute_V()
- self.compute_A()
- self.compute_P()
-
- return self._mws[orders_], self._ampss[orders_], self._vs[orders_]
-
- def compute_V(self):
- """
- This function allows to retrieve the complex voltage at each bus of the grid for each contingency.
-
- .. warning:: Order of the results
-
- The order in which the results are returned is NOT necessarily the order in which the contingencies have
- been entered. Please use `get_flows()` method for easier reading back of the results
-
- """
- v_init = self.grid2op_env.backend.V
- self.computer.compute(v_init,
- self.grid2op_env.backend.max_it,
- self.grid2op_env.backend.tol)
- self._vs = self.computer.get_voltages()
- self.__computed = True
- return self._vs
-
- def compute_A(self):
- """
- This function returns the current flows (in Amps, A) at the origin / high voltage side
-
- .. warning:: Order of the results
-
- The order in which the results are returned is NOT necessarily the order in which the contingencies have
- been entered. Please use `get_flows()` method for easier reading back of the results !
-
- """
- if not self.__computed:
- raise RuntimeError("This function can only be used if compute_V has been sucessfully called")
- self._ampss = 1e3 * self.computer.compute_flows()
- return self._ampss
-
- def compute_P(self):
- """
- This function returns the active power flows (in MW) at the origin / high voltage side
-
- .. warning:: Order of the results
-
- The order in which the results are returned is NOT necessarily the order in which the contingencies have
- been entered. Please use `get_flows()` method for easier reading back of the results !
-
- """
- if not self.__computed:
- raise RuntimeError("This function can only be used if compute_V has been sucessfully called")
- self._mws = 1.0 * self.computer.compute_power_flows()
- return self._mws
-
- def close(self):
- """permanently close the object"""
- self.grid2op_env.close()
- self.clear()
- self.computer.close()
+# Deprecated now, will be removed
+SecurityAnalysisCPP = ContingencyAnalysisCPP
+SecurityAnalysis = ContingencyAnalysis
diff --git a/lightsim2grid/tests/case_14_iidm/grid.xiidm b/lightsim2grid/tests/case_14_iidm/grid.xiidm
new file mode 100644
index 00000000..3b0bb3dc
--- /dev/null
+++ b/lightsim2grid/tests/case_14_iidm/grid.xiidm
@@ -0,0 +1,147 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/load_p.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/load_p.csv.bz2
new file mode 100644
index 00000000..cb68d027
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/load_p.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/load_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/load_p_forecasted.csv.bz2
new file mode 100644
index 00000000..19c21de8
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/load_p_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/load_q.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/load_q.csv.bz2
new file mode 100644
index 00000000..35d39813
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/load_q.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/load_q_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/load_q_forecasted.csv.bz2
new file mode 100644
index 00000000..6c262e2d
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/load_q_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/prod_p.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/prod_p.csv.bz2
new file mode 100644
index 00000000..c2f9e044
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/prod_p.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/prod_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/prod_p_forecasted.csv.bz2
new file mode 100644
index 00000000..c7935144
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/prod_p_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/prod_v.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/prod_v.csv.bz2
new file mode 100644
index 00000000..79c31540
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/prod_v.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/prod_v_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/prod_v_forecasted.csv.bz2
new file mode 100644
index 00000000..79c31540
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/prod_v_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/start_datetime.info b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/start_datetime.info
new file mode 100644
index 00000000..5e520426
--- /dev/null
+++ b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/start_datetime.info
@@ -0,0 +1 @@
+2019-01-11 23:55
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/time_interval.info b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/time_interval.info
new file mode 100644
index 00000000..beb9b901
--- /dev/null
+++ b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-12/time_interval.info
@@ -0,0 +1 @@
+00:05
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/load_p.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/load_p.csv.bz2
new file mode 100644
index 00000000..488a31b2
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/load_p.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/load_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/load_p_forecasted.csv.bz2
new file mode 100644
index 00000000..8e341212
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/load_p_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/load_q.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/load_q.csv.bz2
new file mode 100644
index 00000000..887ff90b
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/load_q.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/load_q_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/load_q_forecasted.csv.bz2
new file mode 100644
index 00000000..74f6595f
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/load_q_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/prod_p.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/prod_p.csv.bz2
new file mode 100644
index 00000000..a8f9567a
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/prod_p.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/prod_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/prod_p_forecasted.csv.bz2
new file mode 100644
index 00000000..8a511985
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/prod_p_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/prod_v.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/prod_v.csv.bz2
new file mode 100644
index 00000000..79c31540
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/prod_v.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/prod_v_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/prod_v_forecasted.csv.bz2
new file mode 100644
index 00000000..79c31540
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/prod_v_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/start_datetime.info b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/start_datetime.info
new file mode 100644
index 00000000..d1822dcd
--- /dev/null
+++ b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/start_datetime.info
@@ -0,0 +1 @@
+2019-01-12 23:55
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/time_interval.info b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/time_interval.info
new file mode 100644
index 00000000..beb9b901
--- /dev/null
+++ b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-13/time_interval.info
@@ -0,0 +1 @@
+00:05
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/load_p.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/load_p.csv.bz2
new file mode 100644
index 00000000..3ab5b445
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/load_p.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/load_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/load_p_forecasted.csv.bz2
new file mode 100644
index 00000000..1db273e5
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/load_p_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/load_q.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/load_q.csv.bz2
new file mode 100644
index 00000000..94eb8a08
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/load_q.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/load_q_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/load_q_forecasted.csv.bz2
new file mode 100644
index 00000000..4b46e816
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/load_q_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/prod_p.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/prod_p.csv.bz2
new file mode 100644
index 00000000..a328b067
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/prod_p.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/prod_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/prod_p_forecasted.csv.bz2
new file mode 100644
index 00000000..b0c60dd2
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/prod_p_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/prod_v.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/prod_v.csv.bz2
new file mode 100644
index 00000000..79c31540
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/prod_v.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/prod_v_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/prod_v_forecasted.csv.bz2
new file mode 100644
index 00000000..79c31540
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/prod_v_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/start_datetime.info b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/start_datetime.info
new file mode 100644
index 00000000..89b5afa8
--- /dev/null
+++ b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/start_datetime.info
@@ -0,0 +1 @@
+2019-01-13 23:55
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/time_interval.info b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/time_interval.info
new file mode 100644
index 00000000..beb9b901
--- /dev/null
+++ b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-14/time_interval.info
@@ -0,0 +1 @@
+00:05
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/load_p.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/load_p.csv.bz2
new file mode 100644
index 00000000..2eed48e9
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/load_p.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/load_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/load_p_forecasted.csv.bz2
new file mode 100644
index 00000000..54e50cfe
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/load_p_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/load_q.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/load_q.csv.bz2
new file mode 100644
index 00000000..ed0e14be
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/load_q.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/load_q_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/load_q_forecasted.csv.bz2
new file mode 100644
index 00000000..1b2a3313
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/load_q_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/prod_p.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/prod_p.csv.bz2
new file mode 100644
index 00000000..9a8f00c5
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/prod_p.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/prod_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/prod_p_forecasted.csv.bz2
new file mode 100644
index 00000000..2de34263
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/prod_p_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/prod_v.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/prod_v.csv.bz2
new file mode 100644
index 00000000..79c31540
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/prod_v.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/prod_v_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/prod_v_forecasted.csv.bz2
new file mode 100644
index 00000000..79c31540
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/prod_v_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/start_datetime.info b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/start_datetime.info
new file mode 100644
index 00000000..db8513cf
--- /dev/null
+++ b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/start_datetime.info
@@ -0,0 +1 @@
+2019-01-14 23:55
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/time_interval.info b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/time_interval.info
new file mode 100644
index 00000000..beb9b901
--- /dev/null
+++ b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-15/time_interval.info
@@ -0,0 +1 @@
+00:05
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/load_p.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/load_p.csv.bz2
new file mode 100644
index 00000000..28692d9c
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/load_p.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/load_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/load_p_forecasted.csv.bz2
new file mode 100644
index 00000000..060dc19e
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/load_p_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/load_q.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/load_q.csv.bz2
new file mode 100644
index 00000000..9bb5f51e
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/load_q.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/load_q_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/load_q_forecasted.csv.bz2
new file mode 100644
index 00000000..857fca00
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/load_q_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/prod_p.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/prod_p.csv.bz2
new file mode 100644
index 00000000..311938f6
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/prod_p.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/prod_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/prod_p_forecasted.csv.bz2
new file mode 100644
index 00000000..b0912c9a
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/prod_p_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/prod_v.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/prod_v.csv.bz2
new file mode 100644
index 00000000..79c31540
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/prod_v.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/prod_v_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/prod_v_forecasted.csv.bz2
new file mode 100644
index 00000000..79c31540
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/prod_v_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/start_datetime.info b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/start_datetime.info
new file mode 100644
index 00000000..f10bfee6
--- /dev/null
+++ b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/start_datetime.info
@@ -0,0 +1 @@
+2019-01-15 23:55
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/time_interval.info b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/time_interval.info
new file mode 100644
index 00000000..beb9b901
--- /dev/null
+++ b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-16/time_interval.info
@@ -0,0 +1 @@
+00:05
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/load_p.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/load_p.csv.bz2
new file mode 100644
index 00000000..5ca2c322
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/load_p.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/load_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/load_p_forecasted.csv.bz2
new file mode 100644
index 00000000..8c82c20f
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/load_p_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/load_q.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/load_q.csv.bz2
new file mode 100644
index 00000000..939d6d6a
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/load_q.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/load_q_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/load_q_forecasted.csv.bz2
new file mode 100644
index 00000000..8a4447fe
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/load_q_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/prod_p.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/prod_p.csv.bz2
new file mode 100644
index 00000000..cc177364
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/prod_p.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/prod_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/prod_p_forecasted.csv.bz2
new file mode 100644
index 00000000..ed4b30c2
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/prod_p_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/prod_v.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/prod_v.csv.bz2
new file mode 100644
index 00000000..79c31540
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/prod_v.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/prod_v_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/prod_v_forecasted.csv.bz2
new file mode 100644
index 00000000..79c31540
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/prod_v_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/start_datetime.info b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/start_datetime.info
new file mode 100644
index 00000000..58683364
--- /dev/null
+++ b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/start_datetime.info
@@ -0,0 +1 @@
+2019-01-16 23:55
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/time_interval.info b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/time_interval.info
new file mode 100644
index 00000000..beb9b901
--- /dev/null
+++ b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-17/time_interval.info
@@ -0,0 +1 @@
+00:05
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/load_p.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/load_p.csv.bz2
new file mode 100644
index 00000000..b49e835d
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/load_p.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/load_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/load_p_forecasted.csv.bz2
new file mode 100644
index 00000000..9b71a7ab
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/load_p_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/load_q.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/load_q.csv.bz2
new file mode 100644
index 00000000..fefafb55
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/load_q.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/load_q_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/load_q_forecasted.csv.bz2
new file mode 100644
index 00000000..84b4bca3
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/load_q_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/prod_p.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/prod_p.csv.bz2
new file mode 100644
index 00000000..9c832827
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/prod_p.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/prod_p_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/prod_p_forecasted.csv.bz2
new file mode 100644
index 00000000..b8120c0b
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/prod_p_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/prod_v.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/prod_v.csv.bz2
new file mode 100644
index 00000000..79c31540
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/prod_v.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/prod_v_forecasted.csv.bz2 b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/prod_v_forecasted.csv.bz2
new file mode 100644
index 00000000..79c31540
Binary files /dev/null and b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/prod_v_forecasted.csv.bz2 differ
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/start_datetime.info b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/start_datetime.info
new file mode 100644
index 00000000..d4d158ac
--- /dev/null
+++ b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/start_datetime.info
@@ -0,0 +1 @@
+2019-01-17 23:55
diff --git a/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/time_interval.info b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/time_interval.info
new file mode 100644
index 00000000..beb9b901
--- /dev/null
+++ b/lightsim2grid/tests/case_14_storage_iidm/chronics/2019-01-18/time_interval.info
@@ -0,0 +1 @@
+00:05
diff --git a/lightsim2grid/tests/case_14_storage_iidm/config.py b/lightsim2grid/tests/case_14_storage_iidm/config.py
new file mode 100644
index 00000000..afefe03d
--- /dev/null
+++ b/lightsim2grid/tests/case_14_storage_iidm/config.py
@@ -0,0 +1,40 @@
+from grid2op.Action import PowerlineChangeDispatchAndStorageAction
+from grid2op.Reward import L2RPNReward
+from grid2op.Rules import DefaultRules
+from grid2op.Chronics import Multifolder
+from grid2op.Chronics import GridStateFromFileWithForecasts
+from grid2op.Backend import PandaPowerBackend
+
+config = {
+ "backend": PandaPowerBackend,
+ "action_class": PowerlineChangeDispatchAndStorageAction,
+ "observation_class": None,
+ "reward_class": L2RPNReward,
+ "gamerules_class": DefaultRules,
+ "chronics_class": Multifolder,
+ "grid_value_class": GridStateFromFileWithForecasts,
+ "volagecontroler_class": None,
+ "thermal_limits": [
+ 541.0,
+ 450.0,
+ 375.0,
+ 636.0,
+ 175.0,
+ 285.0,
+ 335.0,
+ 657.0,
+ 496.0,
+ 827.0,
+ 442.0,
+ 641.0,
+ 840.0,
+ 156.0,
+ 664.0,
+ 235.0,
+ 119.0,
+ 179.0,
+ 1986.0,
+ 1572.0,
+ ],
+ "names_chronics_to_grid": None,
+}
diff --git a/lightsim2grid/tests/case_14_storage_iidm/convert_from_grid2op.py b/lightsim2grid/tests/case_14_storage_iidm/convert_from_grid2op.py
new file mode 100644
index 00000000..47e14243
--- /dev/null
+++ b/lightsim2grid/tests/case_14_storage_iidm/convert_from_grid2op.py
@@ -0,0 +1,293 @@
+# Copyright (c) 2023, RTE (https://www.rte-france.com)
+# See AUTHORS.txt
+# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
+# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
+# you can obtain one at http://mozilla.org/MPL/2.0/.
+# SPDX-License-Identifier: MPL-2.0
+# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
+
+import pandapower as pp
+import pypowsybl as pypo
+import pandas as pd
+import numpy as np
+import pypowsybl.network as pypo_n
+import grid2op
+
+from lightsim2grid.gridmodel import init as init_from_pp
+from lightsim2grid.gridmodel.from_pypowsybl import init as init_from_pypo
+
+
+
+def get_voltage_level_id(el, nb_dig_bus):
+ return f'vl_{f"{el}".zfill(nb_dig_bus)}'
+
+
+def get_bus_id(el, nb_dig_bus):
+ return f'bus_{f"{el}".zfill(nb_dig_bus)}'
+
+env = grid2op.make("educ_case14_storage", test=True)
+pp_grid = env.backend._grid
+pypo_grid = pypo_n.create_empty() # network_id='educ_case14_storage'
+
+# add substation NO BECAUSE pypowsybl call "site" substations
+for el in range(env.n_sub):
+ pypo_grid.create_substations(id=f"sub_{el}", name=f"sub_{el}")
+
+# pypowsybl._pypowsybl.PyPowsyblError: Could not create transformer 3_6_0: both voltage ids must be on the same substation
+corresp_sub = {6 : 3, 8 : 3, 5: 4, 7 : 3}
+
+nb_dig_bus = len(str(env.n_sub))
+# then add voltage levels
+for el in range(env.n_sub):
+ nominal_v = pp_grid.bus.iloc[el]["vn_kv"]
+ pypo_grid.create_voltage_levels(id=get_voltage_level_id(el, nb_dig_bus),
+ substation_id=f"sub_{el}" if not el in corresp_sub else f"sub_{corresp_sub[el]}",
+ name=f"vl_{el}",
+ topology_kind="BUS_BREAKER",
+ nominal_v=nominal_v,
+ low_voltage_limit= 0.90 * nominal_v,
+ high_voltage_limit= 1.1 * nominal_v)
+
+# add the buses (not modified grid)
+for el in range(env.n_sub):
+ pypo_grid.create_buses(id=get_bus_id(el, nb_dig_bus),
+ voltage_level_id=get_voltage_level_id(el, nb_dig_bus))
+
+# add loads
+nb_dig = len(str(env.n_load))
+for el in range(env.n_load):
+ el_pp = pp_grid.load.iloc[el]
+ pypo_grid.create_loads(id=f'load_{f"{el}".zfill(nb_dig)}',
+ name=f"load_{el_pp['bus']}_{el}",
+ voltage_level_id=get_voltage_level_id(el_pp["bus"], nb_dig_bus),
+ bus_id=get_bus_id(el_pp["bus"], nb_dig_bus),
+ p0=el_pp["p_mw"],
+ q0=el_pp["q_mvar"],
+ )
+
+# add generators
+nb_dig = len(str(env.n_gen))
+for el in range(env.n_gen):
+ el_pp = pp_grid.gen.iloc[el]
+ tg_v_pu = el_pp["vm_pu"]
+ tg_v_kv = tg_v_pu * pp_grid.bus.loc[el_pp['bus']]["vn_kv"]
+ pypo_grid.create_generators(id=f'gen_{f"{el}".zfill(nb_dig)}',
+ voltage_level_id=get_voltage_level_id(el_pp['bus'], nb_dig_bus),
+ bus_id=get_bus_id(el_pp["bus"], nb_dig_bus),
+ max_p=9999999.,
+ min_p=0.,
+ target_p=el_pp["p_mw"],
+ target_v=tg_v_kv,
+ voltage_regulator_on=True,
+ )
+
+# add storage units
+nb_dig = len(str(env.n_storage))
+for el in range(env.n_storage):
+ el_pp = pp_grid.storage.iloc[el]
+ pypo_grid.create_batteries(id=f'storage_{f"{el}".zfill(nb_dig)}',
+ name=f"storage_{el_pp['bus']}_{el}",
+ voltage_level_id=get_voltage_level_id(el_pp['bus'], nb_dig_bus),
+ bus_id=get_bus_id(el_pp["bus"], nb_dig_bus),
+ min_p=-9999999.,
+ max_p=9999999.,
+ target_p=el_pp["p_mw"],
+ target_q=0.,
+ )
+
+# add shunts
+nb_dig = len(str(env.n_shunt))
+shunt_df = pd.DataFrame({"id": [],
+ "voltage_level_id": [],
+ "bus_id" : [],
+ "name": [],
+ "model_type": [],
+ "section_count": []
+ })
+model_df = pd.DataFrame({'id': [],
+ "g_per_section": [],
+ "b_per_section": [],
+ "max_section_count": []})
+for el in range(env.n_shunt):
+ el_pp = pp_grid.shunt.iloc[el]
+ shunt_kv = el_pp["vn_kv"]
+ id_ = f'shunt_{f"{el}".zfill(nb_dig)}'
+ sh_dict = {"id": id_,
+ "voltage_level_id": get_voltage_level_id(el_pp['bus'], nb_dig_bus),
+ "bus_id": get_bus_id(el_pp["bus"], nb_dig_bus),
+ "name": f"shunt_{el_pp['bus']}_{el}",
+ "model_type": "LINEAR",
+ "section_count": 1
+ }
+ mod_dict = {"id": id_,
+ "g_per_section": -el_pp["p_mw"] / shunt_kv**2,
+ "b_per_section": -el_pp["q_mvar"] / shunt_kv**2,
+ "max_section_count": 1}
+ shunt_df = pd.concat((shunt_df, pd.DataFrame({k: [v] for k, v in sh_dict.items()})))
+ model_df = pd.concat((model_df, pd.DataFrame({k: [v] for k, v in mod_dict.items()})))
+shunt_df["id"] = shunt_df["id"].astype(str)
+shunt_df.set_index("id", inplace=True)
+shunt_df["section_count"] = shunt_df["section_count"].astype(int)
+model_df["id"] = model_df["id"].astype(str)
+model_df.set_index("id", inplace=True)
+model_df["max_section_count"] = model_df["max_section_count"].astype(int)
+pypo_grid.create_shunt_compensators(shunt_df, model_df)
+
+# powerlines
+f_hz = 50.
+nb_dig = len(str(pp_grid.line.shape[0]))
+for el in range(pp_grid.line.shape[0]):
+ el_pp = pp_grid.line.iloc[el]
+ id_ = f'line_{f"{el}".zfill(nb_dig)}'
+ nm_ = f"{el_pp['from_bus']}_{el_pp['to_bus']}_{el}"
+ length_ = el_pp["length_km"]
+ line_b = el_pp["c_nf_per_km"] * length_ * 2.0 * f_hz * np.pi * 1e-9
+ pypo_grid.create_lines(id=id_,
+ name=nm_,
+ voltage_level1_id=get_voltage_level_id(el_pp['from_bus'], nb_dig_bus),
+ bus1_id=get_bus_id(el_pp["from_bus"], nb_dig_bus),
+ voltage_level2_id=get_voltage_level_id(el_pp['to_bus'], nb_dig_bus),
+ bus2_id=get_bus_id(el_pp["to_bus"], nb_dig_bus),
+ r=el_pp["r_ohm_per_km"] * length_,
+ x=el_pp["x_ohm_per_km"] * length_,
+ g1=0.5 * el_pp["g_us_per_km"] * length_ * 1e-6, # TODO
+ g2=0.5 * el_pp["g_us_per_km"] * length_ * 1e-6, # TODO
+ b1=0.5 * line_b, # TODO
+ b2=0.5 * line_b, # TODO
+ )
+
+# transformers
+nb_dig = len(str(pp_grid.trafo.shape[0]))
+from pandapower.build_branch import _calc_tap_from_dataframe, _wye_delta, _calc_r_x_y_from_dataframe
+net = {"_options": {"calculate_voltage_angles": True, "mode": "pf", "trafo_model": "t"}}
+ppc = {}
+sequence = 1
+vn_trafo_hv, vn_trafo_lv, shift = _calc_tap_from_dataframe(net, pp_grid.trafo)
+vn_lv = pp_grid.trafo["vn_lv_kv"].values / pp_grid.bus.loc[pp_grid.trafo["lv_bus"]]["vn_kv"].values
+r, x, y = _calc_r_x_y_from_dataframe(pp_grid, pp_grid.trafo, vn_trafo_lv, vn_lv, ppc, sequence=sequence)
+sn_mva = pp_grid.sn_mva
+for el in range(pp_grid.trafo.shape[0]):
+ el_pp = pp_grid.trafo.iloc[el]
+ id_ = f'trafo_{f"{el}".zfill(nb_dig)}'
+ nm_ = f"{el_pp['hv_bus']}_{el_pp['lv_bus']}_{el}"
+ assert el_pp["tap_side"] == "hv" or el_pp["tap_side"] is None or not el_pp["tap_side"], f'{el_pp["tap_side"]} is not hv or None'
+ # TODO I suppose here that tap is hv side !!!
+ # otherwise reverse the tap here
+ vn_kv_2 = pp_grid.bus.loc[el_pp["lv_bus"]]["vn_kv"]
+ vn_kv_1 = pp_grid.bus.loc[el_pp["hv_bus"]]["vn_kv"]
+ if np.isfinite(el_pp["tap_step_percent"]):
+ vn_kv_1 *= (1. + el_pp["tap_pos"] * el_pp["tap_step_percent"] / 100.)
+
+ # trafo r and trafo x
+ vn_lv_kv = el_pp["vn_lv_kv"]
+ # parallel = 1
+ # sn_mva = 1.
+ # sn_trafo_mva = 1.
+ # vk_percent = el_pp["vk_percent"]
+ # vkr_percent = el_pp["vkr_percent"]
+ # vn_lv = el_pp["vn_lv_kv"]
+ # sn = 1.
+ # tap_lv = np.square(vn_trafo_lv[el] / vn_lv) * sn_mva
+ # parallel = 1
+
+ # # r and x
+ # z_sc = vk_percent / 100. / sn_trafo_mva * tap_lv
+ # trafo_r = vkr_percent / 100. / sn_trafo_mva * tap_lv
+ # trafo_x = np.sign(z_sc) * np.sqrt((z_sc ** 2 - trafo_r ** 2).astype(float))
+
+ # # y which is needed for b and g
+ # baseR = np.square(vn_lv) / sn_mva
+ # pfe = el_pp["pfe_kw"] * 1e-3
+ # vnl_squared = vn_lv_kv ** 2
+ # b_real = pfe / vnl_squared * baseR
+ # i0 = el_pp["i0_percent"]
+ # b_img = (i0 / 100. * sn) ** 2 - pfe ** 2
+ # if b_img < 0:
+ # b_img = 0.
+ # b_img = np.sqrt(b_img) * baseR / vnl_squared
+ # y = - b_real * 1j - b_img * np.sign(i0)
+
+ # # then convert star to pi
+ # trafo_r, trafo_x, y =_wye_delta(np.array([trafo_r]), np.array([trafo_x]), np.array([y]))
+ trafo_to_pu = 1. / sn_mva
+ trafo_r = r[el]
+ trafo_x = x[el]
+
+ pypo_grid.create_2_windings_transformers(id=id_,
+ name=nm_,
+ voltage_level1_id=get_voltage_level_id(el_pp['hv_bus'], nb_dig_bus),
+ bus1_id=get_bus_id(el_pp["hv_bus"], nb_dig_bus),
+ voltage_level2_id=get_voltage_level_id(el_pp['lv_bus'], nb_dig_bus),
+ bus2_id=get_bus_id(el_pp["lv_bus"], nb_dig_bus),
+ r=trafo_r * trafo_to_pu,
+ x=trafo_x * trafo_to_pu,
+ g=y[el].real / trafo_to_pu,
+ b=y[el].imag / trafo_to_pu,
+ rated_u1=vn_kv_1,
+ rated_u2=vn_kv_2
+ )
+
+
+# check that grid are equals
+ls_grid_pp = init_from_pp(pp_grid)
+ls_grid_pypo = init_from_pypo(pypo_grid,
+ gen_slack_id=np.where(pp_grid.gen["slack"])[0],
+ sn_mva=1.)
+
+# check the elements are consistent
+for i, (el_pp, el_pypo) in enumerate(zip(ls_grid_pp.get_buses(), ls_grid_pypo.get_buses())):
+ assert el_pp == el_pypo, f"error for {i}"
+
+for i, (el_pp, el_pypo) in enumerate(zip(ls_grid_pp.get_loads(), ls_grid_pypo.get_loads())):
+ assert el_pp.bus_id == el_pypo.bus_id, f"error for {i}"
+ assert el_pp.target_p_mw == el_pypo.target_p_mw, f"error for {i}"
+ assert el_pp.target_q_mvar == el_pypo.target_q_mvar, f"error for {i}"
+
+for i, (el_pp, el_pypo) in enumerate(zip(ls_grid_pp.get_generators(), ls_grid_pypo.get_generators())):
+ assert el_pp.bus_id == el_pypo.bus_id, f"error for {i}"
+ assert el_pp.target_p_mw == el_pypo.target_p_mw, f"error for {i}"
+ assert el_pp.target_vm_pu == el_pypo.target_vm_pu, f"error for {i}"
+ assert el_pp.is_slack == el_pypo.is_slack, f"error for {i}"
+
+for i, (el_pp, el_pypo) in enumerate(zip(ls_grid_pp.get_storages(), ls_grid_pypo.get_storages())):
+ assert el_pp.bus_id == el_pypo.bus_id, f"error for {i}"
+ assert el_pp.target_p_mw == el_pypo.target_p_mw, f"error for {i}"
+ assert el_pp.target_q_mvar == el_pypo.target_q_mvar, f"error for {i}"
+
+for i, (el_pp, el_pypo) in enumerate(zip(ls_grid_pp.get_shunts(), ls_grid_pypo.get_shunts())):
+ assert el_pp.bus_id == el_pypo.bus_id, f"error for {i}"
+ assert el_pp.target_p_mw == el_pypo.target_p_mw, f"error for {i}"
+ assert el_pp.target_q_mvar == el_pypo.target_q_mvar, f"error for {i}"
+
+for i, (el_pp, el_pypo) in enumerate(zip(ls_grid_pp.get_lines(), ls_grid_pypo.get_lines())):
+ assert np.allclose(el_pp.bus_or_id, el_pypo.bus_or_id), f"error for {i}"
+ assert np.allclose(el_pp.bus_ex_id, el_pypo.bus_ex_id), f"error for {i}"
+ assert np.allclose(el_pp.r_pu, el_pypo.r_pu), f"error for {i}: {el_pp.r_pu} vs {el_pypo.r_pu}"
+ assert np.allclose(el_pp.x_pu, el_pypo.x_pu), f"error for {i}: {el_pp.x_pu} vs {el_pypo.x_pu}"
+ assert np.allclose(el_pp.h_pu, el_pypo.h_pu), f"error for {i}: {el_pp.h_pu} vs {el_pypo.h_pu}"
+ assert np.allclose(el_pp.h_or_pu, el_pypo.h_or_pu), f"error for {i}: {el_pp.h_or_pu} vs {el_pypo.h_or_pu}"
+ assert np.allclose(el_pp.h_ex_pu, el_pypo.h_ex_pu), f"error for {i}: {el_pp.h_ex_pu} vs {el_pypo.h_ex_pu}"
+
+for i, (el_pp, el_pypo) in enumerate(zip(ls_grid_pp.get_trafos(), ls_grid_pypo.get_trafos())):
+ assert np.allclose(el_pp.bus_lv_id, el_pypo.bus_lv_id), f"error for {i}"
+ assert np.allclose(el_pp.bus_hv_id, el_pypo.bus_hv_id), f"error for {i}"
+ assert np.allclose(el_pp.ratio, el_pypo.ratio), f"error for {i}: {el_pp.ratio} vs {el_pypo.ratio}"
+ assert np.allclose(el_pp.shift_rad, el_pypo.shift_rad), f"error for {i}: {el_pp.shift_rad} vs {el_pypo.shift_rad}"
+ assert np.allclose(el_pp.r_pu, el_pypo.r_pu), f"error for {i}: {el_pp.r_pu} vs {el_pypo.r_pu}"
+ assert np.allclose(el_pp.x_pu, el_pypo.x_pu), f"error for {i}: {el_pp.x_pu} vs {el_pypo.x_pu}"
+ assert np.allclose(el_pp.h_pu, el_pypo.h_pu), f"error for {i}: {el_pp.h_pu} vs {el_pypo.h_pu}"
+
+# check powerflow is the same
+V_init_pp = np.zeros(len(ls_grid_pp.get_buses()), dtype=complex) * 1.06
+V_init_pypo = np.zeros(len(ls_grid_pypo.get_buses()), dtype=complex) * 1.06
+V_pp = ls_grid_pp.ac_pf(1. * V_init_pp, 10, 1e-8)
+V_pypo = ls_grid_pypo.ac_pf(1. * V_init_pypo, 10, 1e-8)
+assert np.allclose(V_pp[:env.n_sub], V_pypo[:env.n_sub])
+V_pp = ls_grid_pp.dc_pf(1. * V_init_pp, 10, 1e-8)
+V_pypo = ls_grid_pypo.dc_pf(1. * V_init_pypo, 10, 1e-8)
+assert np.allclose(V_pp[:env.n_sub], V_pypo[:env.n_sub])
+
+# TODO dc_lines, static generators
+
+# and now save the grid
+pypo_grid.dump("grid.xiidm")
diff --git a/lightsim2grid/tests/case_14_storage_iidm/difficulty_levels.json b/lightsim2grid/tests/case_14_storage_iidm/difficulty_levels.json
new file mode 100644
index 00000000..da831744
--- /dev/null
+++ b/lightsim2grid/tests/case_14_storage_iidm/difficulty_levels.json
@@ -0,0 +1,58 @@
+{
+ "0": {
+ "NO_OVERFLOW_DISCONNECTION": true,
+ "NB_TIMESTEP_OVERFLOW_ALLOWED": 9999,
+ "NB_TIMESTEP_COOLDOWN_SUB": 0,
+ "NB_TIMESTEP_COOLDOWN_LINE": 0,
+ "HARD_OVERFLOW_THRESHOLD": 9999,
+ "NB_TIMESTEP_RECONNECTION": 0,
+ "IGNORE_MIN_UP_DOWN_TIME": true,
+ "ALLOW_DISPATCH_GEN_SWITCH_OFF": true,
+ "ENV_DC": false,
+ "FORECAST_DC": false,
+ "MAX_SUB_CHANGED": 1,
+ "MAX_LINE_STATUS_CHANGED": 1
+ },
+ "1": {
+ "NO_OVERFLOW_DISCONNECTION": false,
+ "NB_TIMESTEP_OVERFLOW_ALLOWED": 6,
+ "NB_TIMESTEP_COOLDOWN_SUB": 0,
+ "NB_TIMESTEP_COOLDOWN_LINE": 0,
+ "HARD_OVERFLOW_THRESHOLD": 3.0,
+ "NB_TIMESTEP_RECONNECTION": 1,
+ "IGNORE_MIN_UP_DOWN_TIME": true,
+ "ALLOW_DISPATCH_GEN_SWITCH_OFF": true,
+ "ENV_DC": false,
+ "FORECAST_DC": false,
+ "MAX_SUB_CHANGED": 1,
+ "MAX_LINE_STATUS_CHANGED": 1
+ },
+ "2": {
+ "NO_OVERFLOW_DISCONNECTION": false,
+ "NB_TIMESTEP_OVERFLOW_ALLOWED": 3,
+ "NB_TIMESTEP_COOLDOWN_SUB": 1,
+ "NB_TIMESTEP_COOLDOWN_LINE": 1,
+ "HARD_OVERFLOW_THRESHOLD": 2.5,
+ "NB_TIMESTEP_RECONNECTION": 6,
+ "IGNORE_MIN_UP_DOWN_TIME": true,
+ "ALLOW_DISPATCH_GEN_SWITCH_OFF": true,
+ "ENV_DC": false,
+ "FORECAST_DC": false,
+ "MAX_SUB_CHANGED": 1,
+ "MAX_LINE_STATUS_CHANGED": 1
+ },
+ "competition": {
+ "NO_OVERFLOW_DISCONNECTION": false,
+ "NB_TIMESTEP_OVERFLOW_ALLOWED": 3,
+ "NB_TIMESTEP_COOLDOWN_SUB": 3,
+ "NB_TIMESTEP_COOLDOWN_LINE": 3,
+ "HARD_OVERFLOW_THRESHOLD": 2.0,
+ "NB_TIMESTEP_RECONNECTION": 12,
+ "IGNORE_MIN_UP_DOWN_TIME": true,
+ "ALLOW_DISPATCH_GEN_SWITCH_OFF": true,
+ "ENV_DC": false,
+ "FORECAST_DC": false,
+ "MAX_SUB_CHANGED": 1,
+ "MAX_LINE_STATUS_CHANGED": 1
+ }
+}
diff --git a/lightsim2grid/tests/case_14_storage_iidm/grid.xiidm b/lightsim2grid/tests/case_14_storage_iidm/grid.xiidm
new file mode 100644
index 00000000..d2eedb6a
--- /dev/null
+++ b/lightsim2grid/tests/case_14_storage_iidm/grid.xiidm
@@ -0,0 +1,155 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lightsim2grid/tests/case_14_storage_iidm/grid_layout.json b/lightsim2grid/tests/case_14_storage_iidm/grid_layout.json
new file mode 100644
index 00000000..e1534647
--- /dev/null
+++ b/lightsim2grid/tests/case_14_storage_iidm/grid_layout.json
@@ -0,0 +1,58 @@
+{
+ "sub_0": [
+ -280.0,
+ -81.0
+ ],
+ "sub_1": [
+ -100.0,
+ -270.0
+ ],
+ "sub_2": [
+ 366.0,
+ -270.0
+ ],
+ "sub_3": [
+ 366.0,
+ -54.0
+ ],
+ "sub_4": [
+ -64.0,
+ -54.0
+ ],
+ "sub_5": [
+ -64.0,
+ 54.0
+ ],
+ "sub_6": [
+ 450.0,
+ 0.0
+ ],
+ "sub_7": [
+ 550.0,
+ 0.0
+ ],
+ "sub_8": [
+ 326.0,
+ 54.0
+ ],
+ "sub_9": [
+ 222.0,
+ 108.0
+ ],
+ "sub_10": [
+ 79.0,
+ 162.0
+ ],
+ "sub_11": [
+ -170.0,
+ 270.0
+ ],
+ "sub_12": [
+ -64.0,
+ 270.0
+ ],
+ "sub_13": [
+ 222.0,
+ 216.0
+ ]
+}
diff --git a/lightsim2grid/tests/case_14_storage_iidm/prods_charac.csv b/lightsim2grid/tests/case_14_storage_iidm/prods_charac.csv
new file mode 100644
index 00000000..0c1159a0
--- /dev/null
+++ b/lightsim2grid/tests/case_14_storage_iidm/prods_charac.csv
@@ -0,0 +1,7 @@
+Pmax,Pmin,name,type,bus,max_ramp_up,max_ramp_down,min_up_time,min_down_time,marginal_cost,shut_down_cost,start_cost,x,y,V
+140,0.0,gen_1_0,nuclear,1,5,5,96,96,40,10,20,180,10,142.1
+120,0.0,gen_2_1,thermal,2,10,10,4,4,70,1,2,646,10,142.1
+70,0.0,gen_5_2,wind,5,0,0,0,0,0,0,0,216,334,22.0
+70,0.0,gen_5_3,solar,5,0,0,0,0,0,0,0,216,334,22.0
+40,0.0,gen_7_4,solar,7,0,0,0,0,0,0,0,718,280,13.2
+100,0.0,gen_0_5,hydro,0,15,15,4,4,70,1,2,0,199,142.1
diff --git a/lightsim2grid/tests/case_14_storage_iidm/storage_units_charac.csv b/lightsim2grid/tests/case_14_storage_iidm/storage_units_charac.csv
new file mode 100644
index 00000000..0bb5168f
--- /dev/null
+++ b/lightsim2grid/tests/case_14_storage_iidm/storage_units_charac.csv
@@ -0,0 +1,3 @@
+Emax,Emin,name,type,max_p_prod,max_p_absorb,marginal_cost,power_loss,charging_efficiency,discharging_efficiency
+15,0,storage_5_0,battery,5,5,20,0.1,0.95,1
+7,0,storage_7_1,battery,10,10,20,0.1,1,0.9
diff --git a/lightsim2grid/tests/test_Computers.py b/lightsim2grid/tests/test_Computers.py
index 15940a94..afb6d825 100644
--- a/lightsim2grid/tests/test_Computers.py
+++ b/lightsim2grid/tests/test_Computers.py
@@ -11,13 +11,11 @@
from grid2op.Parameters import Parameters
import warnings
import numpy as np
-from numpy.core.shape_base import stack
-import lightsim2grid
-import lightsim2grid_cpp
from lightsim2grid import LightSimBackend
-from lightsim2grid_cpp import Computers
+from lightsim2grid_cpp import TimeSeriesCPP
-class TestComputers(unittest.TestCase):
+
+class TestTimeSeriesCPP(unittest.TestCase):
def test_basic(self):
# print(f"{lightsim2grid_cpp.__file__}")
env_name = "l2rpn_case14_sandbox"
@@ -37,7 +35,7 @@ def test_basic(self):
load_q = 1.0 * env.chronics_handler.real_data.data.load_q
# now perform the computation
- computer = Computers(grid)
+ computer = TimeSeriesCPP(grid)
# print("start the computation")
status = computer.compute_Vs(prod_p,
np.zeros((prod_p.shape[0], 0)), # no static generators for now !
@@ -83,7 +81,7 @@ def test_amps(self):
load_q = 1.0 * env.chronics_handler.real_data.data.load_q
# now perform the computation
- computer = Computers(grid)
+ computer = TimeSeriesCPP(grid)
# print("start the computation")
status = computer.compute_Vs(prod_p,
np.zeros((prod_p.shape[0], 0)), # no static generators for now !
diff --git a/lightsim2grid/tests/test_GridModel.py b/lightsim2grid/tests/test_GridModel.py
index 0d6329ee..7230fc7f 100644
--- a/lightsim2grid/tests/test_GridModel.py
+++ b/lightsim2grid/tests/test_GridModel.py
@@ -103,7 +103,8 @@ def make_v0(self, net):
return V0
def run_me_pf(self, V0):
- return self.model.compute_newton(V0, self.max_it, self.tol)
+ res = self.model.ac_pf(V0, self.max_it, self.tol)
+ return res
def run_ref_pf(self, net):
with warnings.catch_warnings():
@@ -111,7 +112,7 @@ def run_ref_pf(self, net):
pp.runpp(net,
init="flat",
lightsim2grid=False,
- numba=True,
+ numba=False,
distributed_slack=False)
def do_i_skip(self, func_name):
diff --git a/lightsim2grid/tests/test_SameResPP.py b/lightsim2grid/tests/test_SameResPP.py
index 43d746c5..13ae6a86 100644
--- a/lightsim2grid/tests/test_SameResPP.py
+++ b/lightsim2grid/tests/test_SameResPP.py
@@ -131,7 +131,7 @@ def _aux_test(self, pn_net):
V = backend._grid.ac_pf(v_tmp, 10, 1e-5)
assert V.shape[0], "? lightsim diverge when initialized with pp final voltage ?"
- backend._grid.tell_topo_changed()
+ backend._grid.tell_solver_need_reset()
Y_pp = backend.init_pp_backend._grid._ppc["internal"]["Ybus"]
Sbus = backend.init_pp_backend._grid._ppc["internal"]["Sbus"]
@@ -218,9 +218,9 @@ def _aux_test(self, pn_net):
backend._grid.deactivate_result_computation()
Vdc = backend._grid.dc_pf(Vinit, max_iter, tol_this)
backend._grid.reactivate_result_computation()
- backend._grid.tell_topo_changed()
- Ydc_me = copy.deepcopy(backend._grid.get_Ybus())
- Sdc_me = copy.deepcopy(backend._grid.get_Sbus())
+ backend._grid.tell_solver_need_reset()
+ Ydc_me = copy.deepcopy(backend._grid.get_dcYbus())
+ Sdc_me = copy.deepcopy(backend._grid.get_dcSbus())
assert np.max(np.abs(V_init_ref[pp_vect_converter] - Vdc[:nb_sub])) <= 100.*self.tol,\
f"\t Error for the DC approximation: resulting voltages are different " \
f"{np.max(np.abs(V_init_ref[pp_vect_converter] - Vdc[:nb_sub])):.5f}pu"
diff --git a/lightsim2grid/tests/test_SecurityAnlysis.py b/lightsim2grid/tests/test_SecurityAnlysis.py
index 4927f6fe..1ab67daa 100644
--- a/lightsim2grid/tests/test_SecurityAnlysis.py
+++ b/lightsim2grid/tests/test_SecurityAnlysis.py
@@ -11,7 +11,7 @@
import numpy as np
import grid2op
-from lightsim2grid import SecurityAnalysis
+from lightsim2grid import ContingencyAnalysis
from lightsim2grid import LightSimBackend
import warnings
import pdb
@@ -28,10 +28,10 @@ def tearDown(self) -> None:
return super().tearDown()
def test_can_create(self):
- sa = SecurityAnalysis(self.env)
+ sa = ContingencyAnalysis(self.env)
def test_clear(self):
- sa = SecurityAnalysis(self.env)
+ sa = ContingencyAnalysis(self.env)
# add simple contingencies
sa.add_multiple_contingencies(0, 1, 2, 3)
@@ -46,7 +46,7 @@ def test_clear(self):
assert len(sa._contingency_order) == 0
def test_add_single_contingency(self):
- sa = SecurityAnalysis(self.env)
+ sa = ContingencyAnalysis(self.env)
with self.assertRaises(RuntimeError):
sa.add_single_contingency("toto")
with self.assertRaises(RuntimeError):
@@ -64,7 +64,7 @@ def test_add_single_contingency(self):
assert len(sa._contingency_order) == 4
def test_add_multiple_contingencies(self):
- sa = SecurityAnalysis(self.env)
+ sa = ContingencyAnalysis(self.env)
# add simple contingencies
sa.add_multiple_contingencies(0, 1, 2, 3)
all_conts = sa.computer.my_defaults()
@@ -91,7 +91,7 @@ def test_add_multiple_contingencies(self):
assert len(sa._contingency_order) == 4
def test_add_all_n1_contingencies(self):
- sa = SecurityAnalysis(self.env)
+ sa = ContingencyAnalysis(self.env)
sa.add_all_n1_contingencies()
all_conts = sa.computer.my_defaults()
assert len(all_conts) == self.env.n_line
@@ -100,7 +100,7 @@ def test_add_all_n1_contingencies(self):
def test_get_flows_simple(self):
"""test the get_flows method in the most simplest way: ask for all contingencies,
contingencies are given in the right order"""
- sa = SecurityAnalysis(self.env)
+ sa = ContingencyAnalysis(self.env)
sa.add_multiple_contingencies(0, 1, 2)
res_p, res_a, res_v = sa.get_flows()
assert res_a.shape == (3, self.env.n_line)
@@ -111,7 +111,7 @@ def test_get_flows_simple(self):
def test_get_flows_1(self):
"""test the get_flows method: ask for all contingencies ,
contingencies are NOT given in the right order"""
- sa = SecurityAnalysis(self.env)
+ sa = ContingencyAnalysis(self.env)
sa.add_multiple_contingencies(0, 2, 1)
res_p, res_a, res_v = sa.get_flows()
assert res_a.shape == (3, self.env.n_line)
@@ -122,7 +122,7 @@ def test_get_flows_1(self):
def test_get_flows_2(self):
"""test the get_flows method: don't ask for all contingencies (same order as given),
contingencies are given in the right order"""
- sa = SecurityAnalysis(self.env)
+ sa = ContingencyAnalysis(self.env)
sa.add_multiple_contingencies(0, 1, 2)
res_p, res_a, res_v = sa.get_flows(0, 1)
assert res_a.shape == (2, self.env.n_line)
@@ -132,7 +132,7 @@ def test_get_flows_2(self):
def test_get_flows_3(self):
"""test the get_flows method in the most simplest way: not all contingencies (not same order as given),
contingencies are given in the right order"""
- sa = SecurityAnalysis(self.env)
+ sa = ContingencyAnalysis(self.env)
sa.add_multiple_contingencies(0, 1, 2)
res_p, res_a, res_v = sa.get_flows(0, 2)
assert res_a.shape == (2, self.env.n_line)
@@ -142,7 +142,7 @@ def test_get_flows_3(self):
def test_get_flows_4(self):
"""test the get_flows method: don't ask for all contingencies (same order as given),
contingencies are NOT given in the right order"""
- sa = SecurityAnalysis(self.env)
+ sa = ContingencyAnalysis(self.env)
sa.add_multiple_contingencies(0, 2, 1)
res_p, res_a, res_v = sa.get_flows(0, 2)
assert res_a.shape == (2, self.env.n_line)
@@ -152,7 +152,7 @@ def test_get_flows_4(self):
def test_get_flows_5(self):
"""test the get_flows method in the most simplest way: not all contingencies (not same order as given),
contingencies are NOT given in the right order"""
- sa = SecurityAnalysis(self.env)
+ sa = ContingencyAnalysis(self.env)
sa.add_multiple_contingencies(0, 2, 1)
res_p, res_a, res_v = sa.get_flows(0, 1)
assert res_a.shape == (2, self.env.n_line)
@@ -161,7 +161,7 @@ def test_get_flows_5(self):
def test_get_flows_multiple(self):
"""test the get_flows function when multiple contingencies"""
- sa = SecurityAnalysis(self.env)
+ sa = ContingencyAnalysis(self.env)
sa.add_multiple_contingencies(0, [0, 4], [5, 7], 4)
# everything
@@ -197,11 +197,11 @@ def test_get_flows_multiple(self):
def test_change_injection(self):
"""test the capacity of the things to handle different steps"""
- sa1 = SecurityAnalysis(self.env)
+ sa1 = ContingencyAnalysis(self.env)
conts = [0, [0, 4], [5, 7], 4]
sa1.add_multiple_contingencies(*conts)
self.env.reset()
- sa2 = SecurityAnalysis(self.env)
+ sa2 = ContingencyAnalysis(self.env)
sa2.add_multiple_contingencies(*conts)
res_p1, res_a1, res_v1 = sa1.get_flows()
diff --git a/lightsim2grid/tests/test_SecurityAnlysis_cpp.py b/lightsim2grid/tests/test_SecurityAnlysis_cpp.py
index bbf73ae7..15de1086 100644
--- a/lightsim2grid/tests/test_SecurityAnlysis_cpp.py
+++ b/lightsim2grid/tests/test_SecurityAnlysis_cpp.py
@@ -10,7 +10,7 @@
import numpy as np
import grid2op
-from lightsim2grid_cpp import SecurityAnalysisCPP
+from lightsim2grid_cpp import ContingencyAnalysisCPP
from lightsim2grid import LightSimBackend
import warnings
import pdb
@@ -29,11 +29,11 @@ def tearDown(self) -> None:
return super().tearDown()
def test_can_create(self):
- SA = SecurityAnalysisCPP(self.env.backend._grid)
+ SA = ContingencyAnalysisCPP(self.env.backend._grid)
assert len(SA.my_defaults()) == 0
def test_add_n1(self):
- SA = SecurityAnalysisCPP(self.env.backend._grid)
+ SA = ContingencyAnalysisCPP(self.env.backend._grid)
SA.add_n1(0)
all_def = SA.my_defaults()
assert len(all_def) == 1
@@ -64,7 +64,7 @@ def test_add_n1(self):
SA.add_n1(20)
def test_add_multiple_n1(self):
- SA = SecurityAnalysisCPP(self.env.backend._grid)
+ SA = ContingencyAnalysisCPP(self.env.backend._grid)
SA.add_multiple_n1([0])
all_def = SA.my_defaults()
assert len(all_def) == 1
@@ -99,7 +99,7 @@ def test_add_multiple_n1(self):
SA.add_multiple_n1([20])
def test_add_nk(self):
- SA = SecurityAnalysisCPP(self.env.backend._grid)
+ SA = ContingencyAnalysisCPP(self.env.backend._grid)
SA.add_nk([0])
all_def = SA.my_defaults()
assert len(all_def) == 1
@@ -132,13 +132,13 @@ def test_add_nk(self):
SA.add_nk([20])
def test_add_all_n1(self):
- SA = SecurityAnalysisCPP(self.env.backend._grid)
+ SA = ContingencyAnalysisCPP(self.env.backend._grid)
SA.add_all_n1()
all_def = SA.my_defaults()
assert len(all_def) == self.env.n_line
def test_remove_n1(self):
- SA = SecurityAnalysisCPP(self.env.backend._grid)
+ SA = ContingencyAnalysisCPP(self.env.backend._grid)
SA.add_all_n1()
assert SA.remove_n1(0) # this should remove it and return true (because the removing is a success)
all_def = SA.my_defaults()
@@ -148,7 +148,7 @@ def test_remove_n1(self):
assert len(all_def) == self.env.n_line - 1
def test_clear(self):
- SA = SecurityAnalysisCPP(self.env.backend._grid)
+ SA = ContingencyAnalysisCPP(self.env.backend._grid)
SA.add_all_n1()
all_def = SA.my_defaults()
assert len(all_def) == self.env.n_line
@@ -157,7 +157,7 @@ def test_clear(self):
assert len(all_def) == 0
def test_remove_multiple_n1(self):
- SA = SecurityAnalysisCPP(self.env.backend._grid)
+ SA = ContingencyAnalysisCPP(self.env.backend._grid)
SA.add_all_n1()
nb_removed = SA.remove_multiple_n1([0, 1, 2])
assert nb_removed == 3
@@ -169,7 +169,7 @@ def test_remove_multiple_n1(self):
assert len(all_def) == self.env.n_line - 5
def test_remove_nk(self):
- SA = SecurityAnalysisCPP(self.env.backend._grid)
+ SA = ContingencyAnalysisCPP(self.env.backend._grid)
SA.add_nk([0, 1])
SA.add_nk([0, 2])
SA.add_nk([0, 3])
@@ -188,7 +188,7 @@ def test_remove_nk(self):
assert len(all_def) == 2
def test_compute(self):
- SA = SecurityAnalysisCPP(self.env.backend._grid)
+ SA = ContingencyAnalysisCPP(self.env.backend._grid)
lid_cont = [0, 1, 2, 3]
nb_sub = self.env.n_sub
SA.add_multiple_n1(lid_cont)
@@ -207,7 +207,7 @@ def test_compute(self):
assert np.max(np.abs(res_flows[cont_id] - sim_obs.a_or*1e-3)) <= 1e-6, f"error in flows when disconnecting line {l_id} (contingency nb {cont_id})"
def test_compute_nonconnected_graph(self):
- SA = SecurityAnalysisCPP(self.env.backend._grid)
+ SA = ContingencyAnalysisCPP(self.env.backend._grid)
lid_cont = [17, 18, 19] # 17 is ok, 18 lead to divergence, i need to check then that 19 is correct (no divergence)
nb_sub = self.env.n_sub
SA.add_multiple_n1(lid_cont)
diff --git a/lightsim2grid/tests/test_backend_pypowsybl.py b/lightsim2grid/tests/test_backend_pypowsybl.py
new file mode 100644
index 00000000..1390698d
--- /dev/null
+++ b/lightsim2grid/tests/test_backend_pypowsybl.py
@@ -0,0 +1,174 @@
+# Copyright (c) 2023, RTE (https://www.rte-france.com)
+# See AUTHORS.txt
+# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
+# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
+# you can obtain one at http://mozilla.org/MPL/2.0/.
+# SPDX-License-Identifier: MPL-2.0
+# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
+
+import unittest
+import warnings
+import os
+import numpy as np
+from lightsim2grid import LightSimBackend
+from lightsim2grid.gridmodel.from_pypowsybl import init as init_pypow
+import grid2op
+from grid2op.Runner import Runner
+import pypowsybl.network as pypow_net
+
+try:
+ from grid2op._create_test_suite import create_test_suite
+ CAN_DO_TEST_SUITE = True
+except ImportError as exc_:
+ CAN_DO_TEST_SUITE = False
+
+
+def _aux_get_loader_kwargs_storage():
+ return {"use_buses_for_sub": True, "double_bus_per_sub": True, "gen_slack_id": 5}
+
+def _aux_get_loader_kwargs():
+ return {"use_buses_for_sub": True, "double_bus_per_sub": True, "gen_slack_id": 0}
+
+
+class BackendTester(unittest.TestCase):
+ """issue is still not replicated and these tests pass"""
+ def setUp(self) -> None:
+ dir_path = os.path.dirname(os.path.realpath(__file__))
+ self.path = os.path.join(dir_path, "case_14_iidm")
+ self.file_name = "grid.xiidm"
+
+ def _aux_prep_backend(self, backend):
+ backend.set_env_name("case_14_iidm_BackendTester")
+ backend.load_grid(self.path, self.file_name)
+ backend.load_storage_data(self.path)
+ backend.load_redispacthing_data(self.path)
+ backend.assert_grid_correct()
+
+ def test_load_grid(self):
+ backend = LightSimBackend(loader_method="pypowsybl",
+ loader_kwargs=_aux_get_loader_kwargs())
+ self._aux_prep_backend(backend)
+ cls = type(backend)
+ assert cls.n_line == 20, f"wrong number of line {cls.n_line} vs 20"
+ assert cls.n_load == 11, f"wrong number of load {cls.n_load} vs 11"
+ assert cls.n_gen == 5, f"wrong number of gen {cls.n_gen} vs 5"
+ assert cls.n_sub == 14, f"wrong number of gen {cls.n_sub} vs 14"
+ assert cls.n_storage == 0, f"wrong number of storage { cls.n_storage} vs 0"
+ assert cls.n_shunt == 1, f"wrong number of shunt { cls.n_shunt} vs 1"
+ assert cls.shunts_data_available
+
+ assert (backend._LightSimBackend__init_topo_vect == 1).all()
+ assert backend._LightSimBackend__nb_powerline == 17
+ assert backend._LightSimBackend__nb_bus_before == 14
+ assert backend.nb_bus_total == 28
+
+ def test_runpf(self):
+ backend = LightSimBackend(loader_method="pypowsybl", loader_kwargs=_aux_get_loader_kwargs())
+ self._aux_prep_backend(backend)
+ # AC powerflow
+ conv, exc_ = backend.runpf()
+ assert conv
+ # DC powerflow
+ conv, exc_ = backend.runpf(is_dc=True)
+ assert conv
+
+class BackendTester2(unittest.TestCase):
+ """issue is still not replicated and these tests pass"""
+ def _aux_prep_backend(self, backend):
+ backend.set_env_name("case_14_storage_iidm_BackendTester2")
+ backend.load_grid(self.path, self.file_name)
+ backend.load_storage_data(self.path)
+ backend.load_redispacthing_data(self.path)
+ backend.assert_grid_correct()
+
+ def setUp(self) -> None:
+ dir_path = os.path.dirname(os.path.realpath(__file__))
+ self.path = os.path.join(dir_path, "case_14_storage_iidm")
+ self.file_name = "grid.xiidm"
+
+ def test_init(self):
+ grid_tmp = pypow_net.load(os.path.join(self.path, self.file_name))
+ grid = init_pypow(grid_tmp, gen_slack_id=5, sort_index=True)
+ grid.ac_pf(np.ones(14, dtype=np.complex128), 10, 1e-6)
+
+ def test_runpf(self):
+ backend = LightSimBackend(loader_method="pypowsybl",
+ loader_kwargs=_aux_get_loader_kwargs())
+ self._aux_prep_backend(backend)
+ # AC powerflow
+ conv, exc_ = backend.runpf()
+ assert conv
+ # DC powerflow
+ conv, exc_ = backend.runpf(is_dc=True)
+ assert conv
+
+if CAN_DO_TEST_SUITE:
+ dir_path = os.path.dirname(os.path.realpath(__file__))
+ path_case_14_storage_iidm = os.path.join(dir_path, "case_14_storage_iidm")
+ from grid2op.tests.aaa_test_backend_interface import AAATestBackendAPI
+
+ class TestBackendAPI_PyPoBk(AAATestBackendAPI, unittest.TestCase):
+ def get_path(self):
+ return path_case_14_storage_iidm
+
+ def get_casefile(self):
+ return "grid.xiidm"
+
+ def make_backend(self, detailed_infos_for_cascading_failures=False):
+ return LightSimBackend(loader_method="pypowsybl",
+ loader_kwargs=_aux_get_loader_kwargs_storage(),
+ detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures)
+
+ # # add test of grid2op for the backend based on pypowsybl
+ # def this_make_backend(self, detailed_infos_for_cascading_failures=False):
+ # return LightSimBackend(
+ # loader_method="pypowsybl",
+ # loader_kwargs=_aux_get_loader_kwargs_storage(),
+ # detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures
+ # )
+ # add_name_cls = "test_LightSimBackend_pypowsybl"
+
+if CAN_DO_TEST_SUITE:
+ # requires grid2Op 1.9.6 at least
+ class EnvTester(unittest.TestCase):
+ def setUp(self) -> None:
+ dir_path = os.path.dirname(os.path.realpath(__file__))
+ path_case_14_storage_iidm = os.path.join(dir_path, "case_14_storage_iidm")
+ self.env = grid2op.make(path_case_14_storage_iidm,
+ backend=LightSimBackend(loader_method="pypowsybl",
+ loader_kwargs=_aux_get_loader_kwargs_storage(),
+ ),
+ _add_to_name=type(self).__name__
+ )
+ super().setUp()
+
+ def tearDown(self) -> None:
+ self.env.close()
+ return super().tearDown()
+
+ def test_can_make(self):
+ self.env.reset()
+ 1 + 1
+
+ def test_copy(self):
+ obs = self.env.reset()
+ env_cpy = self.env.copy()
+ obs_cpy = env_cpy.reset()
+ assert self.env.backend.supported_grid_format == ("xiidm", )
+ assert env_cpy.backend.supported_grid_format == ("xiidm", )
+
+ def test_runner(self):
+ obs = self.env.reset()
+ env_cpy = self.env.copy()
+ runner = Runner(**self.env.get_params_for_runner())
+ runner_cpy = Runner(**env_cpy.get_params_for_runner())
+ res = runner.run(nb_episode=1, max_iter=10)
+ res_cpy = runner_cpy.run(nb_episode=1, max_iter=10)
+ for el, el_cpy in zip(res[0], res_cpy[0]):
+ assert el == el_cpy, f"{el} vs {el_cpy}"
+
+
+# TODO env tester
+if __name__ == "__main__":
+ unittest.main()
+
\ No newline at end of file
diff --git a/lightsim2grid/tests/test_basic_backend_api.py b/lightsim2grid/tests/test_basic_backend_api.py
new file mode 100644
index 00000000..08a8dde0
--- /dev/null
+++ b/lightsim2grid/tests/test_basic_backend_api.py
@@ -0,0 +1,30 @@
+# Copyright (c) 2020, RTE (https://www.rte-france.com)
+# See AUTHORS.txt
+# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
+# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
+# you can obtain one at http://mozilla.org/MPL/2.0/.
+# SPDX-License-Identifier: MPL-2.0
+# This file is part of LightSim2grid, LightSim2grid a implements a c++ backend targeting the Grid2Op platform.
+
+import unittest
+
+try:
+ from grid2op._create_test_suite import create_test_suite
+ CAN_PERFORM_THESE = True
+except ImportError as exc_:
+ CAN_PERFORM_THESE = False
+
+from lightsim2grid import LightSimBackend
+
+if CAN_PERFORM_THESE:
+ from grid2op.tests.aaa_test_backend_interface import AAATestBackendAPI
+ class TestBackendAPI_LSTester(AAATestBackendAPI, unittest.TestCase):
+ def make_backend(self, detailed_infos_for_cascading_failures=False):
+ return LightSimBackend(detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures)
+
+else:
+ print("Have you installed grid2op in dev / editable mode ? We cannot make the `create_test_suite` :-(")
+
+# and run it with `python -m unittest gridcal_backend_tests.py`
+if __name__ == "__main__":
+ unittest.main()
diff --git a/lightsim2grid/tests/test_case118.py b/lightsim2grid/tests/test_case118.py
index c4e48902..95b5d875 100644
--- a/lightsim2grid/tests/test_case118.py
+++ b/lightsim2grid/tests/test_case118.py
@@ -131,7 +131,7 @@ def test_neurips_track2(self):
with warnings.catch_warnings():
warnings.filterwarnings("ignore")
ls_grid = init(self.pp_net)
- ls_grid.tell_topo_changed()
+ ls_grid.tell_solver_need_reset()
V = np.ones(2 * self.nb_bus_total, dtype=np.complex_)
V = ls_grid.ac_pf(V, self.max_it, self.tol)
self.check_results(V[:self.nb_bus_total], ls_grid, self.pp_net)
diff --git a/lightsim2grid/tests/test_dist_slack_backend.py b/lightsim2grid/tests/test_dist_slack_backend.py
index 5025a5c0..2d63007e 100644
--- a/lightsim2grid/tests/test_dist_slack_backend.py
+++ b/lightsim2grid/tests/test_dist_slack_backend.py
@@ -44,28 +44,36 @@ def _prepare_env(self, env):
env.set_id(0)
def _run_env(self, env):
+ # print("Run env starts")
obs = env.reset()
done = False
ts = 0
aor = np.zeros((self.max_iter_real, env.n_line))
gen_p = np.zeros((self.max_iter_real, env.n_gen))
+ info = None
+ # print("While starts")
while not done:
+ # print("\tbefore step")
obs, reward, done, info = env.step(env.action_space())
aor[ts,:] = obs.a_or
gen_p[ts,:] = obs.gen_p
ts += 1
if ts >= self.max_iter_real:
break
- return ts, done, aor, gen_p
+ # print("Run env stops")
+ return ts, done, aor, gen_p, info
def test_different(self):
self._aux_test_different(self.env_ss, self.env_ds)
def _aux_test_different(self, env_ss, env_ds):
- ts_ss, done_ss, aor_ss, gen_p_ss = self._run_env(env_ss)
- ts_ds, done_ds, aor_ds, gen_p_ds = self._run_env(env_ds)
+ # print("before single slack")
+ ts_ss, done_ss, aor_ss, gen_p_ss, info_ss = self._run_env(env_ss)
+ # print("before dist slack")
+ ts_ds, done_ds, aor_ds, gen_p_ds, info_ds = self._run_env(env_ds)
+ # print("after dist slack")
- assert ts_ss == ts_ds
+ assert ts_ss == ts_ds, f"ts_ss={ts_ss} != {ts_ds}=ts_ds: info_ds={info_ds['exception']}, info_ss={info_ss['exception']}"
assert done_ss == done_ds
# non redispatchable gen are not affected
@@ -91,14 +99,21 @@ def test_after_reset(self):
_ = self.env_ds.reset()
self._prepare_env(self.env_ds)
self._aux_test_different(self.env_ss, self.env_ds)
-
+
+ def _aux_get_kwargs_runner(self):
+ return dict(nb_episode=1,
+ max_iter=self.max_iter_real,
+ add_detailed_output=True,
+ env_seeds=[0])
+
def test_after_runner(self):
"""test I can use the runner"""
runner_ss = Runner(**self.env_ss.get_params_for_runner())
runner_ds = Runner(**self.env_ds.get_params_for_runner())
- res_ss = runner_ss.run(nb_episode=1, max_iter=self.max_iter_real, add_detailed_output=True)
- res_ds = runner_ds.run(nb_episode=1, max_iter=self.max_iter_real, add_detailed_output=True)
- assert res_ss[0][3] == res_ds[0][3] # same number of steps survived
+ res_ss = runner_ss.run(**self._aux_get_kwargs_runner())
+ res_ds = runner_ds.run(**self._aux_get_kwargs_runner())
+ if res_ss[0][3] != res_ds[0][3]: # same number of steps survived
+ raise RuntimeError(f"{res_ss[0][3]} vs {res_ds[0][3]}: ")
assert res_ss[0][2] != res_ds[0][2] # not the same reward
ep_ss = res_ss[0][-1]
ep_ds = res_ds[0][-1]
diff --git a/lightsim2grid/tests/test_init_from_pypowsybl.py b/lightsim2grid/tests/test_init_from_pypowsybl.py
index e9d8c407..24bc8efe 100644
--- a/lightsim2grid/tests/test_init_from_pypowsybl.py
+++ b/lightsim2grid/tests/test_init_from_pypowsybl.py
@@ -22,11 +22,9 @@
class AuxInitFromPyPowSyBl:
def get_pypo_grid(self):
return pp.network.create_ieee14()
-
- def get_gen_slack_id(self):
- return 0
def get_slackbus_id(self):
+ # id in pandapower
return 0
def get_equiv_pdp_grid(self):
@@ -57,7 +55,7 @@ def setUp(self) -> None:
self.ref_samecase = None
# init lightsim2grid model
- self.gridmodel = init(self.network_ref, gen_slack_id=self.get_gen_slack_id())
+ self.gridmodel = init(self.network_ref, slack_bus_id=self.get_slackbus_id())
# use some data
self.nb_bus_total = self.network_ref.get_buses().shape[0]
@@ -86,17 +84,22 @@ def test_compare_pp(self):
v_ls = self.gridmodel.dc_pf(1.0 * self.V_init_dc, 2, self.tol)
v_ls_ref = self.ref_samecase.dc_pf(1.0 * self.V_init_dc, 2, self.tol)
slack_id = self.get_slackbus_id()
- # (array([ 30, 31, 212, 218, 265]), array([265, 31, 212, 218, 30]))
+ reorder = self.gridmodel._orig_to_ls.reshape(1, -1)
# for case 118
- # bus_or = [64]
- # bus_ex = [67]
- # lines = [el.id for el in self.gridmodel.get_lines() if (el.bus_or_id in bus_or and el.bus_ex_id in bus_ex) or (el.bus_or_id in bus_ex and el.bus_ex_id in bus_or)]
+ # reorder_flat = reorder.reshape(-1)
+ # bus_or = [64] # pandapower
+ # bus_ex = [67] # pandapower
+ # lines = [el.id for el in self.gridmodel.get_lines() if (reorder_flat[el.bus_or_id] in bus_or and reorder_flat[el.bus_ex_id] in bus_ex) or (reorder_flat[el.bus_or_id] in bus_ex and reorder_flat[el.bus_ex_id] in bus_or)]
+ # lines = [el.id for el in self.gridmodel.get_lines() if (el.bus_or_id in reorder_flat[bus_or] and el.bus_ex_id in reorder_flat[bus_ex]) or (el.bus_or_id in reorder_flat[bus_ex] and el.bus_ex_id in reorder_flat[bus_or])]
+ # tmp_or = [reorder_flat[el.bus_or_id] for el in self.gridmodel.get_lines()]
+ # tmp_ex = [reorder_flat[el.bus_ex_id] for el in self.gridmodel.get_lines()]
+ # lines = [el.id for el in self.gridmodel.get_trafos() if (reorder_flat[el.bus_hv_id] in bus_or and reorder_flat[el.bus_lv_id] in bus_ex) or (reorder_flat[el.bus_hv_id] in bus_ex and reorder_flat[el.bus_lv_id] in bus_or)]
+ # tmp_or = [reorder_flat[el.bus_hv_id] for el in self.gridmodel.get_trafos()]
+ # tmp_ex = [reorder_flat[el.bus_lv_id] for el in self.gridmodel.get_trafos()]
# lines_ref = [el.id for el in self.ref_samecase.get_lines() if (el.bus_or_id in bus_or and el.bus_ex_id in bus_ex) or (el.bus_or_id in bus_ex and el.bus_ex_id in bus_or)]
# if not lines_ref:
# lines_ref = [el.id for el in self.ref_samecase.get_trafos() if (el.bus_hv_id in bus_or and el.bus_lv_id in bus_ex) or (el.bus_hv_id in bus_ex and el.bus_lv_id in bus_or)]
- # import pdb
- # pdb.set_trace()
# # self.pp_samecase["_ppc"]["internal"]["Bbus"]
# self.pp_samecase["_ppc"]["internal"]["Ybus"][64,67]
# self.pp_samecase["_ppc"]["internal"]["bus"]
@@ -114,61 +117,63 @@ def test_compare_pp(self):
# # self.pp_samecase["_ppc"]["internal"]["Bbus"]
# self.pp_samecase["_ppc"]["internal"]["Ybus"][64,67]
# self.pp_samecase["_ppc"]["internal"]["bus"]
-
- assert np.abs(v_ls - v_ls_ref).max() <= self.tol_eq, "error for vresults for dc"
- tmp_ = self.gridmodel.get_dcYbus() - self.ref_samecase.get_dcYbus()
- assert np.abs(tmp_).max() <= self.tol_eq, "error for dcYbus"
+ max_ = np.abs(v_ls[reorder] - v_ls_ref).max()
+ # assert max_ <= self.tol_eq, f"error for vresults for dc: {max_:.2e}"
+ tmp_ = self.gridmodel.get_dcYbus()[reorder.T, reorder].todense() - self.ref_samecase.get_dcYbus().todense()
+ max_ = np.abs(tmp_).max()
+ mat_ls = self.gridmodel.get_dcYbus()[reorder.T, reorder].todense()
+ mat_pp = self.ref_samecase.get_dcYbus().todense()
+ assert max_ <= self.tol_eq, f"error for dcYbus: {max_:.2e}"
# check Sbus without slack
+ Sbus_ordered = self.gridmodel.get_dcSbus()[reorder].reshape(-1)
if slack_id > 0:
- assert np.abs(self.gridmodel.get_Sbus()[:slack_id] - self.ref_samecase.get_Sbus()[:slack_id]).max() <= self.tol_eq, "error for dc Sbus"
- if slack_id != self.gridmodel.get_Sbus().shape[0] - 1:
- assert np.abs(self.gridmodel.get_Sbus()[(slack_id+1):] - self.ref_samecase.get_Sbus()[(slack_id+1):]).max() <= self.tol_eq, "error for dc Sbus"
+ max_ = np.abs(Sbus_ordered[:slack_id] - self.ref_samecase.get_dcSbus()[:slack_id]).max()
+ assert max_ <= self.tol_eq, f"error for dc Sbus: {max_:.2e}"
+ if slack_id != self.gridmodel.get_dcSbus().shape[0] - 1:
+ max_ = np.abs(Sbus_ordered[(slack_id+1):] - self.ref_samecase.get_dcSbus()[(slack_id+1):]).max()
+ assert max_ <= self.tol_eq, f"error for dc Sbus: {max_:.2e}"
# same in AC
v_ls = self.gridmodel.ac_pf(self.V_init_ac, 2, self.tol)
v_ls_ref = self.ref_samecase.ac_pf(self.V_init_ac, 2, self.tol)
- assert np.abs(self.gridmodel.get_Ybus() - self.ref_samecase.get_Ybus()).max() <= self.tol_eq, "error for Ybus"
+ max_ = np.abs(self.gridmodel.get_Ybus()[reorder.T, reorder] - self.ref_samecase.get_Ybus()).max()
+ assert max_ <= self.tol_eq, f"error for Ybus: {max_:.2e}"
# check Sbus without slack
+ Sbus_ordered = self.gridmodel.get_Sbus()[reorder].reshape(-1)
if slack_id > 0:
- assert np.abs(self.gridmodel.get_Sbus()[:slack_id] - self.ref_samecase.get_Sbus()[:slack_id]).max() <= self.tol_eq, "error for dc Sbus"
+ max_ = np.abs(Sbus_ordered[:slack_id] - self.ref_samecase.get_Sbus()[:slack_id]).max()
+ assert max_ <= self.tol_eq, f"error for dc Sbus: {max_:.2e}"
if slack_id != self.gridmodel.get_Sbus().shape[0] - 1:
- assert np.abs(self.gridmodel.get_Sbus()[(slack_id+1):] - self.ref_samecase.get_Sbus()[(slack_id+1):]).max() <= self.tol_eq, "error for dc Sbus"
+ max_ = np.abs(Sbus_ordered[(slack_id+1):] - self.ref_samecase.get_Sbus()[(slack_id+1):]).max()
+ assert max_ <= self.tol_eq, f"error for dc Sbus : {max_:.2e}"
def test_dc_pf(self):
"""test I get the same results as pandapower in dc"""
v_ls = self.gridmodel.dc_pf(self.V_init_dc, 2, self.tol)
+ reorder = self.gridmodel._orig_to_ls.reshape(1, -1)
if self.compare_pp():
v_ls_ref = self.ref_samecase.dc_pf(self.V_init_dc, 2, self.tol)
- assert np.abs(v_ls - v_ls_ref).max() <= self.tol_eq, "error for vresults for dc"
+ assert np.abs(v_ls[reorder] - v_ls_ref).max() <= self.tol_eq, f"error for vresults for dc: {np.abs(v_ls[reorder] - v_ls_ref).max():.2e}"
lf.run_dc(self.network_ref)
if self.compare_pp():
pdp.rundcpp(self.pp_samecase)
- # for case 300
- # np.where(np.abs(v_ang_ls - v_ang_pypo) > 3) # => 173, 177
- # bus_or = [173]
- # bus_ex = [177]
- # # lines = [el.id for el in self.gridmodel.get_lines() if (el.bus_or_id in bus_or and el.bus_ex_id in bus_ex) or (el.bus_or_id in bus_ex and el.bus_ex_id in bus_or)]
- # lines = [el.id for el in self.gridmodel.get_lines() if (el.bus_or_id in bus_or or el.bus_or_id in bus_ex or el.bus_ex_id in bus_or or el.bus_ex_id in bus_ex)]
- # trafos = [el.id for el in self.gridmodel.get_trafos() if (el.bus_hv_id in bus_or or el.bus_hv_id in bus_ex or el.bus_lv_id in bus_or or el.bus_lv_id in bus_ex)]
- # shunts = [el.id for el in self.gridmodel.get_shunts() if el.bus_id in bus_or or el.bus_id in bus_ex]
- # trafo 77 => 204 2040 in cdf file
- # trafo 85 =>
-
# v_mag not really relevant in dc so i study only va
v_ang_pypo = self.network_ref.get_buses()["v_angle"].values
v_ang_ls = np.rad2deg(np.angle(v_ls))
+ # np.where(np.abs(v_ang_ls[reorder] - v_ang_pypo) >= 9.)
if self.compare_pp():
v_ang_pp = self.pp_samecase.res_bus["va_degree"].values
- assert np.abs(v_ang_ls - v_ang_pp).max() <= self.tol_eq, "error for va results for dc"
- assert np.abs(v_ang_ls - v_ang_pypo).max() <= self.tol_eq
+ assert np.abs(v_ang_ls[reorder] - v_ang_pp).max() <= self.tol_eq, f"error for va results for dc: {np.abs(v_ang_ls[reorder] - v_ang_pp).max():.2e}"
+ assert np.abs(v_ang_ls[reorder] - v_ang_pypo).max() <= self.tol_eq, f"error for va results for dc: {np.abs(v_ang_ls[reorder] - v_ang_pypo).max():.2e}"
def test_ac_pf(self):
# run the powerflows
v_ls = self.gridmodel.ac_pf(1.0 * self.V_init_ac, 10, self.tol)
+ reorder = self.gridmodel._orig_to_ls.reshape(1, -1)
if self.compare_pp():
v_ls_ref = self.ref_samecase.ac_pf(1.0 * self.V_init_ac, 10, self.tol)
- assert np.abs(v_ls - v_ls_ref).max() <= self.tol_eq, "error for vresults for ac"
+ assert np.abs(v_ls[reorder] - v_ls_ref).max() <= self.tol_eq, f"error for vresults for ac: {np.abs(v_ls[reorder] - v_ls_ref).max():.2e}"
try:
param = lf.Parameters(voltage_init_mode=pp._pypowsybl.VoltageInitMode.UNIFORM_VALUES,
@@ -178,7 +183,7 @@ def test_ac_pf(self):
phase_shifter_regulation_on=False,
distributed_slack=False,
provider_parameters={"slackBusSelectionMode": "NAME",
- "slackBusesIds": self.network_ref.get_buses().iloc[self.get_slackbus_id()].name}
+ "slackBusesIds": self.network_ref.get_buses().iloc[self.get_slackbus_id()].name}
)
except TypeError:
param = lf.Parameters(voltage_init_mode=pp._pypowsybl.VoltageInitMode.UNIFORM_VALUES,
@@ -189,42 +194,46 @@ def test_ac_pf(self):
distributed_slack=False,
provider_parameters={"slackBusSelectionMode": "NAME",
"slackBusesIds": self.network_ref.get_buses().iloc[self.get_slackbus_id()].name}
- )
+ )
+
res_pypow = lf.run_ac(self.network_ref, parameters=param)
+ bus_ref_kv = self.network_ref.get_voltage_levels().loc[self.network_ref.get_buses()["voltage_level_id"].values]["nominal_v"].values
+ v_mag_pypo = self.network_ref.get_buses()["v_mag"].values / bus_ref_kv
+ v_ang_pypo = self.network_ref.get_buses()["v_angle"].values
if self.compare_pp():
pdp.runpp(self.pp_samecase, init="flat")
-
+
+ # check that pypow solution is "feasible" as seen by lightsim2grid
+ if res_pypow[0].status == pp._pypowsybl.LoadFlowComponentStatus.CONVERGED:
+ v_pypow = v_mag_pypo * np.exp(1j * np.deg2rad(v_ang_pypo))
+ pypow_to_ls = np.argsort(reorder).reshape(-1)
+ v_pypow_ls = self.gridmodel.check_solution(v_pypow[pypow_to_ls], False)
+ assert np.abs(v_pypow_ls).max() <= 10. * self.tol_eq, f"error when checking results of pypowsybl in lightsim2grid: {np.abs(v_pypow_ls).max():.2e}"
+
# check voltage angles
- v_ang_pypo = self.network_ref.get_buses()["v_angle"].values
v_ang_ls = np.rad2deg(np.angle(v_ls))
if self.compare_pp():
v_ang_pp = self.pp_samecase.res_bus["va_degree"].values - self.pp_samecase.ext_grid["va_degree"].values
- assert np.abs(v_ang_ls - v_ang_pp).max() <= self.tol_eq, "error for va results for ac"
+ assert np.abs(v_ang_ls[reorder] - v_ang_pp).max() <= self.tol_eq, f"error for va results for ac: {np.abs(v_ang_ls[reorder] - v_ang_pp).max():.2e}"
if res_pypow[0].status == pp._pypowsybl.LoadFlowComponentStatus.CONVERGED:
- assert np.abs(v_ang_ls - v_ang_pypo).max() <= self.tol_eq
+ assert np.abs(v_ang_ls[reorder] - v_ang_pypo).max() <= self.tol_eq, f"error for va results for ac: {np.abs(v_ang_ls[reorder] - v_ang_pypo).max():.2e}"
# check voltage magnitudes
- bus_ref_kv = self.network_ref.get_voltage_levels().loc[self.network_ref.get_buses()["voltage_level_id"].values]["nominal_v"].values
- v_mag_pypo = self.network_ref.get_buses()["v_mag"].values / bus_ref_kv
v_mag_ls = np.abs(v_ls)
if self.compare_pp():
v_mag_pp = self.pp_samecase.res_bus["vm_pu"].values
- assert np.abs(v_mag_ls - v_mag_pp).max() <= self.tol_eq, "error for va results for dc"
+ assert np.abs(v_mag_ls[reorder] - v_mag_pp).max() <= self.tol_eq, f"error for va results for dc: {np.abs(v_mag_ls[reorder] - v_mag_pp).max():.2e}"
if res_pypow[0].status == pp._pypowsybl.LoadFlowComponentStatus.CONVERGED:
- assert np.abs(v_mag_ls - v_mag_pypo).max() <= self.tol_eq
+ assert np.abs(v_mag_ls[reorder] - v_mag_pypo).max() <= self.tol_eq, f"error for va results for dc: {np.abs(v_mag_ls[reorder] - v_mag_pypo).max():.2e}"
- # check that pypow solution is "feasible" as seen by lightsim2grid
- if res_pypow[0].status == pp._pypowsybl.LoadFlowComponentStatus.CONVERGED:
- v_pypow = v_mag_pypo * np.exp(1j * np.deg2rad(v_ang_pypo))
- v_pypow_ls = self.gridmodel.check_solution(v_pypow, False)
- assert np.abs(v_pypow_ls).max() <= 10. * self.tol_eq
-
+
class TestCase14FromPypo(AuxInitFromPyPowSyBl, unittest.TestCase):
pass
class TestCase30FromPypo(AuxInitFromPyPowSyBl, unittest.TestCase):
+ """compare from the ieee 30"""
# unittest.TestCase does not work because of https://github.com/powsybl/pypowsybl/issues/644
def get_pypo_grid(self):
return pp.network.create_ieee30()
@@ -234,18 +243,10 @@ def get_equiv_pdp_grid(self):
class TestCase57FromPypo(AuxInitFromPyPowSyBl, unittest.TestCase):
+ """compare from the ieee 57"""
# does not appear to be the same grid !
def get_pypo_grid(self):
res = pp.network.create_ieee57()
- # df = res.get_2_windings_transformers()[["rated_u1"]]
- # df["rated_u1"] = 1.0
- # res.update_2_windings_transformers(df)
-
- # df = res.get_lines()[["b1", "b2", "r"]]
- # df["b1"] = 0.
- # df["b2"] = 0.
- # df["r"] = 0.
- # res.update_lines(df)
return res
def get_equiv_pdp_grid(self):
@@ -259,15 +260,13 @@ def get_tol_eq(self):
class TestCase118FromPypo(AuxInitFromPyPowSyBl, unittest.TestCase):
- # unittest.TestCase does not work because of https://github.com/powsybl/pypowsybl/issues/644
+ """compare from the ieee 118: does not work because of https://github.com/e2nIEE/pandapower/issues/2131"""
+ # does not work because of https://github.com/e2nIEE/pandapower/issues/2131
def get_pypo_grid(self):
return pp.network.create_ieee118()
def get_equiv_pdp_grid(self):
return pn.case118()
-
- def get_gen_slack_id(self):
- return 29
def get_slackbus_id(self):
return 68
@@ -281,10 +280,14 @@ def compare_pp(self):
class TestCase300FromPypo(AuxInitFromPyPowSyBl):
- # does not work, probably grid not ordered the same
- # need further investigation
+ """compare from the ieee 300"""
+ # does not work because of phase tap changer
def get_pypo_grid(self):
- return pp.network.create_ieee300()
+ res = pp.network.create_ieee300()
+ df = res.get_shunt_compensators()[["connected"]] # "b", "g",
+ df["connected"] = False
+ res.update_shunt_compensators(df)
+ return res
def get_equiv_pdp_grid(self):
return pn.case300()
@@ -295,10 +298,6 @@ def get_tol_eq(self):
def compare_pp(self):
return False
-
- def get_gen_slack_id(self):
- # does not work with PP, probably bus not ordered the same
- return 55
def get_slackbus_id(self):
# does not work with PP, probably bus not ordered the same
diff --git a/lightsim2grid/tests/test_issue_56.py b/lightsim2grid/tests/test_issue_56.py
index 6b0a3a6a..412615b8 100644
--- a/lightsim2grid/tests/test_issue_56.py
+++ b/lightsim2grid/tests/test_issue_56.py
@@ -15,7 +15,7 @@
import numpy as np
import grid2op
from lightsim2grid import LightSimBackend, SolverType
-from lightsim2grid.securityAnalysis import SecurityAnalysis
+from lightsim2grid.contingencyAnalysis import ContingencyAnalysis
import pdb
@@ -24,7 +24,7 @@ def setUp(self) -> None:
with warnings.catch_warnings():
warnings.filterwarnings("ignore")
self.env = grid2op.make("l2rpn_case14_sandbox", test=True, backend=LightSimBackend())
- self.sa = SecurityAnalysis(self.env)
+ self.sa = ContingencyAnalysis(self.env)
def test_dc(self):
self.sa.add_all_n1_contingencies()
@@ -35,27 +35,27 @@ def test_dc(self):
self.sa.add_all_n1_contingencies()
res_p_dc, res_a_dc, res_v_dc = self.sa.get_flows()
- assert np.any(res_p != res_p_dc)
- assert np.any(res_a != res_a_dc)
- assert np.any(res_v != res_v_dc)
+ assert np.any(res_p != res_p_dc), "DC and AC solver leads to same results"
+ assert np.any(res_a != res_a_dc), "DC and AC solver leads to same results"
+ assert np.any(res_v != res_v_dc), "DC and AC solver leads to same results"
assert self.sa.computer.get_solver_type() == SolverType.DC
nb_bus = self.env.n_sub
nb_powerline = len(self.env.backend._grid.get_lines())
# now check with the DC computation
- for l_id in range(self.env.n_line):
+ for l_id in range(type(self.env).n_line):
grid_model = self.env.backend._grid.copy()
if l_id < nb_powerline:
grid_model.deactivate_powerline(l_id)
else:
grid_model.deactivate_trafo(l_id - nb_powerline)
- grid_model.tell_topo_changed()
+ grid_model.tell_solver_need_reset()
V = 1.0 * self.env.backend.V # np.ones(2 * self.env.n_sub, dtype=np.complex_)
res = grid_model.dc_pf(V, 10, 1e-8)
if len(res):
# model has converged, I check the results are the same
# check voltages
- assert np.allclose(res_v_dc[l_id, :nb_bus], res[:nb_bus]), f"error for contingency {l_id}"
+ assert np.allclose(res_v_dc[l_id, :nb_bus], res[:nb_bus]), f"error for contingency {l_id}: {np.abs(res_v_dc[l_id, :nb_bus]-res[:nb_bus]).max():.2e}"
# now check the flows
pl_dc, ql_dc, vl_dc, al_dc = grid_model.get_lineor_res()
pt_dc, qt_dc, vt_dc, at_dc = grid_model.get_trafohv_res()
@@ -77,7 +77,7 @@ def setUp(self) -> None:
with warnings.catch_warnings():
warnings.filterwarnings("ignore")
self.env = grid2op.make("l2rpn_neurips_2020_track1", test=True, backend=LightSimBackend())
- self.sa = SecurityAnalysis(self.env)
+ self.sa = ContingencyAnalysis(self.env)
class TestSADC_118(TestSADC_14):
@@ -85,7 +85,7 @@ def setUp(self) -> None:
with warnings.catch_warnings():
warnings.filterwarnings("ignore")
self.env = grid2op.make("l2rpn_wcci_2022", test=True, backend=LightSimBackend())
- self.sa = SecurityAnalysis(self.env)
+ self.sa = ContingencyAnalysis(self.env)
if __name__ == "__main__":
diff --git a/lightsim2grid/tests/test_issue_66.py b/lightsim2grid/tests/test_issue_66.py
new file mode 100644
index 00000000..be2cc08e
--- /dev/null
+++ b/lightsim2grid/tests/test_issue_66.py
@@ -0,0 +1,99 @@
+# Copyright (c) 2023, RTE (https://www.rte-france.com)
+# See AUTHORS.txt
+# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
+# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
+# you can obtain one at http://mozilla.org/MPL/2.0/.
+# SPDX-License-Identifier: MPL-2.0
+# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
+
+import unittest
+import warnings
+from lightsim2grid import LightSimBackend
+from grid2op.Action import PlayableAction
+from grid2op.Rules import AlwaysLegal
+import grid2op
+
+class Issue66Tester(unittest.TestCase):
+ """issue is still not replicated and these tests pass"""
+ def setUp(self) -> None:
+ with warnings.catch_warnings():
+ warnings.filterwarnings("ignore")
+ self.env = grid2op.make("educ_case14_storage", test=True, backend=LightSimBackend(),
+ action_class=PlayableAction,
+ gamerules_class=AlwaysLegal)
+ param = self.env.parameters
+ param.NB_TIMESTEP_COOLDOWN_LINE = 0
+ param.NB_TIMESTEP_COOLDOWN_SUB = 0
+ self.env.change_parameters(param)
+ self.env.change_forecast_parameters(param)
+ return super().setUp()
+
+ def tearDown(self) -> None:
+ self.env.close()
+ return super().tearDown()
+
+ def test_disco_load(self):
+ """test i can disconnect a load"""
+ obs = self.env.reset()
+ act = self.env.action_space({"set_bus": {"loads_id": [(0, -1)]}})
+ obs, reward, done, info = self.env.step(act)
+ assert done
+ # should not raise any RuntimeError
+
+ def test_disco_gen(self):
+ """test i can disconnect a load"""
+ obs = self.env.reset()
+ act = self.env.action_space({"set_bus": {"generators_id": [(0, -1)]}})
+ obs, reward, done, info = self.env.step(act)
+ assert done
+ # should not raise any RuntimeError
+
+ def test_change_bus_load(self):
+ """test i can disconnect a load"""
+ obs = self.env.reset()
+ act = self.env.action_space({"set_bus": {"loads_id": [(9, 2)],
+ "lines_or_id": [(14, 2)]}})
+ obs, reward, done, info = self.env.step(act)
+ assert len(info['exception']) == 0
+ assert not done
+ # should not raise any RuntimeError
+
+ # isolate the load
+ act = self.env.action_space({"set_bus": {"lines_or_id": [(14, 1)]}})
+ obs, reward, done, info = self.env.step(act)
+ assert done
+
+ def test_change_bus_gen(self):
+ """test i can disconnect a gen"""
+ obs = self.env.reset()
+ act = self.env.action_space({"set_bus": {"generators_id": [(0, 2)],
+ "lines_ex_id": [(0, 2)]}})
+ obs, reward, done, info = self.env.step(act)
+ assert len(info['exception']) == 0
+ assert not done
+ # should not raise any RuntimeError
+
+ # isolate the generator
+ act = self.env.action_space({"set_bus": {"lines_ex_id": [(0, 1)]}})
+ obs, reward, done, info = self.env.step(act)
+ assert done
+
+ def test_disco_storage(self):
+ """test i can disconnect a storage unit"""
+ obs = self.env.reset()
+ act = self.env.action_space({"set_bus": {"storages_id": [(0, -1)]}})
+ obs, reward, done, info = self.env.step(act)
+ assert len(info['exception']) == 0
+ assert not done
+ # should not raise any RuntimeError
+
+ act = self.env.action_space({"set_storage": [(0, -1)]})
+ obs, reward, done, info = self.env.step(act)
+ assert len(info['exception']) == 0
+ assert not done
+ # should not raise any RuntimeError
+
+
+if __name__ == "__main__":
+ unittest.main()
+
\ No newline at end of file
diff --git a/lightsim2grid/tests/test_multi_slack.py b/lightsim2grid/tests/test_multi_slack.py
index 6a98c656..387d6bf1 100644
--- a/lightsim2grid/tests/test_multi_slack.py
+++ b/lightsim2grid/tests/test_multi_slack.py
@@ -77,7 +77,7 @@ def test_Ybus(self):
# Ybus seen by pandapower
pp.runpp(self.net, **self.get_pp_options())
Ybus_pp_tmp = self.net["_ppc"]["internal"]["Ybus"]
- id_bus_pp = self.net._pd2ppc_lookups["bus"][self.gridmodel._ls_to_pp]
+ id_bus_pp = self.net._pd2ppc_lookups["bus"][self.gridmodel._ls_to_orig]
assert (id_bus_pp == np.arange(id_bus_pp.size)).all() # assert bus are sorted correctly and in the same order
# Ybus_pp = Ybus_pp_tmp[id_bus_pp, id_bus_pp] # does not work in don't know why... anyway
Ybus_pp = Ybus_pp_tmp
@@ -91,7 +91,7 @@ def test_Ybus(self):
def test_Sbus(self):
# Ybus seen by pandapower
pp.runpp(self.net, **self.get_pp_options())
- bus_lookup = self.net._pd2ppc_lookups["bus"] #[self.gridmodel._ls_to_pp]
+ bus_lookup = self.net._pd2ppc_lookups["bus"] #[self.gridmodel._ls_to_orig]
Sbus_pp = self.net["_ppc"]["internal"]["Sbus"]
*_, V0 = self._aux_get_init_pp_data()
# Ybus from lightsim2grid
diff --git a/lightsim2grid/tests/test_n_busbar_per_sub.py b/lightsim2grid/tests/test_n_busbar_per_sub.py
new file mode 100644
index 00000000..aa51bb0a
--- /dev/null
+++ b/lightsim2grid/tests/test_n_busbar_per_sub.py
@@ -0,0 +1,307 @@
+# Copyright (c) 2020, RTE (https://www.rte-france.com)
+# See AUTHORS.txt
+# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
+# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
+# you can obtain one at http://mozilla.org/MPL/2.0/.
+# SPDX-License-Identifier: MPL-2.0
+# This file is part of LightSim2grid, LightSim2grid a implements a c++ backend targeting the Grid2Op platform.
+import unittest
+import warnings
+import numpy as np
+
+import grid2op
+from grid2op.Action import CompleteAction
+
+from lightsim2grid import LightSimBackend
+
+class TestLightSimBackend_3busbars(unittest.TestCase):
+ def get_nb_bus(self):
+ return 3
+
+ def get_env_nm(self):
+ return "educ_case14_storage"
+
+ def get_backend_kwargs(self):
+ return dict()
+
+ def setUp(self) -> None:
+ with warnings.catch_warnings():
+ warnings.filterwarnings("ignore")
+ self.env = grid2op.make(self.get_env_nm(),
+ backend=LightSimBackend(**self.get_backend_kwargs()),
+ action_class=CompleteAction,
+ test=True,
+ n_busbar=self.get_nb_bus(),
+ _add_to_name=type(self).__name__ + f'_{self.get_nb_bus()}')
+ self.list_loc_bus = [-1] + list(range(1, type(self.env).n_busbar_per_sub + 1))
+ return super().setUp()
+
+ def tearDown(self) -> None:
+ self.env.close()
+ return super().tearDown()
+
+ def test_right_bus_made(self):
+ assert len(self.env.backend._grid.get_bus_vn_kv()) == self.get_nb_bus() * type(self.env).n_sub
+ assert (~np.array(self.env.backend._grid.get_bus_status())[type(self.env).n_sub:]).all()
+ assert (np.array(self.env.backend._grid.get_bus_status())[:type(self.env).n_sub]).all()
+
+ @staticmethod
+ def _aux_find_sub(env, obj_col):
+ """find a sub with 4 elements, the type of elements and at least 2 lines"""
+ cls = type(env)
+ res = None
+ for sub_id in range(cls.n_sub):
+ this_sub_mask = cls.grid_objects_types[:,cls.SUB_COL] == sub_id
+ this_sub = cls.grid_objects_types[this_sub_mask, :]
+ if this_sub.shape[0] <= 3:
+ # not enough element
+ continue
+ if (this_sub[:, obj_col] == -1).all():
+ # no load
+ continue
+ if ((this_sub[:, cls.LOR_COL] != -1) | (this_sub[:, cls.LEX_COL] != -1)).sum() <= 1:
+ # only 1 line
+ continue
+ el_id = this_sub[this_sub[:, obj_col] != -1, obj_col][0]
+ if (this_sub[:, cls.LOR_COL] != -1).any():
+ line_or_id = this_sub[this_sub[:, cls.LOR_COL] != -1, cls.LOR_COL][0]
+ line_ex_id = None
+ else:
+ line_or_id = None
+ line_ex_id = this_sub[this_sub[:, cls.LEX_COL] != -1, cls.LEX_COL][0]
+ res = (sub_id, el_id, line_or_id, line_ex_id)
+ break
+ return res
+
+ @staticmethod
+ def _aux_find_sub_shunt(env):
+ """find a sub with 4 elements, the type of elements and at least 2 lines"""
+ cls = type(env)
+ res = None
+ for el_id in range(cls.n_shunt):
+ sub_id = cls.shunt_to_subid[el_id]
+ this_sub_mask = cls.grid_objects_types[:,cls.SUB_COL] == sub_id
+ this_sub = cls.grid_objects_types[this_sub_mask, :]
+ if this_sub.shape[0] <= 3:
+ # not enough element
+ continue
+ if ((this_sub[:, cls.LOR_COL] != -1) | (this_sub[:, cls.LEX_COL] != -1)).sum() <= 1:
+ # only 1 line
+ continue
+ if (this_sub[:, cls.LOR_COL] != -1).any():
+ line_or_id = this_sub[this_sub[:, cls.LOR_COL] != -1, cls.LOR_COL][0]
+ line_ex_id = None
+ else:
+ line_or_id = None
+ line_ex_id = this_sub[this_sub[:, cls.LEX_COL] != -1, cls.LEX_COL][0]
+ res = (sub_id, el_id, line_or_id, line_ex_id)
+ break
+ return res
+
+ def test_move_load(self):
+ cls = type(self.env)
+ res = self._aux_find_sub(self.env, cls.LOA_COL)
+ if res is None:
+ raise RuntimeError(f"Cannot carry the test 'test_move_load' as "
+ "there are no suitable subastation in your grid.")
+ (sub_id, el_id, line_or_id, line_ex_id) = res
+ for new_bus in self.list_loc_bus:
+ if line_or_id is not None:
+ act = self.env.action_space({"set_bus": {"loads_id": [(el_id, new_bus)], "lines_or_id": [(line_or_id, new_bus)]}})
+ else:
+ act = self.env.action_space({"set_bus": {"loads_id": [(el_id, new_bus)], "lines_ex_id": [(line_ex_id, new_bus)]}})
+ bk_act = self.env._backend_action_class()
+ bk_act += act
+ self.env.backend.apply_action(bk_act)
+ global_bus = sub_id + (new_bus -1) * cls.n_sub
+ if new_bus >= 1:
+ assert self.env.backend._grid.get_loads()[el_id].bus_id == global_bus, f"error for new_bus {new_bus}: {self.env.backend._grid.get_loads()[el_id].bus_id} vs {global_bus}"
+ if line_or_id is not None:
+ assert self.env.backend._grid.get_lines()[line_or_id].bus_or_id == global_bus
+ else:
+ assert self.env.backend._grid.get_lines()[line_ex_id].bus_ex_id == global_bus
+ assert self.env.backend._grid.get_bus_status()[global_bus]
+ else:
+ assert not self.env.backend._grid.get_loads()[el_id].connected
+ if line_or_id is not None:
+ assert not self.env.backend._grid.get_lines()[line_or_id].connected
+ else:
+ assert not self.env.backend._grid.get_lines()[line_ex_id].connected
+ topo_vect = 1 * self.env.backend.get_topo_vect()
+ assert topo_vect[cls.load_pos_topo_vect[el_id]] == new_bus, f"{topo_vect[cls.load_pos_topo_vect[el_id]]} vs {new_bus}"
+
+ def test_move_gen(self):
+ cls = type(self.env)
+ res = self._aux_find_sub(self.env, cls.GEN_COL)
+ if res is None:
+ raise RuntimeError(f"Cannot carry the test 'test_move_gen' as "
+ "there are no suitable subastation in your grid.")
+ (sub_id, el_id, line_or_id, line_ex_id) = res
+ for new_bus in self.list_loc_bus:
+ if line_or_id is not None:
+ act = self.env.action_space({"set_bus": {"generators_id": [(el_id, new_bus)], "lines_or_id": [(line_or_id, new_bus)]}})
+ else:
+ act = self.env.action_space({"set_bus": {"generators_id": [(el_id, new_bus)], "lines_ex_id": [(line_ex_id, new_bus)]}})
+ bk_act = self.env._backend_action_class()
+ bk_act += act
+ self.env.backend.apply_action(bk_act)
+ global_bus = sub_id + (new_bus -1) * cls.n_sub
+ if new_bus >= 1:
+ assert self.env.backend._grid.get_generators()[el_id].bus_id == global_bus, f"error for new_bus {new_bus}: {self.env.backend._grid.get_loads()[el_id].bus_id} vs {global_bus}"
+ if line_or_id is not None:
+ assert self.env.backend._grid.get_lines()[line_or_id].bus_or_id == global_bus
+ else:
+ assert self.env.backend._grid.get_lines()[line_ex_id].bus_ex_id == global_bus
+ assert self.env.backend._grid.get_bus_status()[global_bus]
+ else:
+ assert not self.env.backend._grid.get_generators()[el_id].connected
+ if line_or_id is not None:
+ assert not self.env.backend._grid.get_lines()[line_or_id].connected
+ else:
+ assert not self.env.backend._grid.get_lines()[line_ex_id].connected
+ topo_vect = 1 * self.env.backend.get_topo_vect()
+ assert topo_vect[cls.gen_pos_topo_vect[el_id]] == new_bus, f"{topo_vect[cls.gen_pos_topo_vect[el_id]]} vs {new_bus}"
+
+ def test_move_storage(self):
+ cls = type(self.env)
+ res = self._aux_find_sub(self.env, cls.STORAGE_COL)
+ if res is None:
+ raise RuntimeError(f"Cannot carry the test 'test_move_storage' as "
+ "there are no suitable subastation in your grid.")
+ (sub_id, el_id, line_or_id, line_ex_id) = res
+ for new_bus in self.list_loc_bus:
+ if line_or_id is not None:
+ act = self.env.action_space({"set_bus": {"storages_id": [(el_id, new_bus)], "lines_or_id": [(line_or_id, new_bus)]}})
+ else:
+ act = self.env.action_space({"set_bus": {"storages_id": [(el_id, new_bus)], "lines_ex_id": [(line_ex_id, new_bus)]}})
+ bk_act = self.env._backend_action_class()
+ bk_act += act
+ self.env.backend.apply_action(bk_act)
+ global_bus = sub_id + (new_bus -1) * cls.n_sub
+ if new_bus >= 1:
+ assert self.env.backend._grid.get_storages()[el_id].bus_id == global_bus, f"error for new_bus {new_bus}: {self.env.backend._grid.get_loads()[el_id].bus_id} vs {global_bus}"
+ if line_or_id is not None:
+ assert self.env.backend._grid.get_lines()[line_or_id].bus_or_id == global_bus
+ else:
+ assert self.env.backend._grid.get_lines()[line_ex_id].bus_ex_id == global_bus
+ assert self.env.backend._grid.get_bus_status()[global_bus]
+ else:
+ assert not self.env.backend._grid.get_storages()[el_id].connected
+ if line_or_id is not None:
+ assert not self.env.backend._grid.get_lines()[line_or_id].connected
+ else:
+ assert not self.env.backend._grid.get_lines()[line_ex_id].connected
+ topo_vect = 1 * self.env.backend.get_topo_vect()
+ assert topo_vect[cls.storage_pos_topo_vect[el_id]] == new_bus, f"{topo_vect[cls.storage_pos_topo_vect[el_id]]} vs {new_bus}"
+
+ def test_move_line_or(self):
+ cls = type(self.env)
+ line_id = 0
+ for new_bus in self.list_loc_bus:
+ act = self.env.action_space({"set_bus": {"lines_or_id": [(line_id, new_bus)]}})
+ bk_act = self.env._backend_action_class()
+ bk_act += act
+ self.env.backend.apply_action(bk_act)
+ global_bus = cls.line_or_to_subid[line_id] + (new_bus -1) * cls.n_sub
+ if new_bus >= 1:
+ assert self.env.backend._grid.get_lines()[line_id].bus_or_id == global_bus
+ assert self.env.backend._grid.get_bus_status()[global_bus]
+ else:
+ assert not self.env.backend._grid.get_lines()[line_id].connected
+ topo_vect = 1 * self.env.backend.get_topo_vect()
+ assert topo_vect[cls.line_or_pos_topo_vect[line_id]] == new_bus, f"{topo_vect[cls.line_or_pos_topo_vect[line_id]]} vs {new_bus}"
+
+ def test_move_line_ex(self):
+ cls = type(self.env)
+ line_id = 0
+ for new_bus in self.list_loc_bus:
+ act = self.env.action_space({"set_bus": {"lines_ex_id": [(line_id, new_bus)]}})
+ bk_act = self.env._backend_action_class()
+ bk_act += act
+ self.env.backend.apply_action(bk_act)
+ global_bus = cls.line_ex_to_subid[line_id] + (new_bus -1) * cls.n_sub
+ if new_bus >= 1:
+ assert self.env.backend._grid.get_lines()[line_id].bus_ex_id == global_bus
+ assert self.env.backend._grid.get_bus_status()[global_bus]
+ else:
+ assert not self.env.backend._grid.get_lines()[line_id].connected
+ topo_vect = 1 * self.env.backend.get_topo_vect()
+ assert topo_vect[cls.line_ex_pos_topo_vect[line_id]] == new_bus, f"{topo_vect[cls.line_ex_pos_topo_vect[line_id]]} vs {new_bus}"
+
+ def test_move_shunt(self):
+ cls = type(self.env)
+ res = self._aux_find_sub_shunt(self.env)
+ if res is None:
+ raise RuntimeError(f"Cannot carry the test 'test_move_load' as "
+ "there are no suitable subastation in your grid.")
+ (sub_id, el_id, line_or_id, line_ex_id) = res
+ for new_bus in self.list_loc_bus:
+ if line_or_id is not None:
+ act = self.env.action_space({"shunt": {"set_bus": [(el_id, new_bus)]}, "set_bus": {"lines_or_id": [(line_or_id, new_bus)]}})
+ else:
+ act = self.env.action_space({"shunt": {"set_bus": [(el_id, new_bus)]}, "set_bus": {"lines_ex_id": [(line_ex_id, new_bus)]}})
+ bk_act = self.env._backend_action_class()
+ bk_act += act
+ self.env.backend.apply_action(bk_act)
+ global_bus = sub_id + (new_bus -1) * cls.n_sub
+ if new_bus >= 1:
+ assert self.env.backend._grid.get_shunts()[el_id].bus_id == global_bus, f"error for new_bus {new_bus}: {self.env.backend._grid.get_loads()[el_id].bus_id} vs {global_bus}"
+ if line_or_id is not None:
+ assert self.env.backend._grid.get_lines()[line_or_id].bus_or_id == global_bus
+ else:
+ assert self.env.backend._grid.get_lines()[line_ex_id].bus_ex_id == global_bus
+ assert self.env.backend._grid.get_bus_status()[global_bus]
+ else:
+ assert not self.env.backend._grid.get_shunts()[el_id].connected
+ if line_or_id is not None:
+ assert not self.env.backend._grid.get_lines()[line_or_id].connected
+ else:
+ assert not self.env.backend._grid.get_lines()[line_ex_id].connected
+
+ def test_check_kirchoff(self):
+ cls = type(self.env)
+ res = self._aux_find_sub(self.env, cls.LOA_COL)
+ if res is None:
+ raise RuntimeError("Cannot carry the test 'test_move_load' as "
+ "there are no suitable subastation in your grid.")
+ (sub_id, el_id, line_or_id, line_ex_id) = res
+ for new_bus in self.list_loc_bus:
+ if new_bus <= -1:
+ continue
+ if line_or_id is not None:
+ act = self.env.action_space({"set_bus": {"loads_id": [(el_id, new_bus)], "lines_or_id": [(line_or_id, new_bus)]}})
+ else:
+ act = self.env.action_space({"set_bus": {"loads_id": [(el_id, new_bus)], "lines_ex_id": [(line_ex_id, new_bus)]}})
+ bk_act = self.env._backend_action_class()
+ bk_act += act
+ self.env.backend.apply_action(bk_act)
+ conv, maybe_exc = self.env.backend.runpf()
+ assert conv, f"error : {maybe_exc}"
+ p_subs, q_subs, p_bus, q_bus, diff_v_bus = self.env.backend.check_kirchoff()
+ # assert laws are met
+ assert np.abs(p_subs).max() <= 1e-5, f"error for busbar {new_bus}: {np.abs(p_subs).max():.2e}"
+ assert np.abs(q_subs).max() <= 1e-5, f"error for busbar {new_bus}: {np.abs(q_subs).max():.2e}"
+ assert np.abs(p_bus).max() <= 1e-5, f"error for busbar {new_bus}: {np.abs(p_bus).max():.2e}"
+ assert np.abs(q_bus).max() <= 1e-5, f"error for busbar {new_bus}: {np.abs(q_bus).max():.2e}"
+ assert np.abs(diff_v_bus).max() <= 1e-5, f"error for busbar {new_bus}: {np.abs(diff_v_bus).max():.2e}"
+
+
+class TestLightSimBackend_1busbar(TestLightSimBackend_3busbars):
+ def get_nb_bus(self):
+ return 1
+
+
+class TestLightSimBackend_3busbars_iidm(TestLightSimBackend_3busbars):
+ def get_env_nm(self):
+ return "./case_14_storage_iidm"
+
+ def get_backend_kwargs(self):
+ return dict(loader_method="pypowsybl",
+ loader_kwargs={"use_buses_for_sub": True,
+ "double_bus_per_sub": True,
+ "gen_slack_id": 5}
+ )
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/lightsim2grid/tests/test_ptdf.py b/lightsim2grid/tests/test_ptdf.py
new file mode 100644
index 00000000..4a98e4ae
--- /dev/null
+++ b/lightsim2grid/tests/test_ptdf.py
@@ -0,0 +1,110 @@
+# Copyright (c) 2023, RTE (https://www.rte-france.com)
+# See AUTHORS.txt
+# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
+# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
+# you can obtain one at http://mozilla.org/MPL/2.0/.
+# SPDX-License-Identifier: MPL-2.0
+# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
+
+import unittest
+import pandapower as pp
+import pandapower.networks as pn
+import numpy as np
+import warnings
+from scipy.sparse.linalg import spsolve
+
+from lightsim2grid.gridmodel import init
+from lightsim2grid.solver import SolverType
+
+import pdb
+
+class TestCase14SLU(unittest.TestCase):
+ def make_grid(self):
+ case14 = pn.case14()
+ return case14
+
+ def get_solver_type(self):
+ return SolverType.DC
+
+ def setUp(self) -> None:
+ self.case = self.make_grid()
+ with warnings.catch_warnings():
+ warnings.filterwarnings("ignore")
+ self.gridmodel = init(self.case)
+ self.V_init = 1. * self.gridmodel.get_bus_vn_kv()
+ solver_type = self.get_solver_type()
+ if solver_type not in self.gridmodel.available_solvers():
+ self.skipTest("Solver type not supported on this platform")
+ self.gridmodel.change_solver(solver_type)
+ V = self.gridmodel.dc_pf(self.V_init, 1, 1e-8)
+ assert len(V), f"dc pf has diverged with error {self.gridmodel.get_dc_solver().get_error()}"
+ self.dcYbus = 1.0 * self.gridmodel.get_dcYbus()
+ self.dcSbus = 1.0 * self.gridmodel.get_dcSbus().real
+ self.Bbus = 1.0 * self.dcYbus.real
+ self.res_powerflow = 1.0 * np.concatenate((self.gridmodel.get_lineor_res()[0], self.gridmodel.get_trafohv_res()[0]))
+ self.nb = self.case.bus.shape[0]
+ self.nbr = self.case.line.shape[0] + self.case.trafo.shape[0]
+ self.slack_bus = self.case.ext_grid.iloc[0]["bus"]
+ self.noref = np.arange(1, self.nb) ## use bus 1 for voltage angle reference
+ self.noslack = np.flatnonzero(np.arange(self.nb) != self.slack_bus)
+ self.tol = 1e-6
+ return super().setUp()
+
+ def test_from_pp(self):
+ # test the right computation of Bf matrix (PTDF derived from python, might be slower)
+ Bf = 1.0 * self.gridmodel.get_Bf()
+ PTDF = np.zeros((self.nbr, self.nb))
+ PTDF[:, self.noslack] = spsolve(self.Bbus[np.ix_(self.noslack, self.noref)].T, Bf[:, self.noref].toarray().T).T
+ # test the solver works correctly
+ tmp_mat = self.Bbus[np.ix_(self.noslack, self.noref)].T.todense()
+ for line_id in range(self.nbr):
+ solve_error = np.abs(np.dot(tmp_mat, PTDF[line_id, self.noslack]).T - Bf[line_id, self.noref].toarray().T).max()
+ assert solve_error <= self.tol, f"error for line {line_id}: {solve_error:.2e}MW"
+ # test powerflow are correct
+ res_ptdf = np.dot(PTDF, self.dcSbus * self.gridmodel.get_sn_mva())
+ assert np.abs(res_ptdf - self.res_powerflow).max() <= self.tol, f"max error for powerflow: {np.abs(res_ptdf - self.res_powerflow).max():.2e}MW"
+
+ def test_ptdf_from_ls(self):
+ # now test the right computation of the PTDF
+ Bf = 1.0 * self.gridmodel.get_Bf()
+ PTDF2 = 1.0 * self.gridmodel.get_ptdf()
+ # test the solver works correctly
+ tmp_mat = self.Bbus[np.ix_(self.noslack, self.noref)].T.todense()
+ for line_id in range(self.nbr):
+ solve_error = np.abs(np.dot(tmp_mat, PTDF2[line_id, self.noslack]).T - Bf[line_id, self.noref].toarray().T).max()
+ assert solve_error <= self.tol, f"error for line {line_id}: {solve_error:.2e}MW"
+ # test powerflow are correct
+ res_ptdf2 = np.dot(PTDF2, self.dcSbus * self.gridmodel.get_sn_mva())
+ np.where(np.abs(res_ptdf2 - self.res_powerflow) >= 1e3)
+ assert np.abs(res_ptdf2 - self.res_powerflow).max() <= self.tol, f"max error for powerflow: {np.abs(res_ptdf2 - self.res_powerflow).max():.2e}MW"
+
+
+class TestCase30SLU(TestCase14SLU):
+ def make_grid(self):
+ res = pn.case30()
+ return res
+
+
+class TestCase118SLU(TestCase14SLU):
+ def make_grid(self):
+ res = pn.case118()
+ return res
+
+
+class TestCase14KLU(TestCase14SLU):
+ def get_solver_type(self):
+ return SolverType.KLUDC
+
+
+class TestCase30KLU(TestCase30SLU):
+ def get_solver_type(self):
+ return SolverType.KLUDC
+
+
+class TestCase118KLU(TestCase118SLU):
+ def get_solver_type(self):
+ return SolverType.KLUDC
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/lightsim2grid/tests/test_solver_control.py b/lightsim2grid/tests/test_solver_control.py
new file mode 100644
index 00000000..ae2a8f12
--- /dev/null
+++ b/lightsim2grid/tests/test_solver_control.py
@@ -0,0 +1,791 @@
+# TODO: test that "if I do something, then with or without the speed optim, I have the same thing"
+
+# use backend._grid.tell_solver_need_reset() to force the reset of the solver
+# and by "something" do :
+# - disconnect line X
+# - disconnect trafo X
+# - reconnect line X
+# - reconnect trafo X
+# - change load X
+# - change gen X
+# - change shunt X
+# - change storage X
+# - change slack X
+# - change slack weight X
+# - turnoff_gen_pv X
+# - when it diverges (and then I can make it un converge normally) X
+# - test change bus to -1 and deactivate element has the same impact (irrelevant !)
+# - test when set_bus to 2
+
+# TODO and do that for all solver type: NR X, NRSingleSlack, GaussSeidel, GaussSeidelSynch, FDPF_XB and FDPFBX
+# and for all solver check everything that can be check: final tolerance, number of iteration, etc. etc.
+
+import unittest
+import warnings
+import numpy as np
+import grid2op
+from grid2op.Action import CompleteAction
+
+from lightsim2grid import LightSimBackend
+from lightsim2grid.solver import SolverType
+
+# TODO when line connected alone at one end, starts a Security Analysis
+# to see if it works
+
+
+class TestSolverControl(unittest.TestCase):
+ def _aux_setup_grid(self):
+ self.need_dc = True # is it worth it to run DC powerflow ?
+ self.can_dist_slack = True
+ self.gridmodel.change_solver(SolverType.SparseLU)
+ self.gridmodel.change_solver(SolverType.DC)
+
+ def setUp(self) -> None:
+ with warnings.catch_warnings():
+ warnings.filterwarnings("ignore")
+ self.env = grid2op.make("educ_case14_storage",
+ test=True,
+ action_class=CompleteAction,
+ backend=LightSimBackend())
+ self.gridmodel = self.env.backend._grid
+ self.iter = 10
+ self._aux_setup_grid()
+ self.v_init = 0.0 * self.env.backend.V + 1.04 # just to have a vector with the right dimension
+ self.tol_solver = 1e-8 # solver
+ self.tol_equal = 1e-10 # for comparing with and without the "smarter solver" things, and make sure everything is really equal!
+
+ def test_update_topo_ac(self, runpf_fun="_run_ac_pf"):
+ """test when I disconnect a line alone at one end: it changes the size of the ybus / sbus vector AC"""
+ LINE_ID = 2
+ dim_topo = type(self.env).dim_topo
+ mask_changed = np.zeros(dim_topo, dtype=bool)
+ mask_val = np.zeros(dim_topo, dtype=np.int32)
+ mask_changed[type(self.env).line_ex_pos_topo_vect[LINE_ID]] = True
+ mask_val[type(self.env).line_ex_pos_topo_vect[LINE_ID]] = 2
+ self.gridmodel.update_topo(mask_changed, mask_val)
+ V = getattr(self, runpf_fun)(gridmodel=self.gridmodel)
+ assert len(V), "it should not have diverged here"
+ self.gridmodel.unset_changes()
+
+ mask_changed = np.zeros(dim_topo, dtype=bool)
+ mask_val = np.zeros(dim_topo, dtype=np.int32)
+ mask_changed[type(self.env).line_or_pos_topo_vect[LINE_ID]] = True
+ mask_val[type(self.env).line_or_pos_topo_vect[LINE_ID]] = -1
+ # mask_changed[type(self.env).line_ex_pos_topo_vect[LINE_ID]] = True
+ # mask_val[type(self.env).line_ex_pos_topo_vect[LINE_ID]] = -1
+ self.gridmodel.update_topo(mask_changed, mask_val)
+ solver = self.gridmodel.get_dc_solver() if runpf_fun == "_run_dc_pf" else self.gridmodel.get_solver()
+ V1 = getattr(self, runpf_fun)(gridmodel=self.gridmodel)
+ assert len(V1), f"it should not have diverged here. Error : {solver.get_error()}"
+
+ self.gridmodel.tell_solver_need_reset()
+ V2 = getattr(self, runpf_fun)(gridmodel=self.gridmodel)
+ assert len(V2), f"it should not have diverged here. Error : {solver.get_error()}"
+ assert np.allclose(V1, V2, rtol=self.tol_equal, atol=self.tol_equal)
+
+ def test_update_topo_dc(self):
+ """test when I disconnect a line alone at one end: it changes the size of the ybus / sbus vector AC"""
+ if not self.need_dc:
+ self.skipTest("Useless to run DC")
+ self.test_update_topo_ac("_run_dc_pf")
+
+ def test_pf_run_dc(self):
+ """test I have the same results if nothing is done with and without restarting from scratch when running dc powerflow"""
+ if not self.need_dc:
+ self.skipTest("Useless to run DC")
+ Vdc_init = self.gridmodel.dc_pf(self.v_init, self.iter, self.tol_solver)
+ assert len(Vdc_init), f"error: gridmodel should converge in DC"
+ self.gridmodel.unset_changes()
+ Vdc_init2 = self.gridmodel.dc_pf(self.v_init, self.iter, self.tol_solver)
+ assert len(Vdc_init2), f"error: gridmodel should converge in DC"
+ self.gridmodel.tell_solver_need_reset()
+ Vdc_init3 = self.gridmodel.dc_pf(self.v_init, self.iter, self.tol_solver)
+ assert len(Vdc_init3), f"error: gridmodel should converge in DC"
+ assert np.allclose(Vdc_init, Vdc_init2, rtol=self.tol_equal, atol=self.tol_equal)
+ assert np.allclose(Vdc_init2, Vdc_init3, rtol=self.tol_equal, atol=self.tol_equal)
+
+ def test_pf_run_ac(self):
+ """test I have the same results if nothing is done with and without restarting from scratch when running ac powerflow"""
+ Vac_init = self.gridmodel.ac_pf(self.v_init, self.iter, self.tol_solver)
+ assert len(Vac_init), f"error: gridmodel should converge in AC"
+ self.gridmodel.unset_changes()
+ Vac_init2 = self.gridmodel.ac_pf(self.v_init, self.iter, self.tol_solver)
+ assert len(Vac_init2), f"error: gridmodel should converge in AC"
+ self.gridmodel.tell_solver_need_reset()
+ Vac_init3 = self.gridmodel.ac_pf(self.v_init, self.iter, self.tol_solver)
+ assert len(Vac_init3), f"error: gridmodel should converge in AC"
+ assert np.allclose(Vac_init, Vac_init2, rtol=self.tol_equal, atol=self.tol_equal)
+ assert np.allclose(Vac_init2, Vac_init3, rtol=self.tol_equal, atol=self.tol_equal)
+
+ def _disco_line_action(self, gridmodel, el_id=0, el_val=0.):
+ gridmodel.deactivate_powerline(el_id)
+
+ def _reco_line_action(self, gridmodel, el_id=0, el_val=0.):
+ gridmodel.reactivate_powerline(el_id)
+
+ def _disco_trafo_action(self, gridmodel, el_id=0, el_val=0.):
+ gridmodel.deactivate_trafo(el_id)
+
+ def _reco_trafo_action(self, gridmodel, el_id=0, el_val=0.):
+ gridmodel.reactivate_trafo(el_id)
+
+ def _run_ac_pf(self, gridmodel):
+ return gridmodel.ac_pf(self.v_init, self.iter, self.tol_solver)
+
+ def _run_dc_pf(self, gridmodel):
+ return gridmodel.dc_pf(self.v_init, self.iter, self.tol_solver)
+
+ def aux_do_undo_ac(self,
+ runpf_fun="_run_ac_pf",
+ funname_do="_disco_line_action",
+ funname_undo="_reco_line_action",
+ el_id=0,
+ el_val=0.,
+ to_add_remove=0.,
+ expected_diff=0.1
+ ):
+ pf_mode = "AC" if runpf_fun=="_run_ac_pf" else "DC"
+
+ # test "do the action"
+ V_init = getattr(self, runpf_fun)(gridmodel=self.gridmodel)
+ assert len(V_init), f"error for el_id={el_id}: gridmodel should converge in {pf_mode}"
+ getattr(self, funname_do)(gridmodel=self.gridmodel, el_id=el_id, el_val=el_val + to_add_remove)
+ V_disc = getattr(self, runpf_fun)(gridmodel=self.gridmodel)
+ if len(V_disc) > 0:
+ # powerflow converges, all should converge
+ assert (np.abs(V_init - V_disc) >= expected_diff).any(), f"error for el_id={el_id}: at least one bus should have changed its result voltage in {pf_mode}: max {np.abs(V_init - V_disc).max():.2e}"
+ self.gridmodel.unset_changes()
+ V_disc1 = getattr(self, runpf_fun)(gridmodel=self.gridmodel)
+ assert len(V_disc1), f"error for el_id={el_id}: gridmodel should converge in {pf_mode}"
+ assert np.allclose(V_disc, V_disc1, rtol=self.tol_equal, atol=self.tol_equal)
+ self.gridmodel.tell_solver_need_reset()
+ V_disc2 = getattr(self, runpf_fun)(gridmodel=self.gridmodel)
+ assert len(V_disc2), f"error for el_id={el_id}: gridmodel should converge in {pf_mode}"
+ assert np.allclose(V_disc2, V_disc1, rtol=self.tol_equal, atol=self.tol_equal)
+ assert np.allclose(V_disc2, V_disc, rtol=self.tol_equal, atol=self.tol_equal)
+ else:
+ #powerflow diverges
+ self.gridmodel.unset_changes()
+ V_disc1 = getattr(self, runpf_fun)(gridmodel=self.gridmodel)
+ assert len(V_disc1) == 0, f"error for el_id={el_id}: powerflow should diverge as it did initially in {pf_mode}"
+ self.gridmodel.tell_solver_need_reset()
+ V_disc2 = getattr(self, runpf_fun)(gridmodel=self.gridmodel)
+ assert len(V_disc1) == 0, f"error for el_id={el_id}: powerflow should diverge as it did initially in {pf_mode}"
+
+ # test "undo the action"
+ self.gridmodel.unset_changes()
+ getattr(self, funname_undo)(gridmodel=self.gridmodel, el_id=el_id, el_val=el_val)
+ V_reco = getattr(self, runpf_fun)(gridmodel=self.gridmodel)
+ assert len(V_reco), f"error for el_id={el_id}: gridmodel should converge in {pf_mode}"
+ assert np.allclose(V_reco, V_init, rtol=self.tol_equal, atol=self.tol_equal), f"error for el_id={el_id}: do an action and then undo it should not have any impact in {pf_mode}: max {np.abs(V_init - V_reco).max():.2e}"
+ self.gridmodel.unset_changes()
+ V_reco1 = getattr(self, runpf_fun)(gridmodel=self.gridmodel)
+ assert len(V_reco1), f"error for el_id={el_id}: gridmodel should converge in {pf_mode}"
+ assert np.allclose(V_reco1, V_reco, rtol=self.tol_equal, atol=self.tol_equal)
+ self.gridmodel.tell_solver_need_reset()
+ V_reco2 = getattr(self, runpf_fun)(gridmodel=self.gridmodel)
+ assert len(V_reco2), f"error for el_id={el_id}: gridmodel should converge in {pf_mode}"
+ assert np.allclose(V_reco2, V_reco1, rtol=self.tol_equal, atol=self.tol_equal)
+ assert np.allclose(V_reco2, V_reco, rtol=self.tol_equal, atol=self.tol_equal)
+
+ def test_disco_reco_line_ac(self, runpf_fun="_run_ac_pf"):
+ """test I have the same results if I disconnect a line with and
+ without restarting from scratch when running ac powerflow"""
+ for el_id in range(len(self.gridmodel.get_lines())):
+ self.gridmodel.tell_solver_need_reset()
+ expected_diff = 3e-2
+ if runpf_fun=="_run_ac_pf":
+ if el_id == 4:
+ expected_diff = 1e-2
+ elif el_id == 10:
+ expected_diff = 1e-3
+ elif el_id == 12:
+ expected_diff = 1e-2
+ elif el_id == 13:
+ expected_diff = 3e-3
+ elif runpf_fun=="_run_dc_pf":
+ if el_id == 4:
+ expected_diff = 1e-2
+ elif el_id == 8:
+ expected_diff = 1e-2
+ elif el_id == 10:
+ expected_diff = 1e-2
+ elif el_id == 12:
+ expected_diff = 1e-2
+ elif el_id == 13:
+ expected_diff = 3e-3
+ elif el_id == 14:
+ expected_diff = 1e-2
+ self.aux_do_undo_ac(funname_do="_disco_line_action",
+ funname_undo="_reco_line_action",
+ runpf_fun=runpf_fun,
+ el_id=el_id,
+ expected_diff=expected_diff
+ )
+
+ def test_disco_reco_line_dc(self):
+ """test I have the same results if I disconnect a line with and
+ without restarting from scratch when running DC powerflow"""
+ if not self.need_dc:
+ self.skipTest("Useless to run DC")
+ self.test_disco_reco_line_ac(runpf_fun="_run_dc_pf")
+
+ def test_disco_reco_trafo_ac(self, runpf_fun="_run_ac_pf"):
+ """test I have the same results if I disconnect a trafo with and
+ without restarting from scratch when running ac powerflow"""
+ for el_id in range(len(self.gridmodel.get_trafos())):
+ self.gridmodel.tell_solver_need_reset()
+ expected_diff = 3e-2
+ if runpf_fun=="_run_ac_pf":
+ if el_id == 1:
+ expected_diff = 1e-2
+ self.aux_do_undo_ac(funname_do="_disco_trafo_action",
+ funname_undo="_reco_trafo_action",
+ runpf_fun=runpf_fun,
+ el_id=el_id,
+ expected_diff=expected_diff
+ )
+
+ def test_disco_reco_trafo_dc(self):
+ """test I have the same results if I disconnect a trafo with and
+ without restarting from scratch when running DC powerflow"""
+ if not self.need_dc:
+ self.skipTest("Useless to run DC")
+ self.test_disco_reco_trafo_ac(runpf_fun="_run_dc_pf")
+
+ def _change_load_p_action(self, gridmodel, el_id, el_val):
+ gridmodel.change_p_load(el_id, el_val)
+
+ def test_change_load_p_ac(self, runpf_fun="_run_ac_pf"):
+ """test I have the same results if I change the load p with and
+ without restarting from scratch when running ac powerflow"""
+ for load in self.gridmodel.get_loads():
+ self.gridmodel.tell_solver_need_reset()
+ expected_diff = 1e-3
+ to_add_remove = 5.
+ self.aux_do_undo_ac(funname_do="_change_load_p_action",
+ funname_undo="_change_load_p_action",
+ runpf_fun=runpf_fun,
+ el_id=load.id,
+ expected_diff=expected_diff,
+ el_val=load.target_p_mw,
+ to_add_remove=to_add_remove,
+ )
+
+ def test_change_load_p_dc(self):
+ """test I have the same results if I change the load p with and
+ without restarting from scratch when running dc powerflow"""
+ if not self.need_dc:
+ self.skipTest("Useless to run DC")
+ self.test_change_load_p_ac(runpf_fun="_run_dc_pf")
+
+ def _change_load_q_action(self, gridmodel, el_id, el_val):
+ gridmodel.change_q_load(el_id, el_val)
+
+ def _change_gen_p_action(self, gridmodel, el_id, el_val):
+ gridmodel.change_p_gen(el_id, el_val)
+
+ def test_change_load_q_ac(self, runpf_fun="_run_ac_pf"):
+ """test I have the same results if I change the load q with and
+ without restarting from scratch when running ac powerflow
+
+ NB: this test has no sense in DC
+ """
+ gen_bus = [el.bus_id for el in self.gridmodel.get_generators()]
+ for load in self.gridmodel.get_loads():
+ if load.bus_id in gen_bus:
+ # nothing will change if there is a gen connected to the
+ # same bus as the load
+ continue
+ self.gridmodel.tell_solver_need_reset()
+ expected_diff = 1e-3
+ to_add_remove = 5.
+ self.aux_do_undo_ac(funname_do="_change_load_q_action",
+ funname_undo="_change_load_q_action",
+ runpf_fun=runpf_fun,
+ el_id=load.id,
+ expected_diff=expected_diff,
+ el_val=load.target_q_mvar,
+ to_add_remove=to_add_remove,
+ )
+
+ def test_change_gen_p_ac(self, runpf_fun="_run_ac_pf"):
+ """test I have the same results if I change the gen p with and
+ without restarting from scratch when running ac powerflow"""
+ for gen in self.gridmodel.get_generators():
+ if gen.is_slack:
+ # nothing to do for the slack bus...
+ continue
+ self.gridmodel.tell_solver_need_reset()
+ expected_diff = 1e-3
+ to_add_remove = 5.
+ self.aux_do_undo_ac(funname_do="_change_gen_p_action",
+ funname_undo="_change_gen_p_action",
+ runpf_fun=runpf_fun,
+ el_id=gen.id,
+ expected_diff=expected_diff,
+ el_val=gen.target_p_mw,
+ to_add_remove=to_add_remove,
+ )
+
+ def test_change_gen_p_dc(self):
+ """test I have the same results if I change the gen p with and
+ without restarting from scratch when running dc powerflow"""
+ if not self.need_dc:
+ self.skipTest("Useless to run DC")
+ self.test_change_gen_p_ac(runpf_fun="_run_dc_pf")
+
+ def _change_gen_v_action(self, gridmodel, el_id, el_val):
+ gridmodel.change_v_gen(el_id, el_val)
+
+ def test_change_gen_v_ac(self, runpf_fun="_run_ac_pf"):
+ """test I have the same results if I change the gen v with and
+ without restarting from scratch when running ac powerflow
+
+ NB: this test has no sense in DC
+ """
+ gen_bus = [el.bus_id for el in self.gridmodel.get_generators()]
+ vn_kv = self.gridmodel.get_bus_vn_kv()
+ for gen in self.gridmodel.get_generators():
+ if gen.bus_id in gen_bus:
+ # nothing will change if there is another gen connected to the
+ # same bus as the gen (error if everything is coded normally
+ # which it might not)
+ continue
+
+ self.gridmodel.tell_solver_need_reset()
+ expected_diff = 1e-3
+ to_add_remove = 0.1 * gen.target_vm_pu * vn_kv[gen.bus_id]
+ self.aux_do_undo_ac(funname_do="_change_gen_v_action",
+ funname_undo="_change_gen_v_action",
+ runpf_fun=runpf_fun,
+ el_id=gen.id,
+ expected_diff=expected_diff,
+ el_val=gen.target_vm_pu * vn_kv[gen.bus_id],
+ to_add_remove=to_add_remove,
+ )
+
+ def _change_shunt_p_action(self, gridmodel, el_id, el_val):
+ gridmodel.change_p_shunt(el_id, el_val)
+
+ def test_change_shunt_p_ac(self, runpf_fun="_run_ac_pf"):
+ """test I have the same results if I change the shunt p with and
+ without restarting from scratch when running ac powerflow"""
+ for shunt in self.gridmodel.get_shunts():
+ self.gridmodel.tell_solver_need_reset()
+ expected_diff = 1e-3
+ to_add_remove = 5.
+ self.aux_do_undo_ac(funname_do="_change_shunt_p_action",
+ funname_undo="_change_shunt_p_action",
+ runpf_fun=runpf_fun,
+ el_id=shunt.id,
+ expected_diff=expected_diff,
+ el_val=shunt.target_p_mw,
+ to_add_remove=to_add_remove,
+ )
+
+ def test_change_shunt_p_dc(self):
+ """test I have the same results if I change the shunt p with and
+ without restarting from scratch when running dc powerflow"""
+ if not self.need_dc:
+ self.skipTest("Useless to run DC")
+ self.test_change_shunt_p_ac(runpf_fun="_run_dc_pf")
+
+ def _change_shunt_q_action(self, gridmodel, el_id, el_val):
+ gridmodel.change_q_shunt(el_id, el_val)
+
+ def test_change_shunt_q_ac(self, runpf_fun="_run_ac_pf"):
+ """test I have the same results if I change the shunt q with and
+ without restarting from scratch when running ac powerflow
+
+ NB dc is not needed here (and does not make sense)"""
+ for shunt in self.gridmodel.get_shunts():
+ self.gridmodel.tell_solver_need_reset()
+ expected_diff = 1e-3
+ to_add_remove = 5.
+ self.aux_do_undo_ac(funname_do="_change_shunt_q_action",
+ funname_undo="_change_shunt_q_action",
+ runpf_fun=runpf_fun,
+ el_id=shunt.id,
+ expected_diff=expected_diff,
+ el_val=shunt.target_q_mvar,
+ to_add_remove=to_add_remove,
+ )
+
+ def _change_storage_p_action(self, gridmodel, el_id, el_val):
+ gridmodel.change_p_storage(el_id, el_val)
+
+ def test_change_storage_p_ac(self, runpf_fun="_run_ac_pf"):
+ """test I have the same results if I change the storage p with and
+ without restarting from scratch when running ac powerflow"""
+ for storage in self.gridmodel.get_storages():
+ self.gridmodel.tell_solver_need_reset()
+ expected_diff = 1e-3
+ to_add_remove = 5.
+ self.aux_do_undo_ac(funname_do="_change_storage_p_action",
+ funname_undo="_change_storage_p_action",
+ runpf_fun=runpf_fun,
+ el_id=storage.id,
+ expected_diff=expected_diff,
+ el_val=storage.target_p_mw,
+ to_add_remove=to_add_remove,
+ )
+
+ def test_change_storage_p_dc(self):
+ """test I have the same results if I change the storage p with and
+ without restarting from scratch when running dc powerflow"""
+ if not self.need_dc:
+ self.skipTest("Useless to run DC")
+ self.test_change_storage_p_ac(runpf_fun="_run_dc_pf")
+
+ def _change_storage_q_action(self, gridmodel, el_id, el_val):
+ gridmodel.change_q_storage(el_id, el_val)
+
+ def test_change_storage_q_ac(self, runpf_fun="_run_ac_pf"):
+ """test I have the same results if I change the storage q with and
+ without restarting from scratch when running ac powerflow"""
+ gen_bus = [el.bus_id for el in self.gridmodel.get_generators()]
+ for storage in self.gridmodel.get_storages():
+ if storage.bus_id in gen_bus:
+ # nothing will change if there is a gen connected to the
+ # same bus as the load
+ continue
+ self.gridmodel.tell_solver_need_reset()
+ expected_diff = 1e-3
+ to_add_remove = 5.
+ self.aux_do_undo_ac(funname_do="_change_storage_q_action",
+ funname_undo="_change_storage_q_action",
+ runpf_fun=runpf_fun,
+ el_id=storage.id,
+ expected_diff=expected_diff,
+ el_val=storage.target_q_mvar,
+ to_add_remove=to_add_remove,
+ )
+
+ def _change_unique_slack_id(self, gridmodel, el_id, el_val):
+ # el_id : new slack
+ # el_val : old slack
+ gridmodel.remove_gen_slackbus(el_val) # remove old slack
+ gridmodel.add_gen_slackbus(el_id, 1.0) # add new slack
+
+ def _unchange_unique_slack_id(self, gridmodel, el_id, el_val):
+ # el_id : new slack
+ # el_val : old slack
+ gridmodel.remove_gen_slackbus(el_id) # remove new slack
+ gridmodel.add_gen_slackbus(el_val, 1.0) # add back old slack
+
+ def test_change_gen_slack_unique_ac(self, runpf_fun="_run_ac_pf"):
+ """test I have the same results if I change the (for this test) unique slack bus
+ This is done in AC"""
+ gen_is_slack = [el.is_slack for el in self.gridmodel.get_generators()]
+ gen_id_slack_init = np.where(gen_is_slack)[0][0]
+
+ for gen in self.gridmodel.get_generators():
+ if gen_is_slack[gen.id]:
+ # generator was already slack, don't expect any change
+ continue
+ self.gridmodel.tell_solver_need_reset()
+ expected_diff = 1e-5 # change slack impact is low
+ self.aux_do_undo_ac(funname_do="_change_unique_slack_id",
+ funname_undo="_unchange_unique_slack_id",
+ runpf_fun=runpf_fun,
+ el_id=gen.id, # new slack
+ expected_diff=expected_diff,
+ el_val=gen_id_slack_init, # old slack
+ to_add_remove=0,
+ )
+
+ def test_change_gen_slack_unique_dc(self):
+ """test I have the same results if I change the (for this test) unique slack bus
+ This is done in DC"""
+ if not self.need_dc:
+ self.skipTest("Useless to run DC")
+ self.test_change_gen_slack_unique_ac(runpf_fun="_run_dc_pf")
+
+ def _change_dist_slack_id(self, gridmodel, el_id, el_val):
+ # el_id : new slack
+ # el_val : old slack
+ gridmodel.add_gen_slackbus(el_val, 0.5) # set all slack a weight of 0.5
+ gridmodel.add_gen_slackbus(el_id, 0.5) # add new slack with a weight of 0.5
+
+ def _unchange_dist_slack_id(self, gridmodel, el_id, el_val):
+ # el_id : new slack
+ # el_val : old slack
+ gridmodel.remove_gen_slackbus(el_id) # remove new slack
+ gridmodel.add_gen_slackbus(el_val, 1.0) # add back old slack with a weight of 1. (as originally)
+
+ def test_distslack_weight_ac(self):
+ """test I have the same results if the slack weights (dist mode) are changed
+
+ NB: not done in DC because as of now DC does not support dist slack
+ """
+ if not self.can_dist_slack :
+ self.skipTest("This solver does not support distributed slack")
+ gen_is_slack = [el.is_slack for el in self.gridmodel.get_generators()]
+ gen_id_slack_init = np.where(gen_is_slack)[0][0]
+ runpf_fun = "_run_ac_pf"
+ for gen in self.gridmodel.get_generators():
+ if gen_is_slack[gen.id]:
+ # generator was already slack, don't expect any change
+ continue
+ self.gridmodel.tell_solver_need_reset()
+ expected_diff = 1e-5 # change slack impact is low
+ self.aux_do_undo_ac(funname_do="_change_dist_slack_id",
+ funname_undo="_unchange_dist_slack_id",
+ runpf_fun=runpf_fun,
+ el_id=gen.id, # new added slack
+ expected_diff=expected_diff,
+ el_val=gen_id_slack_init, # old slack
+ to_add_remove=0,
+ )
+
+ def _change_turnedoff_pv(self, gridmodel, el_id, el_val):
+ gridmodel.turnedoff_no_pv()
+
+ def _unchange_turnedoff_pv(self, gridmodel, el_id, el_val):
+ gridmodel.turnedoff_pv()
+
+ def test_turnedoff_pv_ac(self):
+ """test the `turnedoff_pv` functionality
+ """
+ runpf_fun = "_run_ac_pf"
+ self.gridmodel.tell_solver_need_reset()
+ expected_diff = 1e-2 # change slack impact is low
+ self.aux_do_undo_ac(funname_do="_change_turnedoff_pv",
+ funname_undo="_unchange_turnedoff_pv",
+ runpf_fun=runpf_fun,
+ el_id=0,
+ expected_diff=expected_diff,
+ el_val=0,
+ to_add_remove=0,
+ )
+
+ def _change_for_divergence_sbus(self, gridmodel, el_id, el_val):
+ # el_id=load_p_init, # initial loads
+ # el_val=load_p_div, # final loads
+ [gridmodel.change_p_load(l_id, val) for l_id, val in enumerate(el_val)]
+
+ def _unchange_for_divergence_sbus(self, gridmodel, el_id, el_val):
+ # el_id=load_p_init, # initial loads
+ # el_val=load_p_div, # final loads
+ [gridmodel.change_p_load(l_id, val) for l_id, val in enumerate(el_id)]
+
+ def test_divergence_sbus_ac(self):
+ """test I can make the grid diverge and converge again using sbus (ac mode only)
+
+ It never diverge in DC because of Sbus"""
+ runpf_fun = "_run_ac_pf"
+ self.gridmodel.tell_solver_need_reset()
+ expected_diff = 1e-2 # change slack impact is low
+ load_p_init = 1.0 * np.array([el.target_p_mw for el in self.gridmodel.get_loads()])
+ load_p_div = 5. * load_p_init
+
+ # check that this load makes it diverge too
+ tmp_grid = self.gridmodel.copy()
+ [tmp_grid.change_p_load(l_id, val) for l_id, val in enumerate(load_p_div)]
+ V_tmp = tmp_grid.ac_pf(self.v_init, self.iter, self.tol_solver)
+ assert len(V_tmp) == 0, "should have diverged !"
+
+ self.aux_do_undo_ac(funname_do="_change_for_divergence_sbus",
+ funname_undo="_unchange_for_divergence_sbus",
+ runpf_fun=runpf_fun,
+ el_id=load_p_init, # initial loads
+ expected_diff=expected_diff,
+ el_val=load_p_div, # final loads
+ to_add_remove=0.,
+ )
+
+ def _change_for_divergence_ybus(self, gridmodel, el_id, el_val):
+ [gridmodel.deactivate_trafo(tr_id) for tr_id in el_id]
+
+ def _unchange_for_divergence_ybus(self, gridmodel, el_id, el_val):
+ [gridmodel.reactivate_trafo(tr_id) for tr_id in el_id]
+
+ def test_divergence_ybus_ac(self, runpf_fun="_run_ac_pf"):
+ """test I can make the grid diverge and converge again using ybus (islanding) in AC"""
+ self.gridmodel.tell_solver_need_reset()
+ expected_diff = 1e-2 # change slack impact is low
+ trafo_to_disc = [0, 1, 2]
+
+ # check that this load makes it diverge too
+ tmp_grid = self.gridmodel.copy()
+ [tmp_grid.deactivate_trafo(tr_id) for tr_id in trafo_to_disc]
+ V_tmp = tmp_grid.ac_pf(self.v_init, self.iter, self.tol_solver)
+ assert len(V_tmp) == 0, "should have diverged !"
+
+ self.aux_do_undo_ac(funname_do="_change_for_divergence_ybus",
+ funname_undo="_unchange_for_divergence_ybus",
+ runpf_fun=runpf_fun,
+ el_id=trafo_to_disc, # trafo to disco
+ expected_diff=expected_diff,
+ el_val=0.,
+ to_add_remove=0.,
+ )
+
+ def test_divergence_ybus_dc(self):
+ """test I can make the grid diverge and converge again using ybus (islanding) in DC"""
+ self.test_divergence_ybus_ac("_run_dc_pf")
+
+ def _aux_disco_load(self, gridmodel, el_id, el_val):
+ gridmodel.deactivate_load(el_id)
+
+ def _aux_reco_load(self, gridmodel, el_id, el_val):
+ gridmodel.reactivate_load(el_id)
+
+ def test_disco_reco_load_ac(self, runpf_fun="_run_ac_pf"):
+ """test I can disconnect a load (AC)"""
+ for load in self.gridmodel.get_loads():
+ self.gridmodel.tell_solver_need_reset()
+ expected_diff = 1e-2
+ if load.id == 3:
+ expected_diff = 3e-3
+ self.aux_do_undo_ac(funname_do="_aux_disco_load",
+ funname_undo="_aux_reco_load",
+ runpf_fun=runpf_fun,
+ el_id=load.id,
+ expected_diff=expected_diff,
+ el_val=0,
+ to_add_remove=0,
+ )
+
+ def test_disco_reco_load_dc(self):
+ """test I can disconnect a load (DC)"""
+ if not self.need_dc:
+ self.skipTest("Useless to run DC")
+ self.test_disco_reco_load_ac(runpf_fun="_run_dc_pf")
+
+ def _aux_disco_gen(self, gridmodel, el_id, el_val):
+ gridmodel.deactivate_gen(el_id)
+
+ def _aux_reco_gen(self, gridmodel, el_id, el_val):
+ gridmodel.reactivate_gen(el_id)
+
+ def test_disco_reco_gen_ac(self, runpf_fun="_run_ac_pf"):
+ """test I can disconnect a gen (AC)"""
+ for gen in self.gridmodel.get_generators():
+ if gen.is_slack:
+ # by default single slack, so I don't disconnect it
+ continue
+ if gen.target_p_mw == 0.:
+ # will have not impact as by default gen with p==0. are still pv
+ continue
+ self.gridmodel.tell_solver_need_reset()
+ expected_diff = 1e-2
+ if gen.id == 3:
+ expected_diff = 3e-3
+ self.aux_do_undo_ac(funname_do="_aux_disco_gen",
+ funname_undo="_aux_reco_gen",
+ runpf_fun=runpf_fun,
+ el_id=gen.id,
+ expected_diff=expected_diff,
+ el_val=0,
+ to_add_remove=0,
+ )
+
+ def test_disco_reco_gen_dc(self):
+ """test I can disconnect a shunt (DC)"""
+ if not self.need_dc:
+ self.skipTest("Useless to run DC")
+ self.test_disco_reco_gen_ac(runpf_fun="_run_dc_pf")
+
+ def _aux_disco_shunt(self, gridmodel, el_id, el_val):
+ gridmodel.deactivate_shunt(el_id)
+
+ def _aux_reco_shunt(self, gridmodel, el_id, el_val):
+ gridmodel.reactivate_shunt(el_id)
+
+ def test_disco_reco_shunt_ac(self, runpf_fun="_run_ac_pf"):
+ """test I can disconnect a shunt (AC)"""
+ for shunt in self.gridmodel.get_shunts():
+ self.gridmodel.tell_solver_need_reset()
+ expected_diff = 1e-2
+ if runpf_fun == "_run_dc_pf":
+ # in dc q is not used, so i skipped if no target_p_mw
+ if shunt.target_p_mw == 0:
+ continue
+ self.aux_do_undo_ac(funname_do="_aux_disco_shunt",
+ funname_undo="_aux_reco_shunt",
+ runpf_fun=runpf_fun,
+ el_id=shunt.id,
+ expected_diff=expected_diff,
+ el_val=0,
+ to_add_remove=0,
+ )
+
+ def test_disco_reco_shunt_dc(self):
+ """test I can disconnect a shunt (DC)"""
+ if not self.need_dc:
+ self.skipTest("Useless to run DC")
+ self.test_disco_reco_shunt_ac(runpf_fun="_run_dc_pf")
+
+ def _aux_disco_shunt(self, gridmodel, el_id, el_val):
+ gridmodel.deactivate_bus(0)
+ gridmodel.reactivate_bus(15)
+ gridmodel.change_bus_powerline_or(0, 15)
+ gridmodel.change_bus_powerline_or(1, 15)
+ gridmodel.change_bus_gen(5, 15)
+
+ def _aux_reco_shunt(self, gridmodel, el_id, el_val):
+ gridmodel.reactivate_bus(0)
+ gridmodel.deactivate_bus(15)
+ gridmodel.change_bus_powerline_or(0, 0)
+ gridmodel.change_bus_powerline_or(1, 0)
+ gridmodel.change_bus_gen(5, 0)
+
+ def test_change_bus2_ac(self, runpf_fun="_run_ac_pf"):
+ """test for bus 2, basic test I don't do it for all kind of objects (AC pf)"""
+ expected_diff = 1e-2
+ self.aux_do_undo_ac(funname_do="_aux_disco_shunt",
+ funname_undo="_aux_reco_shunt",
+ runpf_fun=runpf_fun,
+ el_id=0,
+ expected_diff=expected_diff,
+ el_val=0,
+ to_add_remove=0,
+ )
+
+
+class TestSolverControlNRSing(TestSolverControl):
+ def _aux_setup_grid(self):
+ self.need_dc = False # is it worth it to run DC powerflow ?
+ self.can_dist_slack = False
+ self.gridmodel.change_solver(SolverType.SparseLUSingleSlack)
+ self.gridmodel.change_solver(SolverType.DC)
+
+
+class TestSolverControlFDPF_XB(TestSolverControl):
+ def _aux_setup_grid(self):
+ self.need_dc = False # is it worth it to run DC powerflow ?
+ self.can_dist_slack = False
+ self.gridmodel.change_solver(SolverType.FDPF_XB_SparseLU)
+ self.gridmodel.change_solver(SolverType.DC)
+ self.iter = 30
+
+
+class TestSolverControlFDPF_BX(TestSolverControl):
+ def _aux_setup_grid(self):
+ self.need_dc = False # is it worth it to run DC powerflow ?
+ self.can_dist_slack = False
+ self.gridmodel.change_solver(SolverType.FDPF_BX_SparseLU)
+ self.gridmodel.change_solver(SolverType.DC)
+ self.iter = 30
+
+
+class TestSolverControlGaussSeidel(TestSolverControl):
+ def _aux_setup_grid(self):
+ self.need_dc = False # is it worth it to run DC powerflow ?
+ self.can_dist_slack = False
+ self.gridmodel.change_solver(SolverType.GaussSeidel)
+ self.iter = 350
+
+
+class TestSolverControlGaussSeidelSynch(TestSolverControl):
+ def _aux_setup_grid(self):
+ self.need_dc = False # is it worth it to run DC powerflow ?
+ self.can_dist_slack = False
+ self.gridmodel.change_solver(SolverType.GaussSeidelSynch)
+ self.iter = 1000
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/lightsim2grid/tests/test_turnedoff_nopv.py b/lightsim2grid/tests/test_turnedoff_nopv.py
index 49fb8d33..0059d93a 100644
--- a/lightsim2grid/tests/test_turnedoff_nopv.py
+++ b/lightsim2grid/tests/test_turnedoff_nopv.py
@@ -100,8 +100,8 @@ def test_after_runner(self):
"""test I can use the runner"""
runner_pv = Runner(**self.env_pv.get_params_for_runner())
runner_npv = Runner(**self.env_npv.get_params_for_runner())
- res_pv = runner_pv.run(nb_episode=1, max_iter=self.max_iter_real, add_detailed_output=True)
- res_npv = runner_npv.run(nb_episode=1, max_iter=self.max_iter_real, add_detailed_output=True)
+ res_pv = runner_pv.run(nb_episode=1, max_iter=self.max_iter_real, add_detailed_output=True, env_seeds=[0])
+ res_npv = runner_npv.run(nb_episode=1, max_iter=self.max_iter_real, add_detailed_output=True, env_seeds=[0])
assert res_pv[0][3] == res_npv[0][3] # same number of steps survived
assert res_pv[0][2] != res_npv[0][2] # not the same reward
ep_pv = res_pv[0][-1]
diff --git a/lightsim2grid/timeSerie.py b/lightsim2grid/timeSerie.py
index 75bbaf18..2942441b 100644
--- a/lightsim2grid/timeSerie.py
+++ b/lightsim2grid/timeSerie.py
@@ -6,7 +6,9 @@
# SPDX-License-Identifier: MPL-2.0
# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
-__all__ = ["Computers", "TimeSerie"]
+__all__ = ["TimeSerieCPP", "TimeSerie",
+ # deprecated
+ "Computers"]
import numpy as np
import warnings
@@ -15,7 +17,10 @@
from lightsim2grid.lightSimBackend import LightSimBackend
from lightsim2grid.solver import SolverType
-from lightsim2grid_cpp import Computers
+from lightsim2grid_cpp import TimeSeriesCPP
+
+# deprecated
+Computers = TimeSeriesCPP
class TimeSerie:
@@ -79,7 +84,7 @@ def __init__(self, grid2op_env):
raise RuntimeError("Please an environment of class \"Environment\", "
"and not \"MultimixEnv\" or \"BaseMultiProcessEnv\"")
self.grid2op_env = grid2op_env.copy()
- self.computer = Computers(self.grid2op_env.backend._grid)
+ self.computer = TimeSeriesCPP(self.grid2op_env.backend._grid)
self.prod_p = None
self.load_p = None
self.load_q = None
diff --git a/setup.py b/setup.py
index 154f7eda..cb4269da 100644
--- a/setup.py
+++ b/setup.py
@@ -14,7 +14,7 @@
from pybind11.setup_helpers import Pybind11Extension, build_ext
-__version__ = "0.7.5.post1"
+__version__ = "0.8.0"
KLU_SOLVER_AVAILABLE = False
# Try to link against SuiteSparse (if available)
@@ -95,7 +95,7 @@
"be available, which is maybe ~30% slower than \"KLU\". If you are using grid2op there "
"will still be a huge benefit.")
-INCLUDE = INCLUDE_suitesparse
+INCLUDE = ["src"] + INCLUDE_suitesparse
# now add the Eigen library (header only)
eigen_path = os.path.abspath(".")
@@ -137,30 +137,31 @@
f"-DVERSION_MEDIUM={VERSION_MEDIUM}",
f"-DVERSION_MINOR={VERSION_MINOR}"]
src_files = ['src/main.cpp',
+ "src/powerflow_algorithm/GaussSeidelAlgo.cpp",
+ "src/powerflow_algorithm/GaussSeidelSynchAlgo.cpp",
+ "src/powerflow_algorithm/BaseAlgo.cpp",
+ "src/linear_solvers/SparseLUSolver.cpp",
"src/help_fun_msg.cpp",
- "src/SparseLUSolver.cpp",
"src/BaseConstants.cpp",
"src/GridModel.cpp",
- "src/DataConverter.cpp",
- "src/DataLine.cpp",
- "src/DataGeneric.cpp",
- "src/DataShunt.cpp",
- "src/DataTrafo.cpp",
- "src/DataLoad.cpp",
- "src/DataGen.cpp",
- "src/DataSGen.cpp",
- "src/DataDCLine.cpp",
"src/ChooseSolver.cpp",
- "src/GaussSeidelSolver.cpp",
- "src/GaussSeidelSynchSolver.cpp",
- "src/BaseSolver.cpp",
- "src/BaseMultiplePowerflow.cpp",
- "src/Computers.cpp",
- "src/SecurityAnalysis.cpp",
- "src/Solvers.cpp"]
+ "src/Solvers.cpp",
+ "src/Utils.cpp",
+ "src/DataConverter.cpp",
+ "src/batch_algorithm/BaseBatchSolverSynch.cpp",
+ "src/batch_algorithm/TimeSeries.cpp",
+ "src/batch_algorithm/ContingencyAnalysis.cpp",
+ "src/element_container/LineContainer.cpp",
+ "src/element_container/GenericContainer.cpp",
+ "src/element_container/ShuntContainer.cpp",
+ "src/element_container/TrafoContainer.cpp",
+ "src/element_container/LoadContainer.cpp",
+ "src/element_container/GeneratorContainer.cpp",
+ "src/element_container/SGenContainer.cpp",
+ "src/element_container/DCLineContainer.cpp"]
if KLU_SOLVER_AVAILABLE:
- src_files.append("src/KLUSolver.cpp")
+ src_files.append("src/linear_solvers/KLUSolver.cpp")
extra_compile_args_tmp.append("-DKLU_SOLVER_AVAILABLE")
print("INFO: Using KLU package")
@@ -207,7 +208,7 @@
if include_nicslu and libnicslu_path is not None:
LIBS.append(os.path.join(path_nicslu, libnicslu_path))
include_dirs.append(os.path.join(path_nicslu, "include"))
- src_files.append("src/NICSLUSolver.cpp")
+ src_files.append("src/linear_solvers/NICSLUSolver.cpp")
extra_compile_args.append("-DNICSLU_SOLVER_AVAILABLE")
print("INFO: Using NICSLU package")
@@ -254,7 +255,7 @@
if include_cktso and libcktso_path is not None:
LIBS.append(os.path.join(path_cktso, libcktso_path))
include_dirs.append(os.path.join(path_cktso, "include"))
- src_files.append("src/CKTSOSolver.cpp")
+ src_files.append("src/linear_solvers/CKTSOSolver.cpp")
extra_compile_args.append("-DCKTSO_SOLVER_AVAILABLE")
print("INFO: Using CKTSO package")
@@ -386,6 +387,7 @@
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
+ 'Programming Language :: Python :: 3.12',
"License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
"Intended Audience :: Developers",
"Intended Audience :: Education",
diff --git a/src/ChooseSolver.cpp b/src/ChooseSolver.cpp
index 165f7871..bfd87dd0 100644
--- a/src/ChooseSolver.cpp
+++ b/src/ChooseSolver.cpp
@@ -7,3 +7,80 @@
// This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
#include "ChooseSolver.h"
+
+std::ostream& operator<<(std::ostream& out, const SolverType& solver_type)
+{
+ switch (solver_type)
+ {
+ case SolverType::SparseLU:
+ out << "SparseLU";
+ break;
+ case SolverType::KLU:
+ out << "KLU";
+ break;
+ case SolverType::GaussSeidel:
+ out << "GaussSeidel";
+ break;
+ case SolverType::DC:
+ out << "DC";
+ break;
+ case SolverType::GaussSeidelSynch:
+ out << "GaussSeidelSynch";
+ break;
+ case SolverType::NICSLU:
+ out << "NICSLU";
+ break;
+ case SolverType::SparseLUSingleSlack:
+ out << "SparseLUSingleSlack";
+ break;
+ case SolverType::KLUSingleSlack:
+ out << "KLUSingleSlack";
+ break;
+ case SolverType::NICSLUSingleSlack:
+ out << "NICSLUSingleSlack";
+ break;
+ case SolverType::KLUDC:
+ out << "KLUDC";
+ break;
+ case SolverType::NICSLUDC:
+ out << "NICSLUDC";
+ break;
+ case SolverType::CKTSO:
+ out << "CKTSO";
+ break;
+ case SolverType::CKTSOSingleSlack:
+ out << "CKTSOSingleSlack";
+ break;
+ case SolverType::CKTSODC:
+ out << "CKTSODC";
+ break;
+ case SolverType::FDPF_XB_SparseLU:
+ out << "FDPF_XB_SparseLU";
+ break;
+ case SolverType::FDPF_BX_SparseLU:
+ out << "FDPF_BX_SparseLU";
+ break;
+ case SolverType::FDPF_XB_KLU:
+ out << "FDPF_XB_KLU";
+ break;
+ case SolverType::FDPF_BX_KLU:
+ out << "FDPF_BX_KLU";
+ break;
+ case SolverType::FDPF_XB_NICSLU:
+ out << "FDPF_XB_NICSLU";
+ break;
+ case SolverType::FDPF_BX_NICSLU:
+ out << "FDPF_BX_NICSLU";
+ break;
+ case SolverType::FDPF_XB_CKTSO:
+ out << "FDPF_XB_CKTSO";
+ break;
+ case SolverType::FDPF_BX_CKTSO:
+ out << "FDPF_BX_CKTSO";
+ break;
+ default:
+ out << "(unknown)";
+ break;
+ }
+ return out;
+}
diff --git a/src/ChooseSolver.h b/src/ChooseSolver.h
index 57d5e696..9c17e43d 100644
--- a/src/ChooseSolver.h
+++ b/src/ChooseSolver.h
@@ -13,9 +13,6 @@
// import newton raphson solvers using different linear algebra solvers
#include "Solvers.h"
-#include "GaussSeidelSolver.h"
-#include "GaussSeidelSynchSolver.h"
-#include "DCSolver.h"
enum class SolverType {SparseLU, KLU, GaussSeidel, DC, GaussSeidelSynch, NICSLU,
SparseLUSingleSlack, KLUSingleSlack, NICSLUSingleSlack,
@@ -27,6 +24,8 @@ enum class SolverType {SparseLU, KLU, GaussSeidel, DC, GaussSeidelSynch, NICSLU,
FDPF_XB_CKTSO, FDPF_BX_CKTSO // from 0.7.5
};
+
+std::ostream& operator<<(std::ostream& out, const SolverType& solver_type);
// TODO define a template class instead of these weird stuff !!!
// TODO export all methods from base class !
@@ -173,10 +172,12 @@ class ChooseSolver
#endif
// now switch the union (see https://en.cppreference.com/w/cpp/language/union)
+ // reset the old solver
reset();
-
// and assign the right solver
_solver_type = type;
+ // and now reset the new one
+ reset();
}
void reset()
@@ -185,7 +186,7 @@ class ChooseSolver
return p_solver -> reset();
}
- // benefit from dynamic stuff and inheritance by having a method that returns a BaseSolver *
+ // benefit from dynamic stuff and inheritance by having a method that returns a BaseAlgo *
bool compute_pf(const Eigen::SparseMatrix & Ybus, // size (nb_bus, nb_bus)
CplxVect & V, // size nb_bus
const CplxVect & Sbus, // size nb_bus
@@ -218,6 +219,7 @@ class ChooseSolver
auto p_solver = get_prt_solver("get_Vm", true);
return p_solver -> get_Vm();
}
+
Eigen::Ref > get_J() const
{
check_right_solver("get_J");
@@ -262,6 +264,23 @@ class ChooseSolver
else throw std::runtime_error("Unknown solver type encountered (get_J)");
}
+ RealMat get_ptdf(const Eigen::SparseMatrix & dcYbus){
+ if(_solver_type != SolverType::DC &&
+ _solver_type != SolverType::KLUDC &&
+ _solver_type != SolverType::NICSLUDC &&
+ _solver_type != SolverType::CKTSODC){
+ throw std::runtime_error("ChooseSolver::get_ptdf: cannot get ptdf for a solver that is not DC.");
+ }
+ auto p_solver = get_prt_solver("get_ptdf", true);
+ const auto & res = p_solver -> get_ptdf(dcYbus);
+ return res;
+ }
+
+ void tell_solver_control(const SolverControl & solver_control){
+ auto p_solver = get_prt_solver("tell_solver_control", false);
+ p_solver -> tell_solver_control(solver_control);
+ }
+
/** apparently i cannot pass a const ref for a sparse matrix in python**/
Eigen::SparseMatrix get_J_python() const{
Eigen::SparseMatrix res = get_J();
@@ -292,7 +311,17 @@ class ChooseSolver
private:
void check_right_solver(const std::string & error_msg) const
{
- if(_solver_type != _type_used_for_nr) throw std::runtime_error("ChooseSolver: Solver mismatch when calling '"+error_msg+"': current solver is not the last solver used to perform a powerflow");
+ if(_solver_type != _type_used_for_nr){
+ std::ostringstream exc_;
+ exc_ << "ChooseSolver: Solver mismatch when calling '";
+ exc_ << error_msg;
+ exc_ << ": current solver (";
+ exc_ << _solver_type;
+ exc_ << ") is not the one used to perform a powerflow (";
+ exc_ << _type_used_for_nr;
+ exc_ << ").";
+ throw std::runtime_error(exc_.str());
+ }
#ifndef KLU_SOLVER_AVAILABLE
if(_solver_type == SolverType::KLU){
@@ -356,9 +385,9 @@ class ChooseSolver
/**
returns a pointer to the current solver used
**/
- const BaseSolver * get_prt_solver(const std::string & error_msg, bool check_right_solver_=true) const {
+ const BaseAlgo * get_prt_solver(const std::string & error_msg, bool check_right_solver_=true) const {
if (check_right_solver_) check_right_solver(error_msg);
- const BaseSolver * res;
+ const BaseAlgo * res;
if(_solver_type == SolverType::SparseLU){res = &_solver_lu;}
else if(_solver_type == SolverType::SparseLUSingleSlack){res = &_solver_lu_single;}
else if(_solver_type == SolverType::DC){res = &_solver_dc;}
@@ -390,9 +419,9 @@ class ChooseSolver
else throw std::runtime_error("Unknown solver type encountered (ChooseSolver get_prt_solver const)");
return res;
}
- BaseSolver * get_prt_solver(const std::string & error_msg, bool check_right_solver_=true) {
+ BaseAlgo * get_prt_solver(const std::string & error_msg, bool check_right_solver_=true) {
if (check_right_solver_) check_right_solver(error_msg);
- BaseSolver * res;
+ BaseAlgo * res;
if(_solver_type == SolverType::SparseLU){res = &_solver_lu;}
else if(_solver_type == SolverType::SparseLUSingleSlack){res = &_solver_lu_single;}
else if(_solver_type == SolverType::DC){res = &_solver_dc;}
@@ -432,8 +461,8 @@ class ChooseSolver
// TODO have a way to use Union here https://en.cppreference.com/w/cpp/language/union
SparseLUSolver _solver_lu;
SparseLUSolverSingleSlack _solver_lu_single;
- GaussSeidelSolver _solver_gaussseidel;
- GaussSeidelSynchSolver _solver_gaussseidelsynch;
+ GaussSeidelAlgo _solver_gaussseidel;
+ GaussSeidelSynchAlgo _solver_gaussseidelsynch;
DCSolver _solver_dc;
FDPF_XB_SparseLUSolver _solver_fdpf_xb_lu;
FDPF_BX_SparseLUSolver _solver_fdpf_bx_lu;
diff --git a/src/DCSolver.h b/src/DCSolver.h
deleted file mode 100644
index 54e16579..00000000
--- a/src/DCSolver.h
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright (c) 2020, RTE (https://www.rte-france.com)
-// See AUTHORS.txt
-// This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
-// If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
-// you can obtain one at http://mozilla.org/MPL/2.0/.
-// SPDX-License-Identifier: MPL-2.0
-// This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
-
-#ifndef DCSOLVER_H
-#define DCSOLVER_H
-
-#include "BaseSolver.h"
-// TODO make err_ more explicit: use an enum
-template
-class BaseDCSolver: public BaseSolver
-{
- public:
- BaseDCSolver():BaseSolver(false), _linear_solver(), need_factorize_(true){};
-
- ~BaseDCSolver(){}
-
- virtual void reset();
-
- // TODO SLACK : this should be handled in Sbus by the gridmodel maybe ?
- virtual
- bool compute_pf(const Eigen::SparseMatrix & Ybus,
- CplxVect & V,
- const CplxVect & Sbus,
- const Eigen::VectorXi & slack_ids,
- const RealVect & slack_weights, // currently unused
- const Eigen::VectorXi & pv,
- const Eigen::VectorXi & pq,
- int max_iter,
- real_type tol
- );
-
- private:
- // no copy allowed
- BaseDCSolver( const BaseSolver & ) =delete ;
- BaseDCSolver & operator=( const BaseSolver & ) =delete;
-
- protected:
- LinearSolver _linear_solver;
- bool need_factorize_;
-
-};
-
-#include "DCSolver.tpp"
-
-#endif // DCSOLVER_H
diff --git a/src/DCSolver.tpp b/src/DCSolver.tpp
deleted file mode 100644
index ff38cc10..00000000
--- a/src/DCSolver.tpp
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright (c) 2020, RTE (https://www.rte-france.com)
-// See AUTHORS.txt
-// This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
-// If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
-// you can obtain one at http://mozilla.org/MPL/2.0/.
-// SPDX-License-Identifier: MPL-2.0
-// This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
-
-// #include "DCSolver.h"
-
-// TODO SLACK !!!
-template
-bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix & Ybus,
- CplxVect & V,
- const CplxVect & Sbus,
- const Eigen::VectorXi & slack_ids,
- const RealVect & slack_weights,
- const Eigen::VectorXi & pv,
- const Eigen::VectorXi & pq,
- int max_iter,
- real_type tol
- )
-{
- // max_iter is ignored
- // tol is ignored
- // V is used the following way: at pq buses it's completely ignored. For pv bus only the magnitude is used,
- // and for the slack bus both the magnitude and the angle are used.
-
- auto timer = CustTimer();
- BaseSolver::reset_timer();
- const int nb_bus_solver = static_cast(Ybus.rows());
-
- #ifdef __COUT_TIMES
- auto timer_preproc = CustTimer();
- #endif // __COUT_TIMES
- Eigen::SparseMatrix dcYbus = Eigen::SparseMatrix(nb_bus_solver - 1, nb_bus_solver - 1);
-
- const CplxVect & Sbus_tmp = Sbus;
-
- // TODO SLACK (for now i put all slacks as PV, except the first one)
- // this should be handled in Sbus, because we know the amount of power absorbed by the slack
- // so we can compute it correctly !
- const Eigen::VectorXi my_pv = retrieve_pv_with_slack(slack_ids, pv);
- // const Eigen::VectorXi & my_pv = pv;
-
- // find the slack buses
- Eigen::VectorXi slack_bus_ids_solver = extract_slack_bus_id(my_pv, pq, nb_bus_solver);
- // corresp bus -> solverbus
- Eigen::VectorXi ybus_to_me = Eigen::VectorXi::Constant(nb_bus_solver, -1);
- // Eigen::VectorXi me_to_ybus = Eigen::VectorXi::Constant(nb_bus_solver - slack_bus_ids_solver.size(), -1);
- int solver_id = 0;
- for (int ybus_id=0; ybus_id < nb_bus_solver; ++ybus_id){
- if(isin(ybus_id, slack_bus_ids_solver)) continue; // I don't add anything to the slack bus
- ybus_to_me(ybus_id) = solver_id;
- // me_to_ybus(solver_id) = ybus_id;
- ++solver_id;
- }
- // remove the slack bus from Ybus
- // and extract only real part
- // TODO see if "prune" might work here https://eigen.tuxfamily.org/dox/classEigen_1_1SparseMatrix.html#title29
- std::vector > tripletList;
- tripletList.reserve(Ybus.nonZeros());
- for (int k=0; k < nb_bus_solver; ++k){
- if(ybus_to_me(k) == -1) continue; // I don't add anything to the slack bus
- for (Eigen::SparseMatrix::InnerIterator it(Ybus, k); it; ++it)
- {
- int row_res = static_cast(it.row()); // TODO Eigen::Index here ?
- if(ybus_to_me(row_res) == -1) continue;
- row_res = ybus_to_me(row_res);
- int col_res = static_cast(it.col()); // should be k // TODO Eigen::Index here ?
- col_res = ybus_to_me(col_res);
- tripletList.push_back(Eigen::Triplet (row_res, col_res, std::real(it.value())));
- }
- }
- dcYbus.setFromTriplets(tripletList.begin(), tripletList.end());
- dcYbus.makeCompressed();
-
- #ifdef __COUT_TIMES
- std::cout << "\t dc: preproc: " << 1000. * timer_preproc.duration() << "ms" << std::endl;
- #endif // __COUT_TIMES
-
- // initialize the solver (only if needed)
- #ifdef __COUT_TIMES
- auto timer_solve = CustTimer();
- #endif // __COUT_TIMES
- bool just_factorize = false;
- if(need_factorize_){
- ErrorType status_init = _linear_solver.initialize(dcYbus);
- if(status_init != ErrorType::NoError){
- err_ = status_init;
- return false;
- }
- need_factorize_ = false;
- just_factorize = true;
- }
-
- // remove the slack bus from Sbus
- RealVect dcSbus = RealVect::Constant(nb_bus_solver - slack_bus_ids_solver.size(), my_zero_);
- for (int k=0; k < nb_bus_solver; ++k){
- if(ybus_to_me(k) == -1) continue; // I don't add anything to the slack bus
- const int col_res = ybus_to_me(k);
- dcSbus(col_res) = std::real(Sbus_tmp(k));
- }
-
- // solve for theta: Sbus = dcY . theta
- RealVect Va_dc_without_slack = dcSbus;
-
- ErrorType error = _linear_solver.solve(dcYbus, Va_dc_without_slack, just_factorize);
- if(error != ErrorType::NoError){
- err_ = error;
- timer_total_nr_ += timer.duration();
- return false;
- }
-
- if(!Va_dc_without_slack.array().allFinite()){
- // for convergence, all values should be finite
- err_ = ErrorType::SolverSolve;
- V = CplxVect();
- V_ = CplxVect();
- Vm_ = RealVect();
- Va_ = RealVect();
- timer_total_nr_ += timer.duration();
- return false;
- }
-
- #ifdef __COUT_TIMES
- std::cout << "\t dc solve: " << 1000. * timer_solve.duration() << "ms" << std::endl;
- auto timer_postproc = CustTimer();
- #endif // __COUT_TIMES
-
- // retrieve back the results in the proper shape (add back the slack bus)
- // TODO have a better way for this, for example using `.segment(0,npv)`
- // see the BaseSolver.cpp: _evaluate_Fx
- RealVect Va_dc = RealVect::Constant(nb_bus_solver, my_zero_);
- // fill Va from dc approx
- for (int ybus_id=0; ybus_id < nb_bus_solver; ++ybus_id){
- if(ybus_to_me(ybus_id) == -1) continue; // slack bus is handled elsewhere
- const int bus_me = ybus_to_me(ybus_id);
- Va_dc(ybus_id) = Va_dc_without_slack(bus_me);
- }
- Va_dc.array() += std::arg(V(slack_bus_ids_solver(0)));
-
- // save the results
- Va_ = Va_dc;
-
- // add the Voltage setpoints of the generator
- Vm_ = V.array().abs();
- Vm_(slack_bus_ids_solver) = V(slack_bus_ids_solver).array().abs();
-
- // now compute the resulting complex voltage
- V_ = (Va_.array().cos().template cast() + my_i * Va_.array().sin().template cast());
-
- V_.array() *= Vm_.array();
- nr_iter_ = 1;
- V = V_;
-
- #ifdef __COUT_TIMES
- std::cout << "\t dc postproc: " << 1000. * timer_postproc.duration() << "ms" << std::endl;
- #endif // __COUT_TIMES
-
- timer_total_nr_ += timer.duration();
- return true;
-}
-
-template
-void BaseDCSolver::reset(){
- BaseSolver::reset();
- _linear_solver.reset();
- need_factorize_ = true;
-}
diff --git a/src/DataConverter.cpp b/src/DataConverter.cpp
index 3390d2fe..49c5f52c 100644
--- a/src/DataConverter.cpp
+++ b/src/DataConverter.cpp
@@ -129,7 +129,7 @@ std::tuple
-#include
-
-DataDCLine::StateRes DataDCLine::get_state() const
-{
- std::vector loss_percent(loss_percent_.begin(), loss_percent_.end());
- std::vector loss_mw(loss_mw_.begin(), loss_mw_.end());
- std::vector status = status_;
- DataDCLine::StateRes res(from_gen_.get_state(),
- to_gen_.get_state(),
- loss_percent,
- loss_mw,
- status);
- return res;
-}
-
-void DataDCLine::set_state(DataDCLine::StateRes & my_state){
- reset_results();
- from_gen_.set_state(std::get<0>(my_state));
- to_gen_.set_state(std::get<1>(my_state));
- std::vector & loss_percent = std::get<2>(my_state);
- std::vector & loss_mw = std::get<3>(my_state);
- std::vector & status = std::get<4>(my_state);
- status_ = status;
- loss_percent_ = RealVect::Map(&loss_percent[0], loss_percent.size());
- loss_mw_ = RealVect::Map(&loss_mw[0], loss_percent.size());
-}
-
-void DataDCLine::init(const Eigen::VectorXi & branch_from_id,
- const Eigen::VectorXi & branch_to_id,
- const RealVect & p_mw,
- const RealVect & loss_percent,
- const RealVect & loss_mw,
- const RealVect & vm_or_pu,
- const RealVect & vm_ex_pu,
- const RealVect & min_q_or,
- const RealVect & max_q_or,
- const RealVect & min_q_ex,
- const RealVect & max_q_ex){
- loss_percent_ = loss_percent;
- loss_mw_ = loss_mw;
- status_ = std::vector(branch_from_id.size(), true);
-
- from_gen_.init(p_mw, vm_or_pu, min_q_or, max_q_or, branch_from_id);
- RealVect p_ex = p_mw;
- unsigned int size_ = p_mw.size();
- for(unsigned int i = 0; i < size_; ++i){
- p_ex(i) = get_to_mw(i, p_ex(i));
- }
- to_gen_.init(p_ex, vm_ex_pu, min_q_ex, max_q_ex, branch_to_id);
-}
diff --git a/src/DataLoad.cpp b/src/DataLoad.cpp
deleted file mode 100644
index 003ca81c..00000000
--- a/src/DataLoad.cpp
+++ /dev/null
@@ -1,120 +0,0 @@
-// Copyright (c) 2020, RTE (https://www.rte-france.com)
-// See AUTHORS.txt
-// This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
-// If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
-// you can obtain one at http://mozilla.org/MPL/2.0/.
-// SPDX-License-Identifier: MPL-2.0
-// This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
-
-#include "DataLoad.h"
-#include
-
-void DataLoad::init(const RealVect & loads_p,
- const RealVect & loads_q,
- const Eigen::VectorXi & loads_bus_id)
-{
- p_mw_ = loads_p;
- q_mvar_ = loads_q;
- bus_id_ = loads_bus_id;
- status_ = std::vector(loads_p.size(), true);
-}
-
-
-DataLoad::StateRes DataLoad::get_state() const
-{
- std::vector p_mw(p_mw_.begin(), p_mw_.end());
- std::vector q_mvar(q_mvar_.begin(), q_mvar_.end());
- std::vector bus_id(bus_id_.begin(), bus_id_.end());
- std::vector status = status_;
- DataLoad::StateRes res(p_mw, q_mvar, bus_id, status);
- return res;
-}
-void DataLoad::set_state(DataLoad::StateRes & my_state )
-{
- reset_results();
-
- std::vector & p_mw = std::get<0>(my_state);
- std::vector & q_mvar = std::get<1>(my_state);
- std::vector & bus_id = std::get<2>(my_state);
- std::vector & status = std::get<3>(my_state);
- // TODO check sizes
-
- // input data
- p_mw_ = RealVect::Map(&p_mw[0], p_mw.size());
- q_mvar_ = RealVect::Map(&q_mvar[0], q_mvar.size());
- bus_id_ = Eigen::VectorXi::Map(&bus_id[0], bus_id.size());
- status_ = status;
-}
-
-
-void DataLoad::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const {
- int nb_load = nb();
- int bus_id_me, bus_id_solver;
- cplx_type tmp;
- for(int load_id = 0; load_id < nb_load; ++load_id){
- // i don't do anything if the load is disconnected
- if(!status_[load_id]) continue;
-
- bus_id_me = bus_id_(load_id);
- bus_id_solver = id_grid_to_solver[bus_id_me];
- if(bus_id_solver == _deactivated_bus_id){
- std::ostringstream exc_;
- exc_ << "DataLoad::fillSbus: the load with id ";
- exc_ << load_id;
- exc_ << " is connected to a disconnected bus while being connected";
- throw std::runtime_error(exc_.str());
- }
- tmp = static_cast(p_mw_(load_id));
- tmp += my_i * q_mvar_(load_id);
- Sbus.coeffRef(bus_id_solver) -= tmp;
- }
-}
-
-void DataLoad::compute_results(const Eigen::Ref & Va,
- const Eigen::Ref & Vm,
- const Eigen::Ref & V,
- const std::vector & id_grid_to_solver,
- const RealVect & bus_vn_kv,
- real_type sn_mva,
- bool ac)
-{
- int nb_load = nb();
- v_kv_from_vpu(Va, Vm, status_, nb_load, bus_id_, id_grid_to_solver, bus_vn_kv, res_v_);
- v_deg_from_va(Va, Vm, status_, nb_load, bus_id_, id_grid_to_solver, bus_vn_kv, res_theta_);
- res_p_ = p_mw_;
- res_q_ = q_mvar_;
-}
-
-void DataLoad::reset_results(){
- res_p_ = RealVect(); // in MW
- res_q_ = RealVect(); // in MVar
- res_v_ = RealVect(); // in kV
-}
-
-void DataLoad::change_p(int load_id, real_type new_p, bool & need_reset)
-{
- bool my_status = status_.at(load_id); // and this check that load_id is not out of bound
- if(!my_status)
- {
- std::ostringstream exc_;
- exc_ << "DataLoad::change_p: Impossible to change the active value of a disconnected load (check load id ";
- exc_ << load_id;
- exc_ << ")";
- throw std::runtime_error(exc_.str());
- }
- p_mw_(load_id) = new_p;
-}
-
-void DataLoad::change_q(int load_id, real_type new_q, bool & need_reset)
-{
- bool my_status = status_.at(load_id); // and this check that load_id is not out of bound
- if(!my_status)
- {
- std::ostringstream exc_;
- exc_ << "DataLoad::change_q: Impossible to change the reactive value of a disconnected load (check load id ";
- exc_ << load_id;
- exc_ << ")";
- throw std::runtime_error(exc_.str());
- }
- q_mvar_(load_id) = new_q;
-}
diff --git a/src/DataSGen.cpp b/src/DataSGen.cpp
deleted file mode 100644
index c0c6fec7..00000000
--- a/src/DataSGen.cpp
+++ /dev/null
@@ -1,156 +0,0 @@
-// Copyright (c) 2020, RTE (https://www.rte-france.com)
-// See AUTHORS.txt
-// This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
-// If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
-// you can obtain one at http://mozilla.org/MPL/2.0/.
-// SPDX-License-Identifier: MPL-2.0
-// This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.
-
-#include "DataSGen.h"
-void DataSGen::init(const RealVect & sgen_p,
- const RealVect & sgen_q,
- const RealVect & sgen_pmin,
- const RealVect & sgen_pmax,
- const RealVect & sgen_qmin,
- const RealVect & sgen_qmax,
- const Eigen::VectorXi & sgen_bus_id)
-{
- int size = static_cast(sgen_p.size());
- DataGeneric::check_size(sgen_p, size, "sgen_p");
- DataGeneric::check_size(sgen_q, size, "sgen_q");
- DataGeneric::check_size(sgen_pmin, size, "sgen_pmin");
- DataGeneric::check_size(sgen_pmax, size, "sgen_pmax");
- DataGeneric::check_size(sgen_qmin, size, "sgen_qmin");
- DataGeneric::check_size(sgen_qmax, size, "sgen_qmax");
- DataGeneric::check_size(sgen_bus_id, size, "sgen_bus_id");
-
- p_mw_ = sgen_p;
- q_mvar_ = sgen_q;
- p_min_mw_ = sgen_pmin;
- p_max_mw_ = sgen_pmax;
- q_min_mvar_ = sgen_qmin;
- q_max_mvar_ = sgen_qmax;
- bus_id_ = sgen_bus_id;
- status_ = std::vector(sgen_p.size(), true);
-}
-
-
-DataSGen::StateRes DataSGen::get_state() const
-{
- std::vector p_mw(p_mw_.begin(), p_mw_.end());
- std::vector q_mvar(q_mvar_.begin(), q_mvar_.end());
- std::vector p_min(p_min_mw_.begin(), p_min_mw_.end());
- std::vector p_max(p_max_mw_.begin(), p_max_mw_.end());
- std::vector q_min(q_min_mvar_.begin(), q_min_mvar_.end());
- std::vector q_max(q_max_mvar_.begin(), q_max_mvar_.end());
- std::vector bus_id(bus_id_.begin(), bus_id_.end());
- std::vector status = status_;
- DataSGen::StateRes res(p_mw, q_mvar, p_min, p_max, q_min, q_max, bus_id, status);
- return res;
-}
-void DataSGen::set_state(DataSGen::StateRes & my_state )
-{
- reset_results();
-
- std::vector & p_mw = std::get<0>(my_state);
- std::vector & q_mvar = std::get<1>(my_state);
- std::vector & p_min = std::get<2>(my_state);
- std::vector & p_max = std::get<3>(my_state);
- std::vector & q_min = std::get<4>(my_state);
- std::vector & q_max = std::get<5>(my_state);
- std::vector & bus_id = std::get<6>(my_state);
- std::vector & status = std::get<7>(my_state);
- auto size = p_mw.size();
- DataGeneric::check_size(p_mw, size, "p_mw");
- DataGeneric::check_size(q_mvar, size, "q_mvar");
- DataGeneric::check_size(p_min, size, "p_min");
- DataGeneric::check_size(p_max, size, "p_max");
- DataGeneric::check_size(q_min, size, "q_min");
- DataGeneric::check_size(q_max, size, "q_max");
- DataGeneric::check_size(bus_id, size, "bus_id");
- DataGeneric::check_size(status, size, "status");
-
- p_mw_ = RealVect::Map(&p_mw[0], size);
- q_mvar_ = RealVect::Map(&q_mvar[0], size);
- q_mvar_ = RealVect::Map(&q_mvar[0], size);
- p_min_mw_ = RealVect::Map(&p_min[0], size);
- p_max_mw_ = RealVect::Map(&p_max[0], size);
- q_min_mvar_ = RealVect::Map(&q_min[0], size);
- q_max_mvar_ = RealVect::Map(&q_max[0], size);
- bus_id_ = Eigen::VectorXi::Map(&bus_id[0], bus_id.size());
- status_ = status;
-}
-
-
-void DataSGen::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const {
- const int nb_sgen = nb();
- int bus_id_me, bus_id_solver;
- cplx_type tmp;
- for(int sgen_id = 0; sgen_id < nb_sgen; ++sgen_id){
- // i don't do anything if the static generator is disconnected
- if(!status_[sgen_id]) continue;
-
- bus_id_me = bus_id_(sgen_id);
- bus_id_solver = id_grid_to_solver[bus_id_me];
- if(bus_id_solver == _deactivated_bus_id){
- std::ostringstream exc_;
- exc_ << "DataSGen::fillSbus: Static Generator with id ";
- exc_ << sgen_id;
- exc_ << " is connected to a disconnected bus while being connected to the grid.";
- throw std::runtime_error(exc_.str());
- }
- tmp = static_cast(p_mw_(sgen_id));
- tmp += my_i * q_mvar_(sgen_id);
- Sbus.coeffRef(bus_id_solver) += tmp;
- }
-}
-
-void DataSGen::compute_results(const Eigen::Ref & Va,
- const Eigen::Ref & Vm,
- const Eigen::Ref & V,
- const std::vector & id_grid_to_solver,
- const RealVect & bus_vn_kv,
- real_type sn_mva,
- bool ac)
-{
- const int nb_sgen = nb();
- v_kv_from_vpu(Va, Vm, status_, nb_sgen, bus_id_, id_grid_to_solver, bus_vn_kv, res_v_);
- v_deg_from_va(Va, Vm, status_, nb_sgen, bus_id_, id_grid_to_solver, bus_vn_kv, res_theta_);
- res_p_ = p_mw_;
- if(ac) res_q_ = q_mvar_;
- else res_q_ = RealVect::Zero(nb_sgen);
-}
-
-void DataSGen::reset_results(){
- res_p_ = RealVect(); // in MW
- res_q_ = RealVect(); // in MVar
- res_v_ = RealVect(); // in kV
-}
-
-void DataSGen::change_p(int sgen_id, real_type new_p, bool & need_reset)
-{
- bool my_status = status_.at(sgen_id); // and this check that load_id is not out of bound
- if(!my_status)
- {
- std::ostringstream exc_;
- exc_ << "DataSGen::change_p: Impossible to change the active value of a disconnected static generator (check sgen id ";
- exc_ << sgen_id;
- exc_ << ")";
- throw std::runtime_error(exc_.str());
- }
- p_mw_(sgen_id) = new_p;
-}
-
-void DataSGen::change_q(int sgen_id, real_type new_q, bool & need_reset)
-{
- bool my_status = status_.at(sgen_id); // and this check that load_id is not out of bound
- if(!my_status)
- {
- std::ostringstream exc_;
- exc_ << "DataSGen::change_q: Impossible to change the reactive value of a disconnected static generator (check sgen id ";
- exc_ << sgen_id;
- exc_ << ")";
- throw std::runtime_error(exc_.str());
- }
- q_mvar_(sgen_id) = new_q;
-}
diff --git a/src/GridModel.cpp b/src/GridModel.cpp
index c10476b1..67b48534 100644
--- a/src/GridModel.cpp
+++ b/src/GridModel.cpp
@@ -8,21 +8,23 @@
#include "GridModel.h"
#include "ChooseSolver.h" // to avoid circular references
+#include
GridModel::GridModel(const GridModel & other)
{
reset(true, true, true);
-
- _ls_to_pp = other._ls_to_pp;
+ max_nb_bus_per_sub_ = other.max_nb_bus_per_sub_;
init_vm_pu_ = other.init_vm_pu_;
sn_mva_ = other.sn_mva_;
+ compute_results_ = other.compute_results_;
// copy the powersystem representation
// 1. bus
bus_vn_kv_ = other.bus_vn_kv_;
bus_status_ = other.bus_status_;
+ set_ls_to_orig(other._ls_to_orig); // set also orig_to_ls
// 2. powerline
powerlines_ = other.powerlines_;
@@ -72,15 +74,18 @@ GridModel::GridModel(const GridModel & other)
_solver.change_solver(other._solver.get_type());
_dc_solver.change_solver(other._dc_solver.get_type());
compute_results_ = other.compute_results_;
+ solver_control_.tell_all_changed();
_dc_solver.set_gridmodel(this);
_solver.set_gridmodel(this);
+ _dc_solver.tell_solver_control(solver_control_);
+ _solver.tell_solver_control(solver_control_);
}
//pickle
GridModel::StateRes GridModel::get_state() const
{
std::vector bus_vn_kv(bus_vn_kv_.begin(), bus_vn_kv_.end());
- std::vector ls_to_pp(_ls_to_pp.begin(), _ls_to_pp.end());
+ std::vector ls_to_orig(_ls_to_orig.begin(), _ls_to_orig.end());
int version_major = VERSION_MAJOR;
int version_medium = VERSION_MEDIUM;
int version_minor = VERSION_MINOR;
@@ -93,10 +98,26 @@ GridModel::StateRes GridModel::get_state() const
auto res_storage = storages_.get_state();
auto res_dc_line = dc_lines_.get_state();
+ std::vector load_pos_topo_vect(load_pos_topo_vect_.begin(), load_pos_topo_vect_.end());
+ std::vector gen_pos_topo_vect(gen_pos_topo_vect_.begin(), gen_pos_topo_vect_.end());
+ std::vector line_or_pos_topo_vect(line_or_pos_topo_vect_.begin(), line_or_pos_topo_vect_.end());
+ std::vector line_ex_pos_topo_vect(line_ex_pos_topo_vect_.begin(), line_ex_pos_topo_vect_.end());
+ std::vector trafo_hv_pos_topo_vect(trafo_hv_pos_topo_vect_.begin(), trafo_hv_pos_topo_vect_.end());
+ std::vector trafo_lv_pos_topo_vect(trafo_lv_pos_topo_vect_.begin(), trafo_lv_pos_topo_vect_.end());
+ std::vector storage_pos_topo_vect(storage_pos_topo_vect_.begin(), storage_pos_topo_vect_.end());
+
+ std::vector load_to_subid(load_to_subid_.begin(), load_to_subid_.end());
+ std::vector gen_to_subid(gen_to_subid_.begin(), gen_to_subid_.end());
+ std::vector line_or_to_subid(line_or_to_subid_.begin(), line_or_to_subid_.end());
+ std::vector line_ex_to_subid(line_ex_to_subid_.begin(), line_ex_to_subid_.end());
+ std::vector trafo_hv_to_subid(trafo_hv_to_subid_.begin(), trafo_hv_to_subid_.end());
+ std::vector trafo_lv_to_subid(trafo_lv_to_subid_.begin(), trafo_lv_to_subid_.end());
+ std::vector storage_to_subid(storage_to_subid_.begin(), storage_to_subid_.end());
+
GridModel::StateRes res(version_major,
version_medium,
version_minor,
- ls_to_pp,
+ ls_to_orig,
init_vm_pu_,
sn_mva_,
bus_vn_kv,
@@ -108,7 +129,23 @@ GridModel::StateRes GridModel::get_state() const
res_load,
res_sgen,
res_storage,
- res_dc_line
+ res_dc_line,
+ n_sub_,
+ max_nb_bus_per_sub_,
+ load_pos_topo_vect,
+ gen_pos_topo_vect,
+ line_or_pos_topo_vect,
+ line_ex_pos_topo_vect,
+ trafo_hv_pos_topo_vect,
+ trafo_lv_pos_topo_vect,
+ storage_pos_topo_vect,
+ load_to_subid,
+ gen_to_subid,
+ line_or_to_subid,
+ line_ex_to_subid,
+ trafo_hv_to_subid,
+ trafo_lv_to_subid,
+ storage_to_subid
);
return res;
};
@@ -118,9 +155,8 @@ void GridModel::set_state(GridModel::StateRes & my_state)
// after loading back, the instance need to be reset anyway
// TODO see if it's worth the trouble NOT to do it
reset(true, true, true);
- need_reset_ = true;
+ solver_control_.tell_all_changed();
compute_results_ = true;
- topo_changed_ = true;
// extract data from the state
int version_major = std::get<0>(my_state);
@@ -136,35 +172,68 @@ void GridModel::set_state(GridModel::StateRes & my_state)
exc_ << "It is not possible. Please reinstall it.";
throw std::runtime_error(exc_.str());
}
- std::vector ls_to_pp_ = std::get<3>(my_state);
+ const std::vector & ls_to_pp = std::get<3>(my_state);
init_vm_pu_ = std::get<4>(my_state);
sn_mva_ = std::get<5>(my_state);
- std::vector & bus_vn_kv = std::get<6>(my_state);
- std::vector & bus_status = std::get<7>(my_state);
+ const std::vector & bus_vn_kv = std::get<6>(my_state);
+ const std::vector & bus_status = std::get<7>(my_state);
// powerlines
- DataLine::StateRes & state_lines = std::get<8>(my_state);
+ LineContainer::StateRes & state_lines = std::get<8>(my_state);
// shunts
- DataShunt::StateRes & state_shunts = std::get<9>(my_state);
+ ShuntContainer::StateRes & state_shunts = std::get<9>(my_state);
// trafos
- DataTrafo::StateRes & state_trafos = std::get<10>(my_state);
+ TrafoContainer::StateRes & state_trafos = std::get<10>(my_state);
// generators
- DataGen::StateRes & state_gens = std::get<11>(my_state);
+ GeneratorContainer::StateRes & state_gens = std::get<11>(my_state);
// loads
- DataLoad::StateRes & state_loads = std::get<12>(my_state);
+ LoadContainer::StateRes & state_loads = std::get<12>(my_state);
// static gen
- DataSGen::StateRes & state_sgens= std::get<13>(my_state);
+ SGenContainer::StateRes & state_sgens= std::get<13>(my_state);
// storage units
- DataLoad::StateRes & state_storages = std::get<14>(my_state);
+ LoadContainer::StateRes & state_storages = std::get<14>(my_state);
// dc lines
- DataDCLine::StateRes & state_dc_lines = std::get<15>(my_state);
+ DCLineContainer::StateRes & state_dc_lines = std::get<15>(my_state);
+
+ // grid2op specific
+ n_sub_ = std::get<16>(my_state);
+ max_nb_bus_per_sub_ = std::get<17>(my_state);
+ const std::vector & load_pos_topo_vect = std::get<18>(my_state);
+ const std::vector & gen_pos_topo_vect = std::get<19>(my_state);
+ const std::vector & line_or_pos_topo_vect = std::get<20>(my_state);
+ const std::vector & line_ex_pos_topo_vect = std::get<21>(my_state);
+ const std::vector & trafo_hv_pos_topo_vect = std::get<22>(my_state);
+ const std::vector & trafo_lv_pos_topo_vect = std::get<23>(my_state);
+ const std::vector & storage_pos_topo_vect = std::get<24>(my_state);
+ const std::vector & load_to_subid = std::get<25>(my_state);
+ const std::vector & gen_to_subid = std::get<26>(my_state);
+ const std::vector & line_or_to_subid = std::get<27>(my_state);
+ const std::vector & line_ex_to_subid = std::get<28>(my_state);
+ const std::vector & trafo_hv_to_subid = std::get<29>(my_state);
+ const std::vector & trafo_lv_to_subid = std::get<30>(my_state);
+ const std::vector & storage_to_subid = std::get<31>(my_state);
+
+ load_pos_topo_vect_ = IntVectRowMaj::Map(load_pos_topo_vect.data(), load_pos_topo_vect.size());
+ gen_pos_topo_vect_ = IntVectRowMaj::Map(gen_pos_topo_vect.data(), gen_pos_topo_vect.size());
+ line_or_pos_topo_vect_ = IntVectRowMaj::Map(line_or_pos_topo_vect.data(), line_or_pos_topo_vect.size());
+ line_ex_pos_topo_vect_ = IntVectRowMaj::Map(line_ex_pos_topo_vect.data(), line_ex_pos_topo_vect.size());
+ trafo_hv_pos_topo_vect_ = IntVectRowMaj::Map(trafo_hv_pos_topo_vect.data(), trafo_hv_pos_topo_vect.size());
+ trafo_lv_pos_topo_vect_ = IntVectRowMaj::Map(trafo_lv_pos_topo_vect.data(), trafo_lv_pos_topo_vect.size());
+ storage_pos_topo_vect_ = IntVectRowMaj::Map(storage_pos_topo_vect.data(), storage_pos_topo_vect.size());
+ load_to_subid_ = IntVectRowMaj::Map(load_to_subid.data(), load_to_subid.size());
+ gen_to_subid_ = IntVectRowMaj::Map(gen_to_subid.data(), gen_to_subid.size());
+ line_or_to_subid_ = IntVectRowMaj::Map(line_or_to_subid.data(), line_or_to_subid.size());
+ line_ex_to_subid_ = IntVectRowMaj::Map(line_ex_to_subid.data(), line_ex_to_subid.size());
+ trafo_hv_to_subid_ = IntVectRowMaj::Map(trafo_hv_to_subid.data(), trafo_hv_to_subid.size());
+ trafo_lv_to_subid_ = IntVectRowMaj::Map(trafo_lv_to_subid.data(), trafo_lv_to_subid.size());
+ storage_to_subid_ = IntVectRowMaj::Map(storage_to_subid.data(), storage_to_subid.size());
// assign it to this instance
+ set_ls_to_orig(IntVect::Map(ls_to_pp.data(), ls_to_pp.size())); // set also _orig_to_ls
- _ls_to_pp =IntVect::Map(&ls_to_pp_[0], ls_to_pp_.size());
// buses
// 1. bus_vn_kv_
- bus_vn_kv_ = RealVect::Map(&bus_vn_kv[0], bus_vn_kv.size());
+ bus_vn_kv_ = RealVect::Map(bus_vn_kv.data(), bus_vn_kv.size());
// 2. bus status
bus_status_ = bus_status;
@@ -187,6 +256,48 @@ void GridModel::set_state(GridModel::StateRes & my_state)
dc_lines_.set_state(state_dc_lines);
};
+void GridModel::set_ls_to_orig(const IntVect & ls_to_orig){
+ if(ls_to_orig.size() == 0){
+ _ls_to_orig = IntVect();
+ _orig_to_ls = IntVect();
+ return;
+ }
+
+ if(ls_to_orig.size() != bus_vn_kv_.size())
+ throw std::runtime_error("Impossible to set the converter ls_to_orig: the provided vector has not the same size as the number of bus on the grid.");
+ _ls_to_orig = ls_to_orig;
+ const auto size = ls_to_orig.lpNorm();
+ _orig_to_ls = IntVect::Zero(size);
+ _orig_to_ls.array() -= 1;
+ for(auto i = 0; i < size; ++i){
+ _orig_to_ls[i] = _ls_to_orig[i];
+ }
+}
+
+void GridModel::set_orig_to_ls(const IntVect & orig_to_ls){
+ if(orig_to_ls.size() == 0){
+ _ls_to_orig = IntVect();
+ _orig_to_ls = IntVect();
+ return;
+ }
+ _orig_to_ls = orig_to_ls;
+ Eigen::Index nb_bus_ls = 0;
+ for(const auto el : orig_to_ls){
+ if (el != -1) nb_bus_ls += 1;
+ }
+ if(nb_bus_ls != bus_vn_kv_.size())
+ throw std::runtime_error("Impossible to set the converter orig_to_ls: the number of 'non -1' component in the provided vector does not match the number of buses on the grid.");
+ _ls_to_orig = IntVect::Zero(nb_bus_ls);
+ Eigen::Index ls2or_ind = 0;
+ for(auto or2ls_ind = 0; or2ls_ind < nb_bus_ls; ++or2ls_ind){
+ const auto my_ind = _orig_to_ls[or2ls_ind];
+ if(my_ind >= 0){
+ _ls_to_orig[ls2or_ind] = my_ind;
+ ls2or_ind++;
+ }
+ }
+}
+
//init
void GridModel::init_bus(const RealVect & bus_vn_kv, int nb_line, int nb_trafo){
/**
@@ -194,10 +305,13 @@ void GridModel::init_bus(const RealVect & bus_vn_kv, int nb_line, int nb_trafo){
and
initialize the Ybus_ matrix at the proper shape
**/
- const int nb_bus = static_cast(bus_vn_kv.size());
+ const int nb_bus = static_cast(bus_vn_kv.size()); // size of buses are checked in set_max_nb_bus_per_sub
+
bus_vn_kv_ = bus_vn_kv; // base_kv
bus_status_ = std::vector(nb_bus, true); // by default everything is connected
+ _orig_to_ls = IntVect();
+ _ls_to_orig = IntVect();
}
void GridModel::reset(bool reset_solver, bool reset_ac, bool reset_dc)
@@ -216,12 +330,20 @@ void GridModel::reset(bool reset_solver, bool reset_ac, bool reset_dc)
Ybus_dc_ = Eigen::SparseMatrix();
}
- Sbus_ = CplxVect();
+ timer_last_ac_pf_= 0.;
+ timer_last_dc_pf_ = 0.;
+
+ acSbus_ = CplxVect();
+ dcSbus_ = CplxVect();
bus_pv_ = Eigen::VectorXi();
bus_pq_ = Eigen::VectorXi();
+ solver_control_.tell_all_changed();
+
+ slack_bus_id_ac_me_ = Eigen::VectorXi(); // slack bus id, gridmodel number
+ slack_bus_id_ac_solver_ = Eigen::VectorXi(); // slack bus id, solver number
+ slack_bus_id_dc_me_ = Eigen::VectorXi();
+ slack_bus_id_dc_solver_ = Eigen::VectorXi();
slack_weights_ = RealVect();
- need_reset_ = true;
- topo_changed_ = true;
// reset the solvers
if (reset_solver){
@@ -230,13 +352,14 @@ void GridModel::reset(bool reset_solver, bool reset_ac, bool reset_dc)
_solver.set_gridmodel(this);
_dc_solver.set_gridmodel(this);
}
- // std::cout << "GridModel::reset called" << std::endl;
+
}
CplxVect GridModel::ac_pf(const CplxVect & Vinit,
int max_iter,
real_type tol)
{
+ auto timer = CustTimer();
const int nb_bus = static_cast(bus_vn_kv_.size());
if(Vinit.size() != nb_bus){
std::ostringstream exc_;
@@ -248,28 +371,45 @@ CplxVect GridModel::ac_pf(const CplxVect & Vinit,
bool conv = false;
CplxVect res = CplxVect();
+ reset_results(); // reset the results
+
// pre process the data to define a proper jacobian matrix, the proper voltage vector etc.
bool is_ac = true;
- bool reset_solver = topo_changed_; // I reset the solver only if the topology change
- CplxVect V = pre_process_solver(Vinit, Ybus_ac_,
+ // std::cout << "before pre process" << std::endl;
+ CplxVect V = pre_process_solver(Vinit,
+ acSbus_,
+ Ybus_ac_,
id_me_to_ac_solver_,
id_ac_solver_to_me_,
+ slack_bus_id_ac_me_,
slack_bus_id_ac_solver_,
- is_ac, reset_solver);
+ is_ac,
+ solver_control_);
// start the solver
- slack_weights_ = generators_.get_slack_weights(Ybus_ac_.rows(), id_me_to_ac_solver_);
- conv = _solver.compute_pf(Ybus_ac_, V, Sbus_, slack_bus_id_ac_solver_, slack_weights_, bus_pv_, bus_pq_, max_iter, tol / sn_mva_);
+ // std::cout << "\tbefore get_slack_weights (ac)" << std::endl;
+ if(solver_control_.need_reset_solver() ||
+ solver_control_.has_dimension_changed() ||
+ solver_control_.has_slack_participate_changed() ||
+ solver_control_.has_pv_changed() ||
+ solver_control_.has_slack_weight_changed()){
+ // std::cout << "get_slack_weights" << std::endl;
+ slack_weights_ = generators_.get_slack_weights(Ybus_ac_.rows(), id_me_to_ac_solver_);
+ }
+ // std::cout << "\tbefore compute_pf" << std::endl;
+ conv = _solver.compute_pf(Ybus_ac_, V, acSbus_, slack_bus_id_ac_solver_, slack_weights_, bus_pv_, bus_pq_, max_iter, tol / sn_mva_);
- // store results (in ac mode)
+ // store results (in ac mode)
+ // std::cout << "\tbefore process_results" << std::endl;
process_results(conv, res, Vinit, true, id_me_to_ac_solver_);
+ timer_last_ac_pf_ = timer.duration();
// return the vector of complex voltage at each bus
return res;
};
void GridModel::check_solution_q_values_onegen(CplxVect & res,
- const DataGen::GenInfo& gen,
+ const GeneratorContainer::GenInfo& gen,
bool check_q_limits) const{
if(check_q_limits)
{
@@ -332,15 +472,21 @@ CplxVect GridModel::check_solution(const CplxVect & V_proposed, bool check_q_lim
// pre process the data to define a proper jacobian matrix, the proper voltage vector etc.
const int nb_bus = static_cast(V_proposed.size());
bool is_ac = true;
- bool reset_solver = false;
- CplxVect V = pre_process_solver(V_proposed, Ybus_ac_,
- id_me_to_ac_solver_, id_ac_solver_to_me_, slack_bus_id_ac_solver_,
+ SolverControl reset_solver;
+ reset_solver.tell_none_changed(); // TODO reset solver
+ CplxVect V = pre_process_solver(V_proposed,
+ acSbus_,
+ Ybus_ac_,
+ id_me_to_ac_solver_,
+ id_ac_solver_to_me_,
+ slack_bus_id_ac_me_,
+ slack_bus_id_ac_solver_,
is_ac, reset_solver);
// compute the mismatch
CplxVect tmp = Ybus_ac_ * V; // this is a vector
tmp = tmp.array().conjugate(); // i take the conjugate
- auto mis = V.array() * tmp.array() - Sbus_.array();
+ auto mis = V.array() * tmp.array() - acSbus_.array(); // TODO ac or dc here
// store results
CplxVect res = _get_results_back_to_orig_nodes(mis,
@@ -362,46 +508,101 @@ CplxVect GridModel::check_solution(const CplxVect & V_proposed, bool check_q_lim
};
CplxVect GridModel::pre_process_solver(const CplxVect & Vinit,
+ CplxVect & Sbus,
Eigen::SparseMatrix & Ybus,
std::vector & id_me_to_solver,
std::vector & id_solver_to_me,
+ Eigen::VectorXi & slack_bus_id_me,
Eigen::VectorXi & slack_bus_id_solver,
bool is_ac,
- bool reset_solver)
+ const SolverControl & solver_control)
{
// TODO get rid of the "is_ac" argument: this info is available in the _solver already
- // std::cout << "GridModel::pre_process_solver : topo_changed_ " << topo_changed_ << std::endl;
- // std::cout << "GridModel::pre_process_solver : reset_solver " << reset_solver << std::endl;
-
- bool reset_ac = topo_changed_ && is_ac;
- bool reset_dc = topo_changed_ && !is_ac;
- // if(need_reset_){ // TODO optimization when it's not mandatory to start from scratch
- if(topo_changed_) reset(reset_solver, reset_ac, reset_dc); // TODO what if pv and pq changed ? :O
- else{
- // topo is not changed, but i can still reset the solver (TODO: no necessarily needed !)
- if (reset_solver)
- {
- if(is_ac) _solver.reset();
- else _dc_solver.reset();
+ if(is_ac){
+ _solver.tell_solver_control(solver_control);
+ if(solver_control.need_reset_solver()){
+ // std::cout << "\t\t_ac_solver.reset();" << std::endl;
+ _solver.reset();
+ }
+ } else {
+ _dc_solver.tell_solver_control(solver_control);
+ if(solver_control.need_reset_solver()){
+ // std::cout << "\t\t_dc_solver.reset();" << std::endl;
+ _dc_solver.reset();
}
}
- slack_bus_id_ = generators_.get_slack_bus_id();
- if(topo_changed_){
- // TODO do not reinit Ybus if the topology does not change
- init_Ybus(Ybus, id_me_to_solver, id_solver_to_me);
- fillYbus(Ybus, is_ac, id_me_to_solver);
- }
- init_Sbus(Sbus_, id_me_to_solver, id_solver_to_me, slack_bus_id_solver);
- fillpv_pq(id_me_to_solver, id_solver_to_me, slack_bus_id_solver); // TODO what if pv and pq changed ? :O
+
+ if (solver_control.need_reset_solver() ||
+ solver_control.has_dimension_changed() ||
+ solver_control.has_slack_participate_changed()){
+ // std::cout << "\t\tslack_bus_id_solver\n";
+ slack_bus_id_me = generators_.get_slack_bus_id();
+ // this is the slack bus ids with the gridmodel ordering, not the solver ordering.
+ // conversion to solver ordering is done in init_slack_bus
+
+ // std::cout << "slack_bus_id_solver 1: ";
+ // for(auto el: slack_bus_id_solver) std::cout << el << ", ";
+ // std::cout << std::endl;
+ }
+ if (solver_control.need_reset_solver() ||
+ solver_control.ybus_change_sparsity_pattern() ||
+ solver_control.has_dimension_changed()){
+ // std::cout << "\t\tinit_Ybus;" << std::endl;
+ init_Ybus(Ybus, id_me_to_solver, id_solver_to_me);
+ // std::cout << "init_Ybus;" << std::endl;
+ }
+ if (solver_control.need_reset_solver() ||
+ solver_control.ybus_change_sparsity_pattern() ||
+ solver_control.has_dimension_changed() ||
+ solver_control.need_recompute_ybus()){
+ // std::cout << "\t\tfillYbus;" << std::endl;
+ fillYbus(Ybus, is_ac, id_me_to_solver);
+ // std::cout << "fillYbus;" << std::endl;
+ }
+ if (solver_control.need_reset_solver() ||
+ solver_control.has_dimension_changed()) {
+ // init Sbus
+ // std::cout << "\t\tinit_Sbus;" << std::endl;
+ Sbus = CplxVect::Constant(id_solver_to_me.size(), 0.);
+ // std::cout << "init_Sbus;" << std::endl;
+ }
+ if (solver_control.need_reset_solver() ||
+ solver_control.has_dimension_changed() ||
+ solver_control.has_slack_participate_changed() ||
+ solver_control.has_pv_changed() ||
+ solver_control.has_pq_changed()) {
+ // std::cout << "\t\tinit_slack_bus;" << std::endl;
+ init_slack_bus(Sbus, id_me_to_solver, id_solver_to_me, slack_bus_id_me, slack_bus_id_solver);
+ // std::cout << "\t\tfillpv_pq;" << std::endl;
+ fillpv_pq(id_me_to_solver, id_solver_to_me, slack_bus_id_solver, solver_control);
+ // std::cout << "fillpv_pq;" << std::endl;
+ }
- int nb_bus_total = static_cast(bus_vn_kv_.size());
- total_q_min_per_bus_ = RealVect::Constant(nb_bus_total, 0.);
- total_q_max_per_bus_ = RealVect::Constant(nb_bus_total, 0.);
- total_gen_per_bus_ = Eigen::VectorXi::Constant(nb_bus_total, 0);
- generators_.init_q_vector(nb_bus_total, total_gen_per_bus_, total_q_min_per_bus_, total_q_max_per_bus_);
- dc_lines_.init_q_vector(nb_bus_total, total_gen_per_bus_, total_q_min_per_bus_, total_q_max_per_bus_);
- fillSbus_me(Sbus_, is_ac, id_me_to_solver);
+ if (is_ac && (solver_control.need_reset_solver() ||
+ solver_control.has_dimension_changed() ||
+ solver_control.need_recompute_sbus() || // TODO do we need it ?
+ solver_control.has_pq_changed()) // TODO do we need it ?
+ ){
+ // std::cout << "\t\tq vector" << std::endl;
+ int nb_bus_total = static_cast(bus_vn_kv_.size());
+ total_q_min_per_bus_ = RealVect::Constant(nb_bus_total, 0.);
+ total_q_max_per_bus_ = RealVect::Constant(nb_bus_total, 0.);
+ total_gen_per_bus_ = Eigen::VectorXi::Constant(nb_bus_total, 0);
+ generators_.init_q_vector(nb_bus_total, total_gen_per_bus_, total_q_min_per_bus_, total_q_max_per_bus_);
+ dc_lines_.init_q_vector(nb_bus_total, total_gen_per_bus_, total_q_min_per_bus_, total_q_max_per_bus_);
+ // std::cout << "total_gen_per_bus_;" << std::endl;
+ }
+ if (solver_control.need_reset_solver() ||
+ solver_control.has_dimension_changed() ||
+ solver_control.has_slack_participate_changed() ||
+ solver_control.has_pq_changed() ||
+ solver_control.need_recompute_sbus()) {
+ // std::cout << "\t\tfillSbus_me;" << std::endl;
+ fillSbus_me(Sbus, is_ac, id_me_to_solver);
+ // std::cout << "fillSbus_me;" << std::endl;
+ }
+ // std::cout << "\t\tstatic_cast(id_solver_to_me.size());" << std::endl;
const int nb_bus_solver = static_cast(id_solver_to_me.size());
CplxVect V = CplxVect::Constant(nb_bus_solver, init_vm_pu_);
for(int bus_solver_id = 0; bus_solver_id < nb_bus_solver; ++bus_solver_id){
@@ -411,6 +612,11 @@ CplxVect GridModel::pre_process_solver(const CplxVect & Vinit,
}
generators_.set_vm(V, id_me_to_solver);
dc_lines_.set_vm(V, id_me_to_solver);
+ // std::cout << "pre_process_solver: V result: "<(Vinit.size()));
} else {
//powerflow diverge
+ // std::cout << "powerflow diverge" << std::endl;
reset_results();
- need_reset_ = true; // in this case, the powerflow diverge, so i need to recompute Ybus next time
+ // TODO solver control ??? something to do here ?
}
}
@@ -469,7 +677,7 @@ void GridModel::init_Ybus(Eigen::SparseMatrix & Ybus,
id_me_to_solver = std::vector(nb_bus_init, _deactivated_bus_id); // by default, if a bus is disconnected, then it has a -1 there
id_solver_to_me = std::vector();
id_solver_to_me.reserve(nb_bus_init);
- int bus_id_solver=0;
+ int bus_id_solver = 0;
for(int bus_id_me=0; bus_id_me < nb_bus_init; ++bus_id_me){
if(bus_status_[bus_id_me]){
// bus is connected
@@ -478,31 +686,55 @@ void GridModel::init_Ybus(Eigen::SparseMatrix & Ybus,
++bus_id_solver;
}
}
- const int nb_bus = static_cast(id_solver_to_me.size());
+ const int nb_bus_solver = static_cast(id_solver_to_me.size());
- Ybus = Eigen::SparseMatrix(nb_bus, nb_bus);
- Ybus.reserve(nb_bus + 2*powerlines_.nb() + 2*trafos_.nb());
+ Ybus = Eigen::SparseMatrix(nb_bus_solver, nb_bus_solver);
+ Ybus.reserve(nb_bus_solver + 2*powerlines_.nb() + 2*trafos_.nb());
}
-void GridModel::init_Sbus(CplxVect & Sbus,
- std::vector& id_me_to_solver,
- std::vector& id_solver_to_me,
- Eigen::VectorXi & slack_bus_id_solver){
-
- const int nb_bus = static_cast(id_solver_to_me.size());
- Sbus = CplxVect::Constant(nb_bus, 0.);
- slack_bus_id_solver = Eigen::VectorXi::Zero(slack_bus_id_.size());
+void GridModel::init_slack_bus(const CplxVect & Sbus,
+ const std::vector& id_me_to_solver,
+ const std::vector& id_solver_to_me,
+ const Eigen::VectorXi & slack_bus_id_me,
+ Eigen::VectorXi & slack_bus_id_solver){
+ // slack_bus_id_solver = Eigen::VectorXi::Zero(slack_bus_id_.size());
+ slack_bus_id_solver = Eigen::VectorXi::Constant(slack_bus_id_me.size(), _deactivated_bus_id);
size_t i = 0;
- for(auto el: slack_bus_id_) {
- slack_bus_id_solver(i) = id_me_to_solver[el];
+ // std::cout << "slack_bus_id_solver 2: ";
+ // for(auto el: slack_bus_id_solver) std::cout << el << ", ";
+ // std::cout << std::endl;
+ // std::cout << "id_me_to_solver: ";
+ // for(auto el: id_me_to_solver) std::cout << el << ", ";
+ // std::cout << std::endl;
+ // std::cout << "id_solver_to_me: ";
+ // for(auto el: id_solver_to_me) std::cout << el << ", ";
+ // std::cout << std::endl;
+
+ // for(auto el: slack_bus_id_) {
+ for(auto el: slack_bus_id_me) {
+ auto tmp = id_me_to_solver[el];
+ if(tmp == _deactivated_bus_id){
+ std::ostringstream exc_;
+ exc_ << "GridModel::init_Sbus: One of the slack bus is disconnected.";
+ exc_ << " You can check element ";
+ exc_ << el;
+ exc_ << ": [";
+ for(auto el2 : slack_bus_id_me) exc_ << el2 << ", ";
+ exc_ << "].";
+ throw std::out_of_range(exc_.str());
+ }
+ slack_bus_id_solver(i) = tmp;
++i;
}
+ // std::cout << "slack_bus_id_solver 3: ";
+ // for(auto el: slack_bus_id_solver) std::cout << el << ", ";
+ // std::cout << std::endl;
if(is_in_vect(_deactivated_bus_id, slack_bus_id_solver)){
// TODO improve error message with the gen_id
// TODO DEBUG MODE: only check that in debug mode
- throw std::runtime_error("One of the slack bus is disconnected !");
+ throw std::runtime_error("GridModel::init_Sbus: One of the slack bus is disconnected !");
}
}
void GridModel::fillYbus(Eigen::SparseMatrix & res, bool ac, const std::vector& id_me_to_solver){
@@ -512,9 +744,10 @@ void GridModel::fillYbus(Eigen::SparseMatrix & res, bool ac, const st
**/
// init the Ybus matrix
+ res.setZero(); // it should not be needed but might not hurt too much either.
std::vector > tripletList;
tripletList.reserve(bus_vn_kv_.size() + 4*powerlines_.nb() + 4*trafos_.nb() + shunts_.nb());
- powerlines_.fillYbus(tripletList, ac, id_me_to_solver, sn_mva_);
+ powerlines_.fillYbus(tripletList, ac, id_me_to_solver, sn_mva_); // TODO have a function to dispatch that to all type of elements
shunts_.fillYbus(tripletList, ac, id_me_to_solver, sn_mva_);
trafos_.fillYbus(tripletList, ac, id_me_to_solver, sn_mva_);
loads_.fillYbus(tripletList, ac, id_me_to_solver, sn_mva_);
@@ -522,14 +755,15 @@ void GridModel::fillYbus(Eigen::SparseMatrix & res, bool ac, const st
storages_.fillYbus(tripletList, ac, id_me_to_solver, sn_mva_);
generators_.fillYbus(tripletList, ac, id_me_to_solver, sn_mva_);
dc_lines_.fillYbus(tripletList, ac, id_me_to_solver, sn_mva_);
- res.setFromTriplets(tripletList.begin(), tripletList.end());
+ res.setFromTriplets(tripletList.begin(), tripletList.end()); // works because "The initial contents of *this is destroyed"
res.makeCompressed();
}
void GridModel::fillSbus_me(CplxVect & Sbus, bool ac, const std::vector& id_me_to_solver)
{
- // init the Sbus vector
- powerlines_.fillSbus(Sbus, id_me_to_solver, ac);
+ // init the Sbus
+ Sbus.array() = 0.; // reset to 0.
+ powerlines_.fillSbus(Sbus, id_me_to_solver, ac); // TODO have a function to dispatch that to all type of elements
trafos_.fillSbus(Sbus, id_me_to_solver, ac);
shunts_.fillSbus(Sbus, id_me_to_solver, ac);
loads_.fillSbus(Sbus, id_me_to_solver, ac);
@@ -543,21 +777,24 @@ void GridModel::fillSbus_me(CplxVect & Sbus, bool ac, const std::vector& id
}
void GridModel::fillpv_pq(const std::vector& id_me_to_solver,
- std::vector& id_solver_to_me,
- Eigen::VectorXi & slack_bus_id_solver)
+ const std::vector& id_solver_to_me,
+ const Eigen::VectorXi & slack_bus_id_solver,
+ const SolverControl & solver_control)
{
+ // Nothing to do if neither pv, nor pq nor the dimension of the problem has changed
+
// init pq and pv vector
// TODO remove the order here..., i could be faster in this piece of code (looping once through the buses)
const int nb_bus = static_cast(id_solver_to_me.size()); // number of bus in the solver!
std::vector bus_pq;
+ bus_pq.reserve(nb_bus);
std::vector bus_pv;
+ bus_pv.reserve(nb_bus);
std::vector has_bus_been_added(nb_bus, false);
- // std::cout << "id_me_to_solver.size(): " << id_me_to_solver.size() << std::endl;
-
bus_pv_ = Eigen::VectorXi();
bus_pq_ = Eigen::VectorXi();
- powerlines_.fillpv(bus_pv, has_bus_been_added, slack_bus_id_solver, id_me_to_solver);
+ powerlines_.fillpv(bus_pv, has_bus_been_added, slack_bus_id_solver, id_me_to_solver); // TODO have a function to dispatch that to all type of elements
shunts_.fillpv(bus_pv, has_bus_been_added, slack_bus_id_solver, id_me_to_solver);
trafos_.fillpv(bus_pv, has_bus_been_added, slack_bus_id_solver, id_me_to_solver);
loads_.fillpv(bus_pv, has_bus_been_added, slack_bus_id_solver, id_me_to_solver);
@@ -572,8 +809,8 @@ void GridModel::fillpv_pq(const std::vector& id_me_to_solver,
bus_pq.push_back(bus_id);
has_bus_been_added[bus_id] = true; // don't add it a second time
}
- bus_pv_ = Eigen::Map(bus_pv.data(), bus_pv.size());
- bus_pq_ = Eigen::Map(bus_pq.data(), bus_pq.size());
+ bus_pv_ = Eigen::VectorXi::Map(&bus_pv[0], bus_pv.size());
+ bus_pq_ = Eigen::VectorXi::Map(&bus_pq[0], bus_pq.size());
}
void GridModel::compute_results(bool ac){
// retrieve results from powerflow
@@ -583,7 +820,7 @@ void GridModel::compute_results(bool ac){
const std::vector & id_me_to_solver = ac ? id_me_to_ac_solver_ : id_me_to_dc_solver_;
// for powerlines
- powerlines_.compute_results(Va, Vm, V, id_me_to_solver, bus_vn_kv_, sn_mva_, ac);
+ powerlines_.compute_results(Va, Vm, V, id_me_to_solver, bus_vn_kv_, sn_mva_, ac); // TODO have a function to dispatch that to all type of elements
// for trafo
trafos_.compute_results(Va, Vm, V, id_me_to_solver, bus_vn_kv_, sn_mva_, ac);
// for loads
@@ -601,12 +838,12 @@ void GridModel::compute_results(bool ac){
//handle_slack_bus active power
CplxVect mismatch; // power mismatch at each bus (SOLVER BUS !!!)
- RealVect ractive_mismatch; // not used in dc mode (DO NOT ATTEMPT TO USE IT THERE)
+ RealVect reactive_mismatch; // not used in dc mode (DO NOT ATTEMPT TO USE IT THERE)
RealVect active_mismatch;
if(ac){
// In AC mode i am not forced to run through all the grid
auto tmp = (Ybus_ac_ * V).conjugate();
- mismatch = V.array() * tmp.array() - Sbus_.array();
+ mismatch = V.array() * tmp.array() - acSbus_.array();
active_mismatch = mismatch.real() * sn_mva_;
} else{
active_mismatch = RealVect::Zero(V.size());
@@ -615,20 +852,23 @@ void GridModel::compute_results(bool ac){
// to split among the contributing generators) so it's possible to "mess with" Sbus
// for such purpose
const auto id_slack = slack_bus_id_dc_solver_(0);
- active_mismatch(id_slack) = -Sbus_.real().sum() * sn_mva_;
+ active_mismatch(id_slack) = -dcSbus_.real().sum() * sn_mva_;
}
+ // for(auto el: active_mismatch) std::cout << el << ", ";
+ // std::cout << std::endl;
generators_.set_p_slack(active_mismatch, id_me_to_solver);
- if(ac) ractive_mismatch = mismatch.imag() * sn_mva_;
+ if(ac) reactive_mismatch = mismatch.imag() * sn_mva_;
// mainly to initialize the Q value of the generators in dc (just fill it with 0.)
- generators_.set_q(ractive_mismatch, id_me_to_solver, ac,
+ generators_.set_q(reactive_mismatch, id_me_to_solver, ac,
total_gen_per_bus_, total_q_min_per_bus_, total_q_max_per_bus_);
- dc_lines_.set_q(ractive_mismatch, id_me_to_solver, ac,
+ dc_lines_.set_q(reactive_mismatch, id_me_to_solver, ac,
total_gen_per_bus_, total_q_min_per_bus_, total_q_max_per_bus_);
}
void GridModel::reset_results(){
- powerlines_.reset_results();
+ // std::cout << "reset_results\n";
+ powerlines_.reset_results(); // TODO have a function to dispatch that to all type of elements
shunts_.reset_results();
trafos_.reset_results();
loads_.reset_results();
@@ -647,6 +887,8 @@ CplxVect GridModel::dc_pf(const CplxVect & Vinit,
// the idea is to "mess" with the Sbus beforehand to split the "losses"
// ie fake the action of generators to adjust Sbus such that sum(Sbus) = 0
// and the slack contribution factors are met.
+ auto timer = CustTimer();
+
const int nb_bus = static_cast(bus_vn_kv_.size());
if(Vinit.size() != nb_bus){
//TODO DEBUG MODE:
@@ -659,22 +901,61 @@ CplxVect GridModel::dc_pf(const CplxVect & Vinit,
bool conv = false;
CplxVect res = CplxVect();
+ reset_results(); // reset the results
+
// pre process the data to define a proper jacobian matrix, the proper voltage vector etc.
bool is_ac = false;
- bool reset_solver = topo_changed_; // I reset the solver only if the topology change
- CplxVect V = pre_process_solver(Vinit, Ybus_dc_,
- id_me_to_dc_solver_, id_dc_solver_to_me_, slack_bus_id_dc_solver_,
- is_ac, reset_solver);
-
+ CplxVect V = pre_process_solver(Vinit,
+ dcSbus_,
+ Ybus_dc_,
+ id_me_to_dc_solver_,
+ id_dc_solver_to_me_,
+ slack_bus_id_dc_me_,
+ slack_bus_id_dc_solver_,
+ is_ac,
+ solver_control_);
+ // std::cout << "\tafter pre proces (dc)\n";
// start the solver
- slack_weights_ = generators_.get_slack_weights(Ybus_dc_.rows(), id_me_to_dc_solver_);
- conv = _dc_solver.compute_pf(Ybus_dc_, V, Sbus_, slack_bus_id_dc_solver_, slack_weights_, bus_pv_, bus_pq_, max_iter, tol);
-
+ if(solver_control_.need_reset_solver() ||
+ solver_control_.has_dimension_changed() ||
+ solver_control_.has_slack_participate_changed() ||
+ solver_control_.has_pv_changed() ||
+ solver_control_.has_slack_weight_changed()){
+ // TODO smarter solver: this is done both in ac and in dc !
+ // std::cout << "\tget_slack_weights" << std::endl;
+ slack_weights_ = generators_.get_slack_weights(Ybus_dc_.rows(), id_me_to_dc_solver_);
+ }
+ // std::cout << "V (init to dc pf)\n";
+ // for(auto el: V) std::cout << el << ", ";
+ // std::cout << std::endl;
+ // std::cout << "dcSbus (init to dc pf)\n";
+ // for(auto el: dcSbus_) std::cout << el << ", ";
+ // std::cout << std::endl;
+ // std::cout << "\tbefore compute dc pf" << std::endl;
+ conv = _dc_solver.compute_pf(Ybus_dc_, V, dcSbus_, slack_bus_id_dc_solver_, slack_weights_, bus_pv_, bus_pq_, max_iter, tol);
+ // std::cout << "\tprocess_results (dc) \n";
// store results (fase -> because I am in dc mode)
- process_results(conv, res, Vinit, false, id_me_to_dc_solver_);
+ process_results(conv, res, Vinit, is_ac, id_me_to_dc_solver_);
+ timer_last_dc_pf_ = timer.duration();
return res;
}
+RealMat GridModel::get_ptdf(){
+ if(Ybus_dc_.size() == 0){
+ throw std::runtime_error("GridModel::get_ptdf: Cannot get the ptdf without having first computed a DC powerflow.");
+ }
+ return _dc_solver.get_ptdf(Ybus_dc_);
+}
+
+Eigen::SparseMatrix GridModel::get_Bf(){
+ if(Ybus_dc_.size() == 0){
+ throw std::runtime_error("GridModel::get_Bf: Cannot get the Bf matrix without having first computed a DC powerflow.");
+ }
+ Eigen::SparseMatrix Bf;
+ fillBf_for_PTDF(Bf);
+ return Bf;
+}
+
/**
Retrieve the number of connected buses
**/
@@ -696,7 +977,7 @@ void GridModel::add_gen_slackbus(int gen_id, real_type weight){
exc_ << gen_id;
throw std::runtime_error(exc_.str());
}
- if(gen_id > generators_.nb())
+ if(gen_id >= generators_.nb())
{
std::ostringstream exc_;
exc_ << "GridModel::add_gen_slackbus: There are only " << generators_.nb() << " generators on the grid. ";
@@ -708,7 +989,7 @@ void GridModel::add_gen_slackbus(int gen_id, real_type weight){
exc_ << "GridModel::add_gen_slackbus: please enter a valid weight for the slack bus (> 0.)";
throw std::runtime_error(exc_.str());
}
- generators_.add_slackbus(gen_id, weight);
+ generators_.add_slackbus(gen_id, weight, solver_control_);
}
void GridModel::remove_gen_slackbus(int gen_id){
@@ -720,7 +1001,7 @@ void GridModel::remove_gen_slackbus(int gen_id){
exc_ << gen_id;
throw std::runtime_error(exc_.str());
}
- if(gen_id > generators_.nb())
+ if(gen_id >= generators_.nb())
{
// TODO DEBUG MODE: only check when in debug mode
std::ostringstream exc_;
@@ -728,7 +1009,7 @@ void GridModel::remove_gen_slackbus(int gen_id){
exc_ << "Generator with id " << gen_id << " does not exist and can't be the slack bus";
throw std::runtime_error(exc_.str());
}
- generators_.remove_slackbus(gen_id);
+ generators_.remove_slackbus(gen_id, solver_control_);
}
/** GRID2OP SPECIFIC REPRESENTATION **/
@@ -789,9 +1070,6 @@ void GridModel::update_storages_p(Eigen::Ref > has_changed,
Eigen::Ref > new_values)
{
- const int nb_bus = static_cast(bus_status_.size());
- for(int i = 0; i < nb_bus; ++i) bus_status_[i] = false;
-
update_topo_generic(has_changed, new_values,
load_pos_topo_vect_, load_to_subid_,
&GridModel::reactivate_load,
@@ -837,6 +1115,19 @@ void GridModel::update_topo(Eigen::Ref(bus_status_.size());
+ for(int i = 0; i < nb_bus; ++i) bus_status_[i] = false;
+
+ powerlines_.update_bus_status(bus_status_); // TODO have a function to dispatch that to all type of elements
+ shunts_.update_bus_status(bus_status_);
+ trafos_.update_bus_status(bus_status_);
+ loads_.update_bus_status(bus_status_);
+ sgens_.update_bus_status(bus_status_);
+ storages_.update_bus_status(bus_status_);
+ generators_.update_bus_status(bus_status_);
+ dc_lines_.update_bus_status(bus_status_);
}
// for FDPF (implementation of the alg 2 method FDBX (FDXB will follow) // TODO FDPF
@@ -855,7 +1146,7 @@ void GridModel::fillBp_Bpp(Eigen::SparseMatrix & Bp,
tripletList_Bp.reserve(bus_vn_kv_.size() + 4 * powerlines_.nb() + 4 * trafos_.nb() + shunts_.nb());
tripletList_Bpp.reserve(bus_vn_kv_.size() + 4 * powerlines_.nb() + 4 * trafos_.nb() + shunts_.nb());
// run through the grid and get the parameters to fill them
- powerlines_.fillBp_Bpp(tripletList_Bp, tripletList_Bpp, id_me_to_ac_solver_, sn_mva_, xb_or_bx);
+ powerlines_.fillBp_Bpp(tripletList_Bp, tripletList_Bpp, id_me_to_ac_solver_, sn_mva_, xb_or_bx); // TODO have a function to dispatch that to all type of elements
shunts_.fillBp_Bpp(tripletList_Bp, tripletList_Bpp, id_me_to_ac_solver_, sn_mva_, xb_or_bx);
trafos_.fillBp_Bpp(tripletList_Bp, tripletList_Bpp, id_me_to_ac_solver_, sn_mva_, xb_or_bx);
loads_.fillBp_Bpp(tripletList_Bp, tripletList_Bpp, id_me_to_ac_solver_, sn_mva_, xb_or_bx);
@@ -869,3 +1160,149 @@ void GridModel::fillBp_Bpp(Eigen::SparseMatrix & Bp,
Bpp.setFromTriplets(tripletList_Bpp.begin(), tripletList_Bpp.end());
Bpp.makeCompressed();
}
+
+
+void GridModel::fillBf_for_PTDF(Eigen::SparseMatrix & Bf, bool transpose) const
+{
+ const int nb_bus_solver = static_cast(id_me_to_dc_solver_.size());
+ // TODO DEBUG MODE
+ if(nb_bus_solver == 0) throw std::runtime_error("GridModel::fillBf_for_PTDF: it appears no DC powerflow has run on your grid.");
+
+ // TODO PTDF: nb_line, nb_bus or nb_branch, nb_bus ???
+ // TODO PTDF: if we don't have nb_branch we need a converter line_id => branch
+ if(transpose){
+ Bf = Eigen::SparseMatrix(nb_bus_solver, powerlines_.nb() + trafos_.nb());
+ }else{
+ Bf = Eigen::SparseMatrix(powerlines_.nb() + trafos_.nb(), nb_bus_solver);
+ }
+ std::vector > tripletList;
+ tripletList.reserve(bus_vn_kv_.size() + 2 * powerlines_.nb() + 2 * trafos_.nb());
+
+ powerlines_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb(), transpose); // TODO have a function to dispatch that to all type of elements
+ shunts_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb(), transpose);
+ trafos_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb(), transpose);
+ loads_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb(), transpose);
+ sgens_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb(), transpose);
+ storages_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb(), transpose);
+ generators_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb(), transpose);
+ dc_lines_.fillBf_for_PTDF(tripletList, id_me_to_dc_solver_, sn_mva_, powerlines_.nb(), transpose);
+
+ Bf.setFromTriplets(tripletList.begin(), tripletList.end());
+ Bf.makeCompressed();
+}
+
+// returns only the gen_id with the highest p that is connected to this bus !
+// returns bus_id, gen_bus_id
+std::tuple GridModel::assign_slack_to_most_connected(){
+ auto res = std::tuple(-1, -1);
+ int res_bus_id = -1;
+ int res_gen_id = -1;
+ int max_line = -1;
+ const auto nb_busbars = bus_status_.size();
+ std::vector gen_p_per_bus(nb_busbars, 0.);
+ std::vector nb_line_end_per_bus(nb_busbars, 0);
+
+ // computes the total amount of power produce at each nodes
+ powerlines_.gen_p_per_bus(gen_p_per_bus); // TODO have a function to dispatch that to all type of elements
+ shunts_.gen_p_per_bus(gen_p_per_bus);
+ trafos_.gen_p_per_bus(gen_p_per_bus);
+ loads_.gen_p_per_bus(gen_p_per_bus);
+ sgens_.gen_p_per_bus(gen_p_per_bus);
+ storages_.gen_p_per_bus(gen_p_per_bus);
+ generators_.gen_p_per_bus(gen_p_per_bus);
+ dc_lines_.gen_p_per_bus(gen_p_per_bus);
+
+ // computes the total number of "neighbors" (extremity of connected powerlines and trafo, not real neighbors)
+ powerlines_.nb_line_end(nb_line_end_per_bus); // TODO have a function to dispatch that to all type of elements
+ shunts_.nb_line_end(nb_line_end_per_bus);
+ trafos_.nb_line_end(nb_line_end_per_bus);
+ loads_.nb_line_end(nb_line_end_per_bus);
+ sgens_.nb_line_end(nb_line_end_per_bus);
+ storages_.nb_line_end(nb_line_end_per_bus);
+ generators_.nb_line_end(nb_line_end_per_bus);
+ dc_lines_.nb_line_end(nb_line_end_per_bus);
+
+ // now find the most connected buses
+ for(unsigned int bus_id = 0; bus_id < nb_busbars; ++bus_id)
+ {
+ const auto & nb_lines_this = nb_line_end_per_bus[bus_id];
+ if((nb_lines_this > max_line) && (gen_p_per_bus[bus_id] > 0.)){
+ res_bus_id = bus_id;
+ max_line = nb_lines_this;
+ }
+ }
+ // TODO DEBUG MODE
+ if(res_bus_id == -1) throw std::runtime_error("GridModel::assign_slack_to_most_connected: impossible to find anything connected to a node.");
+ std::get<0>(res) = res_bus_id;
+
+ // and reset the slack bus
+ generators_.remove_all_slackbus();
+ res_gen_id = generators_.assign_slack_bus(res_bus_id, gen_p_per_bus, solver_control_);
+ std::get<1>(res) = res_gen_id;
+ // slack_bus_id_ = std::vector();
+ slack_bus_id_ac_solver_ = Eigen::VectorXi();
+ slack_bus_id_dc_solver_ = Eigen::VectorXi();
+ slack_weights_ = RealVect();
+ return res;
+}
+
+// TODO DC LINE: one side might be in the connected comp and not the other !
+void GridModel::consider_only_main_component(){
+ const auto & slack_buses_id = generators_.get_slack_bus_id();
+
+ // TODO DEBUG MODE
+ if(slack_buses_id.size() == 0) throw std::runtime_error("GridModel::consider_only_main_component: no slack is defined on your grid. This function cannot be used.");
+
+ // build the graph
+ const auto nb_busbars = bus_status_.size();
+ std::vector > tripletList;
+ tripletList.reserve(2 * powerlines_.nb() + 2 * trafos_.nb());
+ powerlines_.get_graph(tripletList); // TODO have a function to dispatch that to all type of elements
+ shunts_.get_graph(tripletList);
+ trafos_.get_graph(tripletList);
+ loads_.get_graph(tripletList);
+ sgens_.get_graph(tripletList);
+ storages_.get_graph(tripletList);
+ generators_.get_graph(tripletList);
+ dc_lines_.get_graph(tripletList);
+ Eigen::SparseMatrix graph = Eigen::SparseMatrix(nb_busbars, nb_busbars);
+ graph.setFromTriplets(tripletList.begin(), tripletList.end());
+ graph.makeCompressed();
+
+ // find the connected buses
+ // TODO copy paste from SecurityAnalysis
+ std::queue neighborhood;
+ for(const auto & el : slack_buses_id) neighborhood.push(el);
+ if(neighborhood.empty()){
+ throw std::runtime_error("There is no slack buses defined in your grid. You cannot isolate the main component.");
+ }
+ std::vector visited(nb_busbars, false);
+ std::vector already_added(nb_busbars, false);
+ while (true)
+ {
+ const Eigen::Index col_id = neighborhood.front();
+ neighborhood.pop();
+ visited[col_id] = true;
+ for (Eigen::SparseMatrix::InnerIterator it(graph, col_id); it; ++it)
+ {
+ // add in the queue all my neighbor (if the coefficient is big enough)
+ if(!visited[it.row()] && !already_added[it.row()]){ // && abs(it.value()) > 1e-8
+ neighborhood.push(it.row());
+ already_added[it.row()] = true;
+ }
+ }
+ if(neighborhood.empty()) break;
+ }
+
+ // disconnected elements not in main component
+ powerlines_.disconnect_if_not_in_main_component(visited);
+ shunts_.disconnect_if_not_in_main_component(visited);
+ trafos_.disconnect_if_not_in_main_component(visited);
+ loads_.disconnect_if_not_in_main_component(visited);
+ sgens_.disconnect_if_not_in_main_component(visited);
+ storages_.disconnect_if_not_in_main_component(visited);
+ generators_.disconnect_if_not_in_main_component(visited);
+ dc_lines_.disconnect_if_not_in_main_component(visited);
+ // and finally deal with the buses
+ init_bus_status();
+}
\ No newline at end of file
diff --git a/src/GridModel.h b/src/GridModel.h
index a32e0920..7a4b69c5 100644
--- a/src/GridModel.h
+++ b/src/GridModel.h
@@ -27,14 +27,14 @@
#include "Eigen/SparseLU"
// import data classes
-#include "DataGeneric.h"
-#include "DataLine.h"
-#include "DataShunt.h"
-#include "DataTrafo.h"
-#include "DataLoad.h"
-#include "DataGen.h"
-#include "DataSGen.h"
-#include "DataDCLine.h"
+#include "element_container/GenericContainer.h"
+#include "element_container/LineContainer.h"
+#include "element_container/ShuntContainer.h"
+#include "element_container/TrafoContainer.h"
+#include "element_container/LoadContainer.h"
+#include "element_container/GeneratorContainer.h"
+#include "element_container/SGenContainer.h"
+#include "element_container/DCLineContainer.h"
// import newton raphson solvers using different linear algebra solvers
#include "ChooseSolver.h"
@@ -42,48 +42,82 @@
// enum class SolverType;
//TODO implement a BFS check to make sure the Ymatrix is "connected" [one single component]
-class GridModel : public DataGeneric
+class GridModel : public GenericContainer
{
- public: // can be modified python side
- IntVect _ls_to_pp; // for converter from bus in lightsim2grid index to bus in pandapower index
-
public:
+ typedef Eigen::Array IntVectRowMaj;
+
typedef std::tuple<
int, // version major
int, // version medium
int, // version minor
- std::vector, // ls_to_pp
+ std::vector, // ls_to_orig
real_type, // init_vm_pu
real_type, //sn_mva
std::vector, // bus_vn_kv
std::vector, // bus_status
// powerlines
- DataLine::StateRes ,
+ LineContainer::StateRes ,
// shunts
- DataShunt::StateRes,
+ ShuntContainer::StateRes,
// trafos
- DataTrafo::StateRes,
+ TrafoContainer::StateRes,
// gens
- DataGen::StateRes,
+ GeneratorContainer::StateRes,
// loads
- DataLoad::StateRes,
+ LoadContainer::StateRes,
// static generators
- DataSGen::StateRes,
+ SGenContainer::StateRes,
// storage units
- DataLoad::StateRes,
+ LoadContainer::StateRes,
//dc lines
- DataDCLine::StateRes
+ DCLineContainer::StateRes,
+ // grid2op specific
+ int, // n_sub
+ int, // max_nb_bus_per_sub
+ std::vector, // load_pos_topo_vect_
+ std::vector,
+ std::vector,
+ std::vector,
+ std::vector,
+ std::vector,
+ std::vector,
+ std::vector, // load_to_subid_
+ std::vector,
+ std::vector,
+ std::vector,
+ std::vector,
+ std::vector,
+ std::vector
> StateRes;
- GridModel():need_reset_(true), topo_changed_(true), compute_results_(true), init_vm_pu_(1.04), sn_mva_(1.0){
+ GridModel():
+ timer_last_ac_pf_(0.),
+ timer_last_dc_pf_(0.),
+ solver_control_(),
+ compute_results_(true),
+ init_vm_pu_(1.04),
+ sn_mva_(1.0),
+ max_nb_bus_per_sub_(2){
+ _solver.change_solver(SolverType::SparseLU);
_dc_solver.change_solver(SolverType::DC);
_solver.set_gridmodel(this);
+ _dc_solver.set_gridmodel(this);
+ solver_control_.tell_all_changed();
}
GridModel(const GridModel & other);
GridModel copy() const{
GridModel res(*this);
return res;
}
+
+ void set_ls_to_orig(const IntVect & ls_to_orig); // set both _ls_to_orig and _orig_to_ls
+ void set_orig_to_ls(const IntVect & orig_to_ls); // set both _orig_to_ls and _ls_to_orig
+ const IntVect & get_ls_to_orig(void) const {return _ls_to_orig;}
+ const IntVect & get_orig_to_ls(void) const {return _orig_to_ls;}
+ double timer_last_ac_pf() const {return timer_last_ac_pf_;}
+ double timer_last_dc_pf() const {return timer_last_dc_pf_;}
+
Eigen::Index total_bus() const {return bus_vn_kv_.size();}
const std::vector & id_me_to_ac_solver() const {return id_me_to_ac_solver_;}
const std::vector & id_ac_solver_to_me() const {return id_ac_solver_to_me_;}
@@ -91,25 +125,26 @@ class GridModel : public DataGeneric
const std::vector & id_dc_solver_to_me() const {return id_dc_solver_to_me_;}
// retrieve the underlying data (raw class)
- const DataGen & get_generators_as_data() const {return generators_;}
- void turnedoff_no_pv(){generators_.turnedoff_no_pv();} // turned off generators are not pv
- void turnedoff_pv(){generators_.turnedoff_pv();} // turned off generators are pv
+ const GeneratorContainer & get_generators_as_data() const {return generators_;}
+ void turnedoff_no_pv(){generators_.turnedoff_no_pv(solver_control_);} // turned off generators are not pv
+ void turnedoff_pv(){generators_.turnedoff_pv(solver_control_);} // turned off generators are pv
bool get_turnedoff_gen_pv() {return generators_.get_turnedoff_gen_pv();}
void update_slack_weights(Eigen::Ref > could_be_slack){
- generators_.update_slack_weights(could_be_slack, topo_changed_);
+ generators_.update_slack_weights(could_be_slack, solver_control_);
}
- const DataSGen & get_static_generators_as_data() const {return sgens_;}
- const DataLoad & get_loads_as_data() const {return loads_;}
- const DataLine & get_powerlines_as_data() const {return powerlines_;}
- const DataTrafo & get_trafos_as_data() const {return trafos_;}
- const DataDCLine & get_dclines_as_data() const {return dc_lines_;}
+ const SGenContainer & get_static_generators_as_data() const {return sgens_;}
+ const LoadContainer & get_loads_as_data() const {return loads_;}
+ const LineContainer & get_powerlines_as_data() const {return powerlines_;}
+ const TrafoContainer & get_trafos_as_data() const {return trafos_;}
+ const DCLineContainer & get_dclines_as_data() const {return dc_lines_;}
Eigen::Ref get_bus_vn_kv() const {return bus_vn_kv_;}
+ std::tuple assign_slack_to_most_connected();
+ void consider_only_main_component();
// solver "control"
void change_solver(const SolverType & type){
- need_reset_ = true;
- topo_changed_ = true;
+ solver_control_.tell_all_changed();
if(_solver.is_dc(type)) _dc_solver.change_solver(type);
else _solver.change_solver(type);
}
@@ -159,7 +194,6 @@ class GridModel : public DataGeneric
const RealVect & trafo_x,
const CplxVect & trafo_b,
const RealVect & trafo_tap_step_pct,
- // const RealVect & trafo_tap_step_degree,
const RealVect & trafo_tap_pos,
const RealVect & trafo_shift_degree,
const std::vector & trafo_tap_hv, // is tap on high voltage (true) or low voltate
@@ -176,6 +210,16 @@ class GridModel : public DataGeneric
const Eigen::VectorXi & generators_bus_id){
generators_.init(generators_p, generators_v, generators_min_q, generators_max_q, generators_bus_id);
}
+ void init_generators_full(const RealVect & generators_p,
+ const RealVect & generators_v,
+ const RealVect & generators_q,
+ const std::vector & voltage_regulator_on,
+ const RealVect & generators_min_q,
+ const RealVect & generators_max_q,
+ const Eigen::VectorXi & generators_bus_id){
+ generators_.init_full(generators_p, generators_v, generators_q, voltage_regulator_on,
+ generators_min_q, generators_max_q, generators_bus_id);
+ }
void init_loads(const RealVect & loads_p,
const RealVect & loads_q,
const Eigen::VectorXi & loads_bus_id){
@@ -210,6 +254,18 @@ class GridModel : public DataGeneric
loss_percent, loss_mw, vm_or_pu, vm_ex_pu,
min_q_or, max_q_or, min_q_ex, max_q_ex);
}
+ void init_bus_status(){
+ const int nb_bus = static_cast(bus_status_.size());
+ for(int i = 0; i < nb_bus; ++i) bus_status_[i] = false;
+ powerlines_.reconnect_connected_buses(bus_status_);
+ shunts_.reconnect_connected_buses(bus_status_);
+ trafos_.reconnect_connected_buses(bus_status_);
+ generators_.reconnect_connected_buses(bus_status_);
+ loads_.reconnect_connected_buses(bus_status_);
+ sgens_.reconnect_connected_buses(bus_status_);
+ storages_.reconnect_connected_buses(bus_status_);
+ dc_lines_.reconnect_connected_buses(bus_status_);
+ }
void add_gen_slackbus(int gen_id, real_type weight);
void remove_gen_slackbus(int gen_id);
@@ -224,7 +280,7 @@ class GridModel : public DataGeneric
unsigned int size_th = 6;
if (my_state.size() != size_th)
{
- std::cout << "LightSim::GridModel state size " << my_state.size() << " instead of "<< size_th << std::endl;
+ // std::cout << "LightSim::GridModel state size " << my_state.size() << " instead of "<< size_th << std::endl;
// TODO more explicit error message
throw std::runtime_error("Invalid state when loading LightSim::GridModel");
}
@@ -232,14 +288,22 @@ class GridModel : public DataGeneric
//powerflows
// control the need to refactorize the topology
- void unset_topo_changed(){topo_changed_ = false;} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed.
- void tell_topo_changed(){topo_changed_ = true;} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed.
+ void unset_changes(){
+ solver_control_.tell_none_changed();
+ } //should be used after the powerflow as run, so some vectors will not be recomputed if not needed.
+ void tell_recompute_ybus(){solver_control_.tell_recompute_ybus();} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed.
+ void tell_recompute_sbus(){solver_control_.tell_recompute_sbus();} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed.
+ void tell_solver_need_reset(){solver_control_.tell_solver_need_reset();} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed.
+ void tell_ybus_change_sparsity_pattern(){solver_control_.tell_ybus_change_sparsity_pattern();} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed.
+ const SolverControl & get_solver_control() const {return solver_control_;}
// dc powerflow
CplxVect dc_pf(const CplxVect & Vinit,
int max_iter, // not used for DC
real_type tol // not used for DC
);
+ RealMat get_ptdf();
+ Eigen::SparseMatrix get_Bf();
// ac powerflow
CplxVect ac_pf(const CplxVect & Vinit,
@@ -251,75 +315,127 @@ class GridModel : public DataGeneric
// deactivate a bus. Be careful, if a bus is deactivated, but an element is
//still connected to it, it will throw an exception
- void deactivate_bus(int bus_id) {_deactivate(bus_id, bus_status_, topo_changed_); }
+ void deactivate_bus(int bus_id) {
+ if(bus_status_[bus_id]){
+ // bus was connected, dim of matrix change
+ solver_control_.need_reset_solver();
+ solver_control_.need_recompute_sbus();
+ solver_control_.need_recompute_ybus();
+ solver_control_.ybus_change_sparsity_pattern();
+ _deactivate(bus_id, bus_status_);
+ }
+ }
// if a bus is connected, but isolated, it will make the powerflow diverge
- void reactivate_bus(int bus_id) {_reactivate(bus_id, bus_status_, topo_changed_); }
+ void reactivate_bus(int bus_id) {
+ if(!bus_status_[bus_id]){
+ // bus was not connected, dim of matrix change
+ solver_control_.need_reset_solver();
+ solver_control_.need_recompute_sbus();
+ solver_control_.need_recompute_ybus();
+ solver_control_.ybus_change_sparsity_pattern();
+ _reactivate(bus_id, bus_status_);
+ }
+ }
int nb_bus() const; // number of activated buses
Eigen::Index nb_powerline() const {return powerlines_.nb();}
Eigen::Index nb_trafo() const {return trafos_.nb();}
// read only data accessor
- const DataLine & get_lines() const {return powerlines_;}
- const DataDCLine & get_dclines() const {return dc_lines_;}
- const DataTrafo & get_trafos() const {return trafos_;}
- const DataGen & get_generators() const {return generators_;}
- const DataLoad & get_loads() const {return loads_;}
- const DataLoad & get_storages() const {return storages_;}
- const DataSGen & get_static_generators() const {return sgens_;}
- const DataShunt & get_shunts() const {return shunts_;}
+ const LineContainer & get_lines() const {return powerlines_;}
+ const DCLineContainer & get_dclines() const {return dc_lines_;}
+ const TrafoContainer & get_trafos() const {return trafos_;}
+ const GeneratorContainer & get_generators() const {return generators_;}
+ const LoadContainer & get_loads() const {return loads_;}
+ const LoadContainer & get_storages() const {return storages_;}
+ const SGenContainer & get_static_generators() const {return sgens_;}
+ const ShuntContainer & get_shunts() const {return shunts_;}
+ const std::vector & get_bus_status() const {return bus_status_;}
+
+ void set_line_names(const std::vector & names){
+ GenericContainer::check_size(names, powerlines_.nb(), "set_line_names");
+ powerlines_.set_names(names);
+ }
+ void set_dcline_names(const std::vector & names){
+ GenericContainer::check_size(names, dc_lines_.nb(), "set_dcline_names");
+ dc_lines_.set_names(names);
+ }
+ void set_trafo_names(const std::vector & names){
+ GenericContainer::check_size(names, trafos_.nb(), "set_trafo_names");
+ trafos_.set_names(names);
+ }
+ void set_gen_names(const std::vector & names){
+ GenericContainer::check_size(names, generators_.nb(), "set_gen_names");
+ generators_.set_names(names);
+ }
+ void set_load_names(const std::vector & names){
+ GenericContainer::check_size(names, loads_.nb(), "set_load_names");
+ loads_.set_names(names);
+ }
+ void set_storage_names(const std::vector & names){
+ GenericContainer::check_size(names, storages_.nb(), "set_storage_names");
+ storages_.set_names(names);
+ }
+ void set_sgen_names(const std::vector & names){
+ GenericContainer::check_size(names, sgens_.nb(), "set_sgen_names");
+ sgens_.set_names(names);
+ }
+ void set_shunt_names(const std::vector & names){
+ GenericContainer::check_size(names, shunts_.nb(), "set_shunt_names");
+ shunts_.set_names(names);
+ }
//deactivate a powerline (disconnect it)
- void deactivate_powerline(int powerline_id) {powerlines_.deactivate(powerline_id, topo_changed_); }
- void reactivate_powerline(int powerline_id) {powerlines_.reactivate(powerline_id, topo_changed_); }
- void change_bus_powerline_or(int powerline_id, int new_bus_id) {powerlines_.change_bus_or(powerline_id, new_bus_id, topo_changed_, static_cast(bus_vn_kv_.size())); }
- void change_bus_powerline_ex(int powerline_id, int new_bus_id) {powerlines_.change_bus_ex(powerline_id, new_bus_id, topo_changed_, static_cast(bus_vn_kv_.size())); }
+ void deactivate_powerline(int powerline_id) {powerlines_.deactivate(powerline_id, solver_control_); }
+ void reactivate_powerline(int powerline_id) {powerlines_.reactivate(powerline_id, solver_control_); }
+ void change_bus_powerline_or(int powerline_id, int new_bus_id) {powerlines_.change_bus_or(powerline_id, new_bus_id, solver_control_, static_cast(bus_vn_kv_.size())); }
+ void change_bus_powerline_ex(int powerline_id, int new_bus_id) {powerlines_.change_bus_ex(powerline_id, new_bus_id, solver_control_, static_cast(bus_vn_kv_.size())); }
int get_bus_powerline_or(int powerline_id) {return powerlines_.get_bus_or(powerline_id);}
int get_bus_powerline_ex(int powerline_id) {return powerlines_.get_bus_ex(powerline_id);}
//deactivate trafo
- void deactivate_trafo(int trafo_id) {trafos_.deactivate(trafo_id, topo_changed_); }
- void reactivate_trafo(int trafo_id) {trafos_.reactivate(trafo_id, topo_changed_); }
- void change_bus_trafo_hv(int trafo_id, int new_bus_id) {trafos_.change_bus_hv(trafo_id, new_bus_id, topo_changed_, static_cast(bus_vn_kv_.size())); }
- void change_bus_trafo_lv(int trafo_id, int new_bus_id) {trafos_.change_bus_lv(trafo_id, new_bus_id, topo_changed_, static_cast(bus_vn_kv_.size())); }
+ void deactivate_trafo(int trafo_id) {trafos_.deactivate(trafo_id, solver_control_); }
+ void reactivate_trafo(int trafo_id) {trafos_.reactivate(trafo_id, solver_control_); }
+ void change_bus_trafo_hv(int trafo_id, int new_bus_id) {trafos_.change_bus_hv(trafo_id, new_bus_id, solver_control_, static_cast(bus_vn_kv_.size())); }
+ void change_bus_trafo_lv(int trafo_id, int new_bus_id) {trafos_.change_bus_lv(trafo_id, new_bus_id, solver_control_, static_cast(bus_vn_kv_.size())); }
int get_bus_trafo_hv(int trafo_id) {return trafos_.get_bus_hv(trafo_id);}
int get_bus_trafo_lv(int trafo_id) {return trafos_.get_bus_lv(trafo_id);}
//load
- void deactivate_load(int load_id) {loads_.deactivate(load_id, topo_changed_); }
- void reactivate_load(int load_id) {loads_.reactivate(load_id, topo_changed_); }
- void change_bus_load(int load_id, int new_bus_id) {loads_.change_bus(load_id, new_bus_id, topo_changed_, static_cast(bus_vn_kv_.size())); }
- void change_p_load(int load_id, real_type new_p) {loads_.change_p(load_id, new_p, topo_changed_); }
- void change_q_load(int load_id, real_type new_q) {loads_.change_q(load_id, new_q, topo_changed_); }
+ void deactivate_load(int load_id) {loads_.deactivate(load_id, solver_control_); }
+ void reactivate_load(int load_id) {loads_.reactivate(load_id, solver_control_); }
+ void change_bus_load(int load_id, int new_bus_id) {loads_.change_bus(load_id, new_bus_id, solver_control_, static_cast(bus_vn_kv_.size())); }
+ void change_p_load(int load_id, real_type new_p) {loads_.change_p(load_id, new_p, solver_control_); }
+ void change_q_load(int load_id, real_type new_q) {loads_.change_q(load_id, new_q, solver_control_); }
int get_bus_load(int load_id) {return loads_.get_bus(load_id);}
//generator
- void deactivate_gen(int gen_id) {generators_.deactivate(gen_id, topo_changed_); }
- void reactivate_gen(int gen_id) {generators_.reactivate(gen_id, topo_changed_); }
- void change_bus_gen(int gen_id, int new_bus_id) {generators_.change_bus(gen_id, new_bus_id, topo_changed_, static_cast(bus_vn_kv_.size())); }
- void change_p_gen(int gen_id, real_type new_p) {generators_.change_p(gen_id, new_p, topo_changed_); }
- void change_v_gen(int gen_id, real_type new_v_pu) {generators_.change_v(gen_id, new_v_pu, topo_changed_); }
+ void deactivate_gen(int gen_id) {generators_.deactivate(gen_id, solver_control_); }
+ void reactivate_gen(int gen_id) {generators_.reactivate(gen_id, solver_control_); }
+ void change_bus_gen(int gen_id, int new_bus_id) {generators_.change_bus(gen_id, new_bus_id, solver_control_, static_cast(bus_vn_kv_.size())); }
+ void change_p_gen(int gen_id, real_type new_p) {generators_.change_p(gen_id, new_p, solver_control_); }
+ void change_v_gen(int gen_id, real_type new_v_pu) {generators_.change_v(gen_id, new_v_pu, solver_control_); }
int get_bus_gen(int gen_id) {return generators_.get_bus(gen_id);}
//shunt
- void deactivate_shunt(int shunt_id) {shunts_.deactivate(shunt_id, topo_changed_); }
- void reactivate_shunt(int shunt_id) {shunts_.reactivate(shunt_id, topo_changed_); }
- void change_bus_shunt(int shunt_id, int new_bus_id) {shunts_.change_bus(shunt_id, new_bus_id, topo_changed_, static_cast(bus_vn_kv_.size())); }
- void change_p_shunt(int shunt_id, real_type new_p) {shunts_.change_p(shunt_id, new_p, topo_changed_); }
- void change_q_shunt(int shunt_id, real_type new_q) {shunts_.change_q(shunt_id, new_q, topo_changed_); }
+ void deactivate_shunt(int shunt_id) {shunts_.deactivate(shunt_id, solver_control_); }
+ void reactivate_shunt(int shunt_id) {shunts_.reactivate(shunt_id, solver_control_); }
+ void change_bus_shunt(int shunt_id, int new_bus_id) {shunts_.change_bus(shunt_id, new_bus_id, solver_control_, static_cast(bus_vn_kv_.size())); }
+ void change_p_shunt(int shunt_id, real_type new_p) {shunts_.change_p(shunt_id, new_p, solver_control_); }
+ void change_q_shunt(int shunt_id, real_type new_q) {shunts_.change_q(shunt_id, new_q, solver_control_); }
int get_bus_shunt(int shunt_id) {return shunts_.get_bus(shunt_id);}
//static gen
- void deactivate_sgen(int sgen_id) {sgens_.deactivate(sgen_id, topo_changed_); }
- void reactivate_sgen(int sgen_id) {sgens_.reactivate(sgen_id, topo_changed_); }
- void change_bus_sgen(int sgen_id, int new_bus_id) {sgens_.change_bus(sgen_id, new_bus_id, topo_changed_, static_cast(bus_vn_kv_.size())); }
- void change_p_sgen(int sgen_id, real_type new_p) {sgens_.change_p(sgen_id, new_p, topo_changed_); }
- void change_q_sgen(int sgen_id, real_type new_q) {sgens_.change_q(sgen_id, new_q, topo_changed_); }
+ void deactivate_sgen(int sgen_id) {sgens_.deactivate(sgen_id, solver_control_); }
+ void reactivate_sgen(int sgen_id) {sgens_.reactivate(sgen_id, solver_control_); }
+ void change_bus_sgen(int sgen_id, int new_bus_id) {sgens_.change_bus(sgen_id, new_bus_id, solver_control_, static_cast(bus_vn_kv_.size())); }
+ void change_p_sgen(int sgen_id, real_type new_p) {sgens_.change_p(sgen_id, new_p, solver_control_); }
+ void change_q_sgen(int sgen_id, real_type new_q) {sgens_.change_q(sgen_id, new_q, solver_control_); }
int get_bus_sgen(int sgen_id) {return sgens_.get_bus(sgen_id);}
//storage units
- void deactivate_storage(int storage_id) {storages_.deactivate(storage_id, topo_changed_); }
- void reactivate_storage(int storage_id) {storages_.reactivate(storage_id, topo_changed_); }
- void change_bus_storage(int storage_id, int new_bus_id) {storages_.change_bus(storage_id, new_bus_id, topo_changed_, static_cast(bus_vn_kv_.size())); }
+ void deactivate_storage(int storage_id) {storages_.deactivate(storage_id, solver_control_); }
+ void reactivate_storage(int storage_id) {storages_.reactivate(storage_id, solver_control_); }
+ void change_bus_storage(int storage_id, int new_bus_id) {storages_.change_bus(storage_id, new_bus_id, solver_control_, static_cast(bus_vn_kv_.size())); }
void change_p_storage(int storage_id, real_type new_p) {
// if(new_p == 0.)
// {
@@ -330,19 +446,19 @@ class GridModel : public DataGeneric
// reactivate_storage(storage_id); // requirement from grid2op, might be discussed
// storages_.change_p(storage_id, new_p, need_reset_);
// }
- storages_.change_p(storage_id, new_p, topo_changed_);
+ storages_.change_p(storage_id, new_p, solver_control_);
}
- void change_q_storage(int storage_id, real_type new_q) {storages_.change_q(storage_id, new_q, topo_changed_); }
+ void change_q_storage(int storage_id, real_type new_q) {storages_.change_q(storage_id, new_q, solver_control_); }
int get_bus_storage(int storage_id) {return storages_.get_bus(storage_id);}
//deactivate a powerline (disconnect it)
- void deactivate_dcline(int dcline_id) {dc_lines_.deactivate(dcline_id, topo_changed_); }
- void reactivate_dcline(int dcline_id) {dc_lines_.reactivate(dcline_id, topo_changed_); }
- void change_p_dcline(int dcline_id, real_type new_p) {dc_lines_.change_p(dcline_id, new_p, topo_changed_); }
- void change_v_or_dcline(int dcline_id, real_type new_v_pu) {dc_lines_.change_v_or(dcline_id, new_v_pu, topo_changed_); }
- void change_v_ex_dcline(int dcline_id, real_type new_v_pu) {dc_lines_.change_v_ex(dcline_id, new_v_pu, topo_changed_); }
- void change_bus_dcline_or(int dcline_id, int new_bus_id) {dc_lines_.change_bus_or(dcline_id, new_bus_id, topo_changed_, static_cast(bus_vn_kv_.size())); }
- void change_bus_dcline_ex(int dcline_id, int new_bus_id) {dc_lines_.change_bus_ex(dcline_id, new_bus_id, topo_changed_, static_cast(bus_vn_kv_.size())); }
+ void deactivate_dcline(int dcline_id) {dc_lines_.deactivate(dcline_id, solver_control_); }
+ void reactivate_dcline(int dcline_id) {dc_lines_.reactivate(dcline_id, solver_control_); }
+ void change_p_dcline(int dcline_id, real_type new_p) {dc_lines_.change_p(dcline_id, new_p, solver_control_); }
+ void change_v_or_dcline(int dcline_id, real_type new_v_pu) {dc_lines_.change_v_or(dcline_id, new_v_pu, solver_control_); }
+ void change_v_ex_dcline(int dcline_id, real_type new_v_pu) {dc_lines_.change_v_ex(dcline_id, new_v_pu, solver_control_); }
+ void change_bus_dcline_or(int dcline_id, int new_bus_id) {dc_lines_.change_bus_or(dcline_id, new_bus_id, solver_control_, static_cast(bus_vn_kv_.size())); }
+ void change_bus_dcline_ex(int dcline_id, int new_bus_id) {dc_lines_.change_bus_ex(dcline_id, new_bus_id, solver_control_, static_cast(bus_vn_kv_.size())); }
int get_bus_dcline_or(int dcline_id) {return dc_lines_.get_bus_or(dcline_id);}
int get_bus_dcline_ex(int dcline_id) {return dc_lines_.get_bus_ex(dcline_id);}
@@ -388,7 +504,10 @@ class GridModel : public DataGeneric
return Ybus_dc_; // This is copied to python
}
Eigen::Ref get_Sbus() const{
- return Sbus_;
+ return acSbus_;
+ }
+ Eigen::Ref get_dcSbus() const{
+ return dcSbus_;
}
Eigen::Ref get_pv() const{
return bus_pv_;
@@ -504,6 +623,21 @@ class GridModel : public DataGeneric
{
n_sub_ = n_sub;
}
+ void set_max_nb_bus_per_sub(int max_nb_bus_per_sub)
+ {
+ if(bus_vn_kv_.size() != n_sub_ * max_nb_bus_per_sub){
+ std::ostringstream exc_;
+ exc_ << "GridModel::set_max_nb_bus_per_sub: ";
+ exc_ << "your model counts ";
+ exc_ << bus_vn_kv_.size() << " buses according to `bus_vn_kv_` but ";
+ exc_ << n_sub_ * max_nb_bus_per_sub_ << " according to n_sub_ * max_nb_bus_per_sub_.";
+ exc_ << "Both should match: either reinit it with another call to `init_bus` or set properly the number of ";
+ exc_ << "substations / buses per substations with `set_n_sub` / `set_max_nb_bus_per_sub`";
+ throw std::runtime_error(exc_.str());
+ }
+ max_nb_bus_per_sub_ = max_nb_bus_per_sub;
+ }
+ int get_max_nb_bus_per_sub() const { return max_nb_bus_per_sub_;}
void fillSbus_other(CplxVect & res, bool ac, const std::vector& id_me_to_solver){
fillSbus_me(res, ac, id_me_to_solver);
@@ -514,6 +648,8 @@ class GridModel : public DataGeneric
Eigen::SparseMatrix & Bpp,
FDPFMethod xb_or_bx) const;
+ void fillBf_for_PTDF(Eigen::SparseMatrix & Bf, bool transpose=false) const;
+
Eigen::SparseMatrix debug_get_Bp_python(FDPFMethod xb_or_bx){
Eigen::SparseMatrix Bp;
Eigen::SparseMatrix Bpp;
@@ -541,23 +677,34 @@ class GridModel : public DataGeneric
if you will perform a powerflow after it or not. (usually put ``true`` here).
**/
CplxVect pre_process_solver(const CplxVect & Vinit,
+ CplxVect & Sbus,
Eigen::SparseMatrix & Ybus,
std::vector & id_me_to_solver,
std::vector & id_solver_to_me,
+ Eigen::VectorXi & slack_bus_id_me,
Eigen::VectorXi & slack_bus_id_solver,
bool is_ac,
- bool reset_solver);
+ const SolverControl & solver_control);
+
+ // init the Ybus matrix (its size, it is filled up elsewhere) and also the
+ // converter from "my bus id" to the "solver bus id" (id_me_to_solver and id_solver_to_me)
void init_Ybus(Eigen::SparseMatrix & Ybus,
std::vector