diff --git a/.circleci/config.yml b/.circleci/config.yml index 6973ecc..cc6fe65 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: @@ -268,11 +367,17 @@ jobs: size: medium # ("medium" "large" "xlarge" "2xlarge") steps: - checkout - # - run: - # name: "Install Python" - # command: choco install python --version=3.9 # use python 3.9 for windows test - - run: py -m pip install virtualenv - - run: py -m virtualenv venv_test + - run: choco install python --version=3.10 --force -y + - run: C:\Python310\python --version + - run: C:\Python310\python -m pip install --upgrade pip setuptools wheel + - run: C:\Python310\python -m pip install virtualenv + - run: C:\Python310\python -m virtualenv venv_test + - run: + name: "Chekc python / pip version in venv" + command: | + .\venv_test\Scripts\activate + python --version + pip --version - run: name: "Install grid2op from source" command: | @@ -286,8 +391,8 @@ jobs: pip install -U pybind11 git submodule init git submodule update - py setup.py build - py -m pip install -e .[test] + python setup.py build + python -m pip install -e .[test] - run: name: "make tests" command: | @@ -300,11 +405,15 @@ workflows: 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/.gitignore b/.gitignore index e4e3737..5272312 100644 --- a/.gitignore +++ b/.gitignore @@ -278,3 +278,5 @@ 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/.readthedocs.yml b/.readthedocs.yml index 97df330..ab6ff9c 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -1,4 +1,4 @@ -version: 2 +version: "2" submodules: include: @@ -6,8 +6,15 @@ submodules: - eigen recursive: true +build: + os: "ubuntu-22.04" + tools: + python: "3.10" + +sphinx: + configuration: docs/conf.py + python: - version: 3.8 install: - method: pip path: . diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 56c4a88..0815664 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -18,11 +18,15 @@ Change Log - maybe have a look at suitesparse "sliplu" tools ? - easier building (get rid of the "make" part) -[0.7.6] 2023-xx-yy +[0.8.0] 2023-03-15 -------------------- - [BREAKING] now able to retrieve `dcSbus` with a dedicated method (and not with the old `get_Sbus`). - If you previously used `gridmodel.get_Subus()` to retrieve the Sbus used for DC powerflow, please use + 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 @@ -30,14 +34,50 @@ Change Log - [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 + better way to copy `LightSimBackend` + [0.7.5] 2023-10-05 -------------------- - [FIXED] a bug in DC powerflow when asking for computation time: it was not reset to 0. when diff --git a/README.md b/README.md index 303ef73..cf1c078 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 b7a15ad..a2e2668 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 7281e06..55dfa24 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 a49c428..4ad4aa2 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 f89887a..c50cbef 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.6.dev1" -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 fbb930a..ceff6cf 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 ef9d80b..8307056 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 dbdd5e6..ff137fa 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 929780f..ec429e7 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.6.dev1" +__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 0000000..f16a504 --- /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 17b9c83..cfa66f0 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 ef7a856..6501ead 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,7 +6,9 @@ # 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 @@ -39,7 +41,8 @@ def init(net : pypo.network, sn_mva = 100., sort_index=True, f_hz = 50., # unused - only_main_component=True): + only_main_component=True, + return_sub_id=False): model = GridModel() # model.set_f_hz(f_hz) @@ -66,7 +69,7 @@ def init(net : pypo.network, 0, 0 # unused ) model._orig_to_ls = 1 * bus_df_orig["bus_id"].values - + # do the generators if sort_index: df_gen = net.get_generators().sort_index() @@ -148,7 +151,7 @@ def init(net : pypo.network, 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) + model.set_line_names(df_line.index) # for trafo if sort_index: @@ -201,19 +204,23 @@ def init(net : pypo.network, for shunt_id, disco in enumerate(sh_disco): if disco: model.deactivate_shunt(shunt_id) - model.set_shunt_names(df_trafo.index) + model.set_shunt_names(df_shunt.index) # for hvdc (TODO not tested yet) - df_dc = net.get_hvdc_lines().sort_index() - df_sations = net.get_vsc_converter_stations().sort_index() + 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 - bus_from_id, hvdc_from_disco = _aux_get_bus(bus_df, df_sations.loc[df_dc["converter_station1_id"].values]) - bus_to_id, hvdc_to_disco = _aux_get_bus(bus_df, df_sations.loc[df_dc["converter_station2_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_from_id, - bus_to_id, + model.init_dclines(hvdc_bus_from_id, + hvdc_bus_to_id, df_dc["target_p"].values, loss_percent, loss_mw, @@ -270,10 +277,34 @@ def init(net : pypo.network, # TODO checks # no 3windings trafo and other exotic stuff if net.get_phase_tap_changers().shape[0] > 0: - pass - # raise RuntimeError("Impossible currently to init a grid with tap changers at the moment.") + 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() - return model + 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/lightSimBackend.py b/lightsim2grid/lightSimBackend.py index cf81459..a7c6319 100644 --- a/lightsim2grid/lightSimBackend.py +++ b/lightsim2grid/lightSimBackend.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020, RTE (https://www.rte-france.com) +# Copyright (c) 2020-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, @@ -10,6 +10,7 @@ from typing import Optional, Union import warnings import numpy as np +import pandas as pd import time from grid2op.Action import CompleteAction @@ -49,42 +50,70 @@ def __init__(self, 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, ): - try: - # for grid2Op >= 1.7.1 - Backend.__init__(self, - detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures, - can_be_copied=can_be_copied, - solver_type=solver_type, - max_iter=max_iter, - tol=tol, - turned_off_pv=turned_off_pv, - dist_slack_non_renew=dist_slack_non_renew, - use_static_gen=use_static_gen, - loader_method=loader_method, - loader_kwargs=loader_kwargs) - 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) + self.max_it = max_iter + self.tol = tol # tolerance for the solver + self._check_suitable_solver_type(solver_type, check_in_avail_solver=False) + self.__current_solver_type = solver_type + + # does the "turned off" generators (including when p=0) + # are pv buses + self._turned_off_pv = turned_off_pv + + # distributed slack, on non renewable gen with P > 0 + self._dist_slack_non_renew = dist_slack_non_renew + + # add the static gen to the list of controlable gen in grid2Op + self._use_static_gen = use_static_gen # TODO implement it - # lazy loading because it crashes... - from lightsim2grid._utils import _DoNotUseAnywherePandaPowerBackend - from grid2op.Space import GridObjects # lazy import - self.__has_storage = hasattr(GridObjects, "n_storage") - if not self.__has_storage: - warnings.warn("Please upgrade your grid2Op to >= 1.5.0. You are using a backward compatibility " - "feature that will be removed in further lightsim2grid version.") 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, + max_iter, + tol, + turned_off_pv, + dist_slack_non_renew, + 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 1.9.6 + self.supported_grid_format = ("json", ) # new in 0.8.0 elif loader_method == "pypowsybl": - self.supported_grid_format = ("xiidm", ) # new in 1.9.6 + 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 + self.__has_storage = hasattr(GridObjects, "n_storage") + if not self.__has_storage: + warnings.warn("Please upgrade your grid2Op to >= 1.5.0. You are using a backward compatibility " + "feature that will be removed in further lightsim2grid version.") self.shunts_data_available = True # needs to be self and not type(self) here @@ -115,8 +144,6 @@ def __init__(self, self.init_pp_backend = _DoNotUseAnywherePandaPowerBackend() self.V = None - self.max_it = max_iter - self.tol = tol # tolerance for the solver self.prod_pu_to_kv = None self.load_pu_to_kv = None @@ -168,12 +195,20 @@ def __init__(self, # available solver in lightsim self.available_solvers = [] - self.comp_time = 0. # computation time of just the powerflow + + # computation time of just the powerflow (when the grid is formatted + # by the gridmodel already) + # it takes only into account the time spend in the powerflow algorithm + self.comp_time = 0. + + # computation time of the powerflow + # it takes into account everything in the gridmodel, including the mapping + # to the solver, building of Ybus and Sbus AND the time to solve the powerflow + self.timer_gridmodel_xx_pf = 0. + self._timer_postproc = 0. self._timer_preproc = 0. self._timer_solver = 0. - self._check_suitable_solver_type(solver_type, check_in_avail_solver=False) - self.__current_solver_type = solver_type # hack for the storage unit: # in grid2op, for simplicity, I suppose that if a storage is alone on a busbar, and @@ -186,32 +221,41 @@ def __init__(self, # backend SHOULD not do these kind of stuff self._idx_hack_storage = [] - # does the "turned off" generators (including when p=0) - # are pv buses - self._turned_off_pv = turned_off_pv - - # distributed slack, on non renewable gen with P > 0 - self._dist_slack_non_renew = dist_slack_non_renew - - # add the static gen to the list of controlable gen in grid2Op - self._use_static_gen = use_static_gen # TODO implement it - - # storage data for this object (otherwise it's in the class) - # self.n_storage = None - # self.storage_to_subid = None - # self.storage_pu_to_kv = None - # self.name_storage = None - # self.storage_to_sub_pos = None - # self.storage_type = None - # self.storage_Emin = None - # self.storage_Emax = None - # self.storage_max_p_prod = None - # self.storage_max_p_absorb = None - # self.storage_marginal_cost = None - # self.storage_loss = None - # self.storage_discharging_efficiency = None - # self.storage_charging_efficiency = None - + def _aux_init_super(self, + detailed_infos_for_cascading_failures, + can_be_copied, + solver_type, + max_iter, + tol, + turned_off_pv, + dist_slack_non_renew, + use_static_gen, + loader_method, + loader_kwargs, + stop_if_load_disco, + stop_if_gen_disco): + try: + # for grid2Op >= 1.7.1 + Backend.__init__(self, + detailed_infos_for_cascading_failures=detailed_infos_for_cascading_failures, + can_be_copied=can_be_copied, + solver_type=solver_type, + max_iter=max_iter, + tol=tol, + turned_off_pv=turned_off_pv, + dist_slack_non_renew=dist_slack_non_renew, + 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() @@ -411,12 +455,64 @@ def _assign_right_solver(self): 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() + 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 @@ -429,28 +525,16 @@ def _load_grid_pypowsybl(self, path=None, filename=None): full_path = self.make_complete_path(path, filename) except AttributeError as exc_: warnings.warn("Please upgrade your grid2op version") - import os - from grid2op.Exceptions import Grid2OpException - def make_complete_path(path, filename): - 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 - full_path = make_complete_path(path, filename) + 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 = init_pypow(grid_tmp, gen_slack_id=gen_slack_id, sort_index=True) + 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 @@ -470,51 +554,73 @@ def make_complete_path(path, filename): self.name_sub = ["sub_{}".format(i) for i, _ in enumerate(df.iterrows())] if not from_sub: - self.load_to_subid = np.array([el.bus_id for el in self._grid.get_loads()], dtype=dt_int) - self.gen_to_subid = np.array([el.bus_id for el in self._grid.get_generators()], dtype=dt_int) - self.line_or_to_subid = np.array([el.bus_or_id for el in self._grid.get_lines()] + - [el.bus_hv_id for el in self._grid.get_trafos()], - dtype=dt_int) - self.line_ex_to_subid = np.array([el.bus_ex_id for el in self._grid.get_lines()] + - [el.bus_lv_id for el in self._grid.get_trafos()], - dtype=dt_int) - self.storage_to_subid = np.array([el.bus_id for el in self._grid.get_storages()], dtype=dt_int) - self.shunt_to_subid = np.array([el.bus_id for el in self._grid.get_shunts()], dtype=dt_int) + # 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() - raise NotImplementedError("TODO") + # 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 - 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())]) + 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()) - self.__nb_bus_before = len(self._grid.get_bus_vn_kv()) # 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) - - # 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"]: - bus_init = self._grid.get_bus_vn_kv() - orig_to_ls = np.array(self._grid._orig_to_ls) - bus_doubled = np.concatenate((bus_init, bus_init)) - self._grid.init_bus(bus_doubled, 0, 0) - 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, - orig_to_ls + self.__nb_bus_before) - ) - self._grid._orig_to_ls = new_orig_to_ls self.nb_bus_total = len(self._grid.get_bus_vn_kv()) # and now things needed by the backend (legacy) @@ -526,11 +632,12 @@ def make_complete_path(path, filename): 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[shunt_bus_id] + # 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() @@ -549,13 +656,30 @@ def _aux_setup_right_after_grid_init(self): # 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._aux_setup_right_after_grid_init() + 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 @@ -603,11 +727,6 @@ def _load_grid_pandapower(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 @@ -653,7 +772,6 @@ def _aux_finish_setup_after_reading(self): # set up the "lightsim grid" accordingly cls = type(self) - self._grid.set_n_sub(self.__nb_bus_before) 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]) @@ -735,7 +853,7 @@ def _aux_finish_setup_after_reading(self): 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(cls.dim_topo, dtype=dt_int) self.__init_topo_vect[:] = self.topo_vect @@ -894,7 +1012,9 @@ def runpf(self, is_dc=False): 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 BackendError(f"Divergence of DC powerflow (non connected grid) at the initialization of AC powerflow. Detailed error: {self._grid.get_dc_solver().get_error()}") @@ -911,8 +1031,18 @@ def runpf(self, is_dc=False): 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], @@ -948,12 +1078,12 @@ def runpf(self, is_dc=False): 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 BackendError(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.): + 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 @@ -968,11 +1098,11 @@ def runpf(self, is_dc=False): 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_ @@ -1044,26 +1174,42 @@ def copy(self): #################### # res = copy.deepcopy(self) # super slow res = type(self).__new__(type(self)) + # make sure to init the "base class" + # in particular with "new" attributes in future grid2op Backend + res._aux_init_super(self.detailed_infos_for_cascading_failures, + self._can_be_copied, + self.__current_solver_type, + self.max_it, + self.tol, + self._turned_off_pv, + self._dist_slack_non_renew, + 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 # copy the regular attribute res.__has_storage = self.__has_storage - res.__current_solver_type = self.__current_solver_type + res.__current_solver_type = self.__current_solver_type # forced here because of special `__` res.__nb_powerline = self.__nb_powerline res.__nb_bus_before = self.__nb_bus_before - res._can_be_copied = self._can_be_copied res.cst_1 = dt_float(1.0) - li_regular_attr = ["detailed_infos_for_cascading_failures", "comp_time", "can_output_theta", "_is_loaded", + li_regular_attr = ["comp_time", "can_output_theta", "_is_loaded", "nb_bus_total", "initdc", - "_big_topo_to_obj", "max_it", "tol", "dim_topo", + "_big_topo_to_obj", "dim_topo", "_idx_hack_storage", "_timer_preproc", "_timer_postproc", "_timer_solver", - "_my_kwargs", "supported_grid_format", - "_turned_off_pv", "_dist_slack_non_renew", - "_loader_method", "_loader_kwargs" + "supported_grid_format", + "max_it", "tol", "_turned_off_pv", "_dist_slack_non_renew", + "_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): - # 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 the numpy array @@ -1082,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) @@ -1190,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 a0cc724..f9db2b1 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,339 +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 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/test_Computers.py b/lightsim2grid/tests/test_Computers.py index 15940a9..afb6d82 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_DCPF.py b/lightsim2grid/tests/test_DCPF.py index 93604c1..3e910d7 100644 --- a/lightsim2grid/tests/test_DCPF.py +++ b/lightsim2grid/tests/test_DCPF.py @@ -124,10 +124,14 @@ def _aux_test(self, pn_net): real_init_file = pp.from_json(case_name) backend = LightSimBackend() + type(backend)._clear_grid_dependant_class_attributes() with warnings.catch_warnings(): warnings.filterwarnings("ignore") + type(backend).env_name = pn_net backend.load_grid(case_name) - + backend.assert_grid_correct() + # backend.init_pp_backend.assert_grid_correct() + nb_sub = backend.n_sub pp_net = backend.init_pp_backend._grid # first i deactivate all slack bus in pp that are connected but not handled in ls diff --git a/lightsim2grid/tests/test_GridModel.py b/lightsim2grid/tests/test_GridModel.py index 0d6329e..7230fc7 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_RedispatchEnv.py b/lightsim2grid/tests/test_RedispatchEnv.py index e7658fd..0f5b5e0 100644 --- a/lightsim2grid/tests/test_RedispatchEnv.py +++ b/lightsim2grid/tests/test_RedispatchEnv.py @@ -59,4 +59,4 @@ def make_backend(self, detailed_infos_for_cascading_failures=False): if __name__ == "__main__": - unittest.main() + unittest.main() \ No newline at end of file diff --git a/lightsim2grid/tests/test_Runner.py b/lightsim2grid/tests/test_Runner.py index 734c992..9f7a497 100644 --- a/lightsim2grid/tests/test_Runner.py +++ b/lightsim2grid/tests/test_Runner.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020, RTE (https://www.rte-france.com) +# Copyright (c) 2020-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, @@ -11,7 +11,7 @@ import grid2op from grid2op.tests.test_Runner import TestRunner as TestRunner_glop from grid2op.tests.test_RunnerFast import TestRunner as TestRunnerFast_glop -from grid2op.tests.test_Runner import HelperTests, L2RPNReward +from grid2op.tests.test_Runner import L2RPNReward from grid2op.Runner import Runner from lightsim2grid.lightSimBackend import LightSimBackend @@ -79,4 +79,4 @@ def setUp(self): if __name__ == "__main__": - unittest.main() + unittest.main() \ No newline at end of file diff --git a/lightsim2grid/tests/test_SameResPP.py b/lightsim2grid/tests/test_SameResPP.py index 9e7e1f7..13ae6a8 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,7 +218,7 @@ 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() + 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,\ diff --git a/lightsim2grid/tests/test_SecurityAnlysis.py b/lightsim2grid/tests/test_SecurityAnlysis.py index 4927f6f..1ab67da 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 bbf73ae..15de108 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_case118.py b/lightsim2grid/tests/test_case118.py index c4e4890..95b5d87 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 40c492a..2d63007 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 diff --git a/lightsim2grid/tests/test_init_from_pypowsybl.py b/lightsim2grid/tests/test_init_from_pypowsybl.py index 7e008be..24bc8ef 100644 --- a/lightsim2grid/tests/test_init_from_pypowsybl.py +++ b/lightsim2grid/tests/test_init_from_pypowsybl.py @@ -175,15 +175,27 @@ def test_ac_pf(self): v_ls_ref = self.ref_samecase.ac_pf(1.0 * self.V_init_ac, 10, self.tol) 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}" - param = lf.Parameters(voltage_init_mode=pp._pypowsybl.VoltageInitMode.UNIFORM_VALUES, - transformer_voltage_control_on=False, - no_generator_reactive_limits=True, - phase_shifter_regulation_on=False, - simul_shunt=False, - distributed_slack=False, - provider_parameters={"slackBusSelectionMode": "NAME", - "slackBusesIds": self.network_ref.get_buses().iloc[self.get_slackbus_id()].name} - ) + try: + param = lf.Parameters(voltage_init_mode=pp._pypowsybl.VoltageInitMode.UNIFORM_VALUES, + transformer_voltage_control_on=False, + use_reactive_limits=False, + shunt_compensator_voltage_control_on=False, + phase_shifter_regulation_on=False, + distributed_slack=False, + provider_parameters={"slackBusSelectionMode": "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, + transformer_voltage_control_on=False, + no_generator_reactive_limits=True, # documented in the doc but apparently fails + phase_shifter_regulation_on=False, + simul_shunt=False, # documented in the doc but apparently fails + 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 diff --git a/lightsim2grid/tests/test_issue_56.py b/lightsim2grid/tests/test_issue_56.py index 6b0a3a6..412615b 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_n_busbar_per_sub.py b/lightsim2grid/tests/test_n_busbar_per_sub.py new file mode 100644 index 0000000..aa51bb0 --- /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 index 8bfb6da..4a98e4a 100644 --- a/lightsim2grid/tests/test_ptdf.py +++ b/lightsim2grid/tests/test_ptdf.py @@ -36,7 +36,8 @@ def setUp(self) -> None: if solver_type not in self.gridmodel.available_solvers(): self.skipTest("Solver type not supported on this platform") self.gridmodel.change_solver(solver_type) - self.gridmodel.dc_pf(self.V_init, 1, 1e-8) + 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 diff --git a/lightsim2grid/tests/test_solver_control.py b/lightsim2grid/tests/test_solver_control.py new file mode 100644 index 0000000..ae2a8f1 --- /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 49fb8d3..0059d93 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 75bbaf1..2942441 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 99193f8..cb4269d 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ from pybind11.setup_helpers import Pybind11Extension, build_ext -__version__ = "0.7.6.dev1" +__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") diff --git a/src/ChooseSolver.cpp b/src/ChooseSolver.cpp index 165f787..bfd87dd 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 4c713b6..9c17e43 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 @@ -275,6 +276,11 @@ class ChooseSolver 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(); @@ -305,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){ @@ -369,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;} @@ -403,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;} @@ -445,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/GridModel.cpp b/src/GridModel.cpp index 1152da9..67b4853 100644 --- a/src/GridModel.cpp +++ b/src/GridModel.cpp @@ -14,9 +14,11 @@ GridModel::GridModel(const GridModel & other) { reset(true, true, true); + 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 @@ -72,8 +74,11 @@ 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 @@ -93,6 +98,22 @@ 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, @@ -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,36 +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_[0], ls_to_pp_.size())); // set also _orig_to_ls + set_ls_to_orig(IntVect::Map(ls_to_pp.data(), ls_to_pp.size())); // set also _orig_to_ls // 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; @@ -237,7 +305,8 @@ 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 @@ -261,13 +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){ @@ -276,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_; @@ -294,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) { @@ -378,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, @@ -408,47 +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); - } - auto & this_Sbus = is_ac ? Sbus_ : dcSbus_; - init_Sbus(this_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(this_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){ @@ -458,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 ? } } @@ -516,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 @@ -525,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){ @@ -559,6 +744,7 @@ 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_); // TODO have a function to dispatch that to all type of elements @@ -569,13 +755,14 @@ 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 + // 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); @@ -590,18 +777,21 @@ 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); // TODO have a function to dispatch that to all type of elements @@ -619,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 @@ -648,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()); @@ -664,17 +854,20 @@ void GridModel::compute_results(bool ac){ const auto id_slack = slack_bus_id_dc_solver_(0); 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(){ + // 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(); @@ -694,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: @@ -706,19 +901,42 @@ 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_); + 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; } @@ -771,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){ @@ -791,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 **/ @@ -852,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, @@ -900,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 @@ -1009,9 +1237,11 @@ std::tuple GridModel::assign_slack_to_most_connected(){ // and reset the slack bus generators_.remove_all_slackbus(); - res_gen_id = generators_.assign_slack_bus(res_bus_id, gen_p_per_bus); + 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_ = std::vector(); + slack_bus_id_ac_solver_ = Eigen::VectorXi(); + slack_bus_id_dc_solver_ = Eigen::VectorXi(); slack_weights_ = RealVect(); return res; } diff --git a/src/GridModel.h b/src/GridModel.h index 02e1940..7a4b69c 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,9 +42,11 @@ // 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: + typedef Eigen::Array IntVectRowMaj; + typedef std::tuple< int, // version major int, // version medium @@ -55,26 +57,53 @@ class GridModel : public DataGeneric 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{ @@ -86,6 +115,8 @@ class GridModel : public DataGeneric 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_;} @@ -94,27 +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); } @@ -164,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 @@ -251,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"); } @@ -259,8 +288,14 @@ 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, @@ -280,101 +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.) // { @@ -385,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);} @@ -443,7 +504,7 @@ 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_; @@ -562,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); @@ -601,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 & id_me_to_solver, std::vector& id_solver_to_me); - void init_Sbus(CplxVect & Sbus, - std::vector & id_me_to_solver, - std::vector& id_solver_to_me, - Eigen::VectorXi & slack_bus_id_solver); + + // converts the slack_bus_id from gridmodel ordering into solver ordering + void 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 + ); void fillYbus(Eigen::SparseMatrix & res, bool ac, const std::vector& id_me_to_solver); void fillSbus_me(CplxVect & res, bool ac, const std::vector& id_me_to_solver); - void fillpv_pq(const std::vector& id_me_to_solver, std::vector& id_solver_to_me, - Eigen::VectorXi & slack_bus_id_solver); + void fillpv_pq(const std::vector& id_me_to_solver, + const std::vector& id_solver_to_me, + const Eigen::VectorXi & slack_bus_id_solver, + const SolverControl & solver_control); // results /**process the results from the solver to this instance @@ -666,29 +753,23 @@ class GridModel : public DataGeneric { for(int el_id = 0; el_id < vect_pos.rows(); ++el_id) { + int el_pos = vect_pos(el_id); + if(! has_changed(el_pos)) continue; int new_bus = new_values(el_pos); if(new_bus > 0){ // new bus is a real bus, so i need to make sure to have it turned on, and then change the bus - int init_bus_me = vect_subid(el_id); - int new_bus_backend = new_bus == 1 ? init_bus_me : init_bus_me + n_sub_ ; + int sub_id = vect_subid(el_id); + int new_bus_backend = sub_id + (new_bus - 1) * n_sub_; bus_status_[new_bus_backend] = true; - if(has_changed(el_pos)) - { - (this->*fun_react)(el_id); // eg reactivate_load(load_id); - (this->*fun_change)(el_id, new_bus_backend); // eg change_bus_load(load_id, new_bus_backend); - topo_changed_ = true; - } + (this->*fun_react)(el_id); // eg reactivate_load(load_id); + (this->*fun_change)(el_id, new_bus_backend); // eg change_bus_load(load_id, new_bus_backend); } else{ - if(has_changed(el_pos)) - { - // new bus is negative, we deactivate it - (this->*fun_deact)(el_id);// eg deactivate_load(load_id); - // bus_status_ is set to "false" in GridModel.update_topo - // and a bus is activated if (and only if) one element is connected to it. - // I must not set `bus_status_[new_bus_backend] = false;` in this case ! - topo_changed_ = true; - } + // new bus is negative, we deactivate it + (this->*fun_deact)(el_id);// eg deactivate_load(load_id); + // bus_status_ is set to "false" in GridModel.update_topo + // and a bus is activated if (and only if) one element is connected to it. + // I must not set `bus_status_[new_bus_backend] = false;` in this case ! } } } @@ -698,7 +779,7 @@ class GridModel : public DataGeneric int size); void check_solution_q_values( CplxVect & res, bool check_q_limits) const; - void check_solution_q_values_onegen(CplxVect & res, const DataGen::GenInfo& gen, bool check_q_limits) const; + void check_solution_q_values_onegen(CplxVect & res, const GeneratorContainer::GenInfo& gen, bool check_q_limits) const; protected: // memory for the import @@ -706,10 +787,14 @@ class GridModel : public DataGeneric IntVect _orig_to_ls; // for converter from bus in lightsim2grid index to bus in original file format (*eg* pandapower or pypowsybl) // member of the grid - // static const int _deactivated_bus_id; - - bool need_reset_; - bool topo_changed_; + double timer_last_ac_pf_; + double timer_last_dc_pf_; + + // bool need_reset_solver_; // some matrices change size, needs to be computed + // bool need_recompute_sbus_; // some coeff of sbus changed, need to recompute it + // bool need_recompute_ybus_; // some coeff of ybus changed, but not its sparsity pattern + // bool ybus_change_sparsity_pattern_; // sparsity pattern of ybus changed (and so are its coeff) + SolverControl solver_control_; bool compute_results_; real_type init_vm_pu_; // default vm initialization, mainly for dc powerflow real_type sn_mva_; @@ -731,44 +816,46 @@ class GridModel : public DataGeneric std::vector id_dc_solver_to_me_; // 2. powerline - DataLine powerlines_; + LineContainer powerlines_; // 3. shunt - DataShunt shunts_; + ShuntContainer shunts_; // 4. transformers // have the r, x, h and ratio // ratio is computed from the tap, so maybe store tap num and tap_step_pct - DataTrafo trafos_; + TrafoContainer trafos_; // 5. generators RealVect total_q_min_per_bus_; RealVect total_q_max_per_bus_; Eigen::VectorXi total_gen_per_bus_; - DataGen generators_; + GeneratorContainer generators_; // 6. loads - DataLoad loads_; + LoadContainer loads_; - // 6. static generators (P,Q generators) - DataSGen sgens_; + // 7. static generators (P,Q generators) + SGenContainer sgens_; - // 7. storage units - DataLoad storages_; + // 8. storage units + LoadContainer storages_; - // hvdc - DataDCLine dc_lines_; + // 9. hvdc + DCLineContainer dc_lines_; - // 8. slack bus - std::vector slack_bus_id_; - Eigen::VectorXi slack_bus_id_ac_solver_; + // 10. slack bus + // std::vector slack_bus_id_; + Eigen::VectorXi slack_bus_id_ac_me_; // slack bus id, gridmodel number + Eigen::VectorXi slack_bus_id_ac_solver_; // slack bus id, solver number + Eigen::VectorXi slack_bus_id_dc_me_; Eigen::VectorXi slack_bus_id_dc_solver_; RealVect slack_weights_; // as matrix, for the solver Eigen::SparseMatrix Ybus_ac_; Eigen::SparseMatrix Ybus_dc_; - CplxVect Sbus_; + CplxVect acSbus_; CplxVect dcSbus_; Eigen::VectorXi bus_pv_; // id are the solver internal id and NOT the initial id Eigen::VectorXi bus_pq_; // id are the solver internal id and NOT the initial id @@ -781,21 +868,22 @@ class GridModel : public DataGeneric // specific grid2op int n_sub_; - Eigen::Array load_pos_topo_vect_; - Eigen::Array gen_pos_topo_vect_; - Eigen::Array line_or_pos_topo_vect_; - Eigen::Array line_ex_pos_topo_vect_; - Eigen::Array trafo_hv_pos_topo_vect_; - Eigen::Array trafo_lv_pos_topo_vect_; - Eigen::Array storage_pos_topo_vect_; - - Eigen::Array load_to_subid_; - Eigen::Array gen_to_subid_; - Eigen::Array line_or_to_subid_; - Eigen::Array line_ex_to_subid_; - Eigen::Array trafo_hv_to_subid_; - Eigen::Array trafo_lv_to_subid_; - Eigen::Array storage_to_subid_; + int max_nb_bus_per_sub_; + IntVectRowMaj load_pos_topo_vect_; + IntVectRowMaj gen_pos_topo_vect_; + IntVectRowMaj line_or_pos_topo_vect_; + IntVectRowMaj line_ex_pos_topo_vect_; + IntVectRowMaj trafo_hv_pos_topo_vect_; + IntVectRowMaj trafo_lv_pos_topo_vect_; + IntVectRowMaj storage_pos_topo_vect_; + + IntVectRowMaj load_to_subid_; + IntVectRowMaj gen_to_subid_; + IntVectRowMaj line_or_to_subid_; + IntVectRowMaj line_ex_to_subid_; + IntVectRowMaj trafo_hv_to_subid_; + IntVectRowMaj trafo_lv_to_subid_; + IntVectRowMaj storage_to_subid_; }; diff --git a/src/Solvers.cpp b/src/Solvers.cpp index eed71c9..9cd8060 100644 --- a/src/Solvers.cpp +++ b/src/Solvers.cpp @@ -13,7 +13,7 @@ // this is why i need to define them here for every specialization. template -void BaseFDPFSolver::fillBp_Bpp(Eigen::SparseMatrix & Bp, Eigen::SparseMatrix & Bpp) const +void BaseFDPFAlgo::fillBp_Bpp(Eigen::SparseMatrix & Bp, Eigen::SparseMatrix & Bpp) const { _gridmodel->fillBp_Bpp(Bp, Bpp, XB_BX); } diff --git a/src/Solvers.h b/src/Solvers.h index 4adaac8..b578f33 100644 --- a/src/Solvers.h +++ b/src/Solvers.h @@ -6,36 +6,41 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "BaseNRSolver.h" -#include "BaseNRSolverSingleSlack.h" -#include "DCSolver.h" -#include "BaseFDPFSolver.h" +#ifndef SOLVERS_H +#define SOLVERS_H -#include "SparseLUSolver.h" -#include "KLUSolver.h" -#include "NICSLUSolver.h" -#include "CKTSOSolver.h" +#include "powerflow_algorithm/BaseDCAlgo.h" +#include "powerflow_algorithm/BaseNRAlgo.h" +#include "powerflow_algorithm/BaseNRSingleSlackAlgo.h" +#include "powerflow_algorithm/BaseFDPFAlgo.h" +#include "powerflow_algorithm/GaussSeidelSynchAlgo.h" +#include "powerflow_algorithm/GaussSeidelAlgo.h" + +#include "linear_solvers/SparseLUSolver.h" +#include "linear_solvers/KLUSolver.h" +#include "linear_solvers/NICSLUSolver.h" +#include "linear_solvers/CKTSOSolver.h" /** Solver based on Newton Raphson, using the SparseLU decomposition of Eigen**/ -typedef BaseNRSolver SparseLUSolver; +typedef BaseNRAlgo SparseLUSolver; /** Solver based on Newton Raphson, using the SparseLU decomposition of Eigen, do not consider multiple slack bus**/ -typedef BaseNRSolverSingleSlack SparseLUSolverSingleSlack; +typedef BaseNRSingleSlackAlgo SparseLUSolverSingleSlack; /** Solver based on Newton Raphson, using the SparseLU decomposition of Eigen, only suitable for the DC approximation**/ -typedef BaseDCSolver DCSolver; +typedef BaseDCAlgo DCSolver; /** Solver based on Fast Decoupled, using the SparseLU decomposition of Eigen**/ -typedef BaseFDPFSolver FDPF_XB_SparseLUSolver; -typedef BaseFDPFSolver FDPF_BX_SparseLUSolver; +typedef BaseFDPFAlgo FDPF_XB_SparseLUSolver; +typedef BaseFDPFAlgo FDPF_BX_SparseLUSolver; #ifdef KLU_SOLVER_AVAILABLE /** Solver based on Newton Raphson, using the KLU linear solver**/ - typedef BaseNRSolver KLUSolver; + typedef BaseNRAlgo KLUSolver; /** Solver based on Newton Raphson, using the KLU linear solver, do not consider multiple slack bus**/ - typedef BaseNRSolverSingleSlack KLUSolverSingleSlack; + typedef BaseNRSingleSlackAlgo KLUSolverSingleSlack; /** Solver based on Newton Raphson, using the KLU linear solver, only suitable for the DC approximation**/ - typedef BaseDCSolver KLUDCSolver; + typedef BaseDCAlgo KLUDCSolver; /** Solver based on Fast Decoupled, using the KLU linear solver**/ - typedef BaseFDPFSolver FDPF_XB_KLUSolver; - typedef BaseFDPFSolver FDPF_BX_KLUSolver; + typedef BaseFDPFAlgo FDPF_XB_KLUSolver; + typedef BaseFDPFAlgo FDPF_BX_KLUSolver; #elif defined(_READ_THE_DOCS) // hack to display accurately the doc in read the doc even if the models are not compiled /** Solver based on Newton Raphson, using the KLU linear solver**/ @@ -51,14 +56,14 @@ typedef BaseFDPFSolver FDPF_BX_SparseLUSol #ifdef NICSLU_SOLVER_AVAILABLE /** Solver based on Newton Raphson, using the NICSLU linear solver (needs a specific license)**/ - typedef BaseNRSolver NICSLUSolver; + typedef BaseNRAlgo NICSLUSolver; /** Solver based on Newton Raphson, using the NICSLU linear solver (needs a specific license), do not consider multiple slack bus**/ - typedef BaseNRSolverSingleSlack NICSLUSolverSingleSlack; + typedef BaseNRSingleSlackAlgo NICSLUSolverSingleSlack; /** Solver based on Newton Raphson, using the NICSLU linear solver (needs a specific license), only suitable for the DC approximation**/ - typedef BaseDCSolver NICSLUDCSolver; + typedef BaseDCAlgo NICSLUDCSolver; /** Solver based on Fast Decoupled, using the NICSLU linear solver (needs a specific license)**/ - typedef BaseFDPFSolver FDPF_XB_NICSLUSolver; - typedef BaseFDPFSolver FDPF_BX_NICSLUSolver; + typedef BaseFDPFAlgo FDPF_XB_NICSLUSolver; + typedef BaseFDPFAlgo FDPF_BX_NICSLUSolver; #elif defined(_READ_THE_DOCS) // hack to display accurately the doc in read the doc even if the models are not compiled /** Solver based on Newton Raphson, using the NICSLU linear solver (needs a specific license)**/ @@ -74,14 +79,14 @@ typedef BaseFDPFSolver FDPF_BX_SparseLUSol #ifdef CKTSO_SOLVER_AVAILABLE /** Solver based on Newton Raphson, using the CKTSO linear solver (needs a specific license)**/ - typedef BaseNRSolver CKTSOSolver; + typedef BaseNRAlgo CKTSOSolver; /** Solver based on Newton Raphson, using the CKTSO linear solver (needs a specific license), do not consider multiple slack bus**/ - typedef BaseNRSolverSingleSlack CKTSOSolverSingleSlack; + typedef BaseNRSingleSlackAlgo CKTSOSolverSingleSlack; /** Solver based on Newton Raphson, using the CKTSO linear solver (needs a specific license), only suitable for the DC approximation**/ - typedef BaseDCSolver CKTSODCSolver; + typedef BaseDCAlgo CKTSODCSolver; /** Solver based on Fast Decoupled, using the CKTSO linear solver (needs a specific license)**/ - typedef BaseFDPFSolver FDPF_XB_CKTSOSolver; - typedef BaseFDPFSolver FDPF_BX_CKTSOSolver; + typedef BaseFDPFAlgo FDPF_XB_CKTSOSolver; + typedef BaseFDPFAlgo FDPF_BX_CKTSOSolver; #elif defined(_READ_THE_DOCS) // hack to display accurately the doc in read the doc even if the models are not compiled /** Solver based on Newton Raphson, using the CKTSO linear solver (needs a specific license)**/ @@ -94,3 +99,5 @@ typedef BaseFDPFSolver FDPF_BX_SparseLUSol class FDPF_XB_CKTSOSolver : public FDPF_XB_SparseLUSolver {}; class FDPF_BX_CKTSOSolver : public FDPF_BX_SparseLUSolver {}; #endif // CKTSO_SOLVER_AVAILABLE + +#endif // SOLVERS_H diff --git a/src/Utils.cpp b/src/Utils.cpp new file mode 100644 index 0000000..835bbb8 --- /dev/null +++ b/src/Utils.cpp @@ -0,0 +1,49 @@ +// 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 "Utils.h" + +std::ostream& operator<<(std::ostream& out, const ErrorType & error_type){ + switch (error_type) + { + case ErrorType::NoError: + out << "NoError"; + break; + case ErrorType::SingularMatrix: + out << "SingularMatrix"; + break; + case ErrorType::TooManyIterations: + out << "TooManyIterations"; + break; + case ErrorType::InifiniteValue: + out << "InifiniteValue"; + break; + case ErrorType::SolverAnalyze: + out << "SolverAnalyze"; + break; + case ErrorType::SolverFactor: + out << "SolverFactor"; + break; + case ErrorType::SolverReFactor: + out << "SolverReFactor"; + break; + case ErrorType::SolverSolve: + out << "SolverSolve"; + break; + case ErrorType::NotInitError: + out << "NotInitError"; + break; + case ErrorType::LicenseError: + out << "LicenseError"; + break; + default: + out << "unknown error (check utils.cpp)"; + break; + } + return out; +} diff --git a/src/Utils.h b/src/Utils.h index 49184ce..eeaec05 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -37,7 +37,17 @@ typedef Eigen::Matrix RealMat; typedef Eigen::Matrix CplxMat; // type of error in the different solvers -enum class ErrorType {NoError, SingularMatrix, TooManyIterations, InifiniteValue, SolverAnalyze, SolverFactor, SolverReFactor, SolverSolve, NotInitError, LicenseError}; +enum class ErrorType {NoError, + SingularMatrix, + TooManyIterations, + InifiniteValue, + SolverAnalyze, + SolverFactor, + SolverReFactor, + SolverSolve, + NotInitError, + LicenseError}; +std::ostream& operator<<(std::ostream& out, const ErrorType & error_type); // define some constant for compilation outside of "setup.py" #ifndef VERSION_MAJOR @@ -52,4 +62,100 @@ enum class ErrorType {NoError, SingularMatrix, TooManyIterations, InifiniteValue #define VERSION_MINOR -1 #endif +class SolverControl +{ + public: + SolverControl(): + change_dimension_(true), + pv_changed_(true), + pq_changed_(true), + slack_participate_changed_(true), + need_reset_solver_(true), + need_recompute_sbus_(true), + need_recompute_ybus_(true), + v_changed_(true), + slack_weight_changed_(true), + ybus_some_coeffs_zero_(true), + ybus_change_sparsity_pattern_(true) + {}; + + void tell_all_changed(){ + change_dimension_ = true; + pv_changed_ = true; + pq_changed_ = true; + slack_participate_changed_ = true; + need_reset_solver_ = true; + need_recompute_sbus_ = true; + need_recompute_ybus_ = true; + v_changed_ = true; + slack_weight_changed_ = true; + ybus_some_coeffs_zero_ = true; + ybus_change_sparsity_pattern_ = true; + } + + void tell_none_changed(){ + change_dimension_ = false; + pv_changed_ = false; + pq_changed_ = false; + slack_participate_changed_ = false; + need_reset_solver_ = false; + need_recompute_sbus_ = false; + need_recompute_ybus_ = false; + v_changed_ = false; + slack_weight_changed_ = false; + ybus_some_coeffs_zero_ = false; + ybus_change_sparsity_pattern_ = false; + } + + // the dimension of the Ybus matrix / Sbus vector has changed (eg. topology changes) + void tell_dimension_changed(){change_dimension_ = true;} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. + // some pv generators are now pq or the opposite + void tell_pv_changed(){pv_changed_ = true;} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. + // some pq nodes are now pv or the opposite + void tell_pq_changed(){pq_changed_ = true;} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. + // some generators that participated to the slack bus now do not, or the opposite + void tell_slack_participate_changed(){slack_participate_changed_ = true;} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. + // ybus need to be recomputed for some reason + void tell_recompute_ybus(){need_recompute_ybus_ = true;} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. + // sbus need to be recomputed for some reason + void tell_recompute_sbus(){need_recompute_sbus_ = true;} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. + // solver needs to be reset from scratch for some reason + void tell_solver_need_reset(){need_reset_solver_ = true;} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. + // the sparsity pattern of ybus changed + void tell_ybus_change_sparsity_pattern(){ybus_change_sparsity_pattern_ = true;} //should be used after the powerflow as run, so some vectors will not be recomputed if not needed. + // tell at least one generator changed its v setpoint + void tell_v_changed(){v_changed_ = true;} + // at least one generator has changed its slack participation + void tell_slack_weight_changed(){slack_weight_changed_ = true;} + // tells that some coeff of ybus might have been set to 0. + // (and ybus compressed again, so these coeffs are really completely hidden) + // might need to trigger some recomputation of some solvers (eg NR based ones) + void tell_ybus_some_coeffs_zero(){ybus_some_coeffs_zero_ = true;} + + bool has_dimension_changed() const {return change_dimension_;} + bool has_pv_changed() const {return pv_changed_;} + bool has_pq_changed() const {return pq_changed_;} + bool has_slack_participate_changed() const {return slack_participate_changed_;} + bool need_reset_solver() const {return need_reset_solver_;} + bool need_recompute_sbus() const {return need_recompute_sbus_;} + bool need_recompute_ybus() const {return need_recompute_ybus_;} + bool ybus_change_sparsity_pattern() const {return ybus_change_sparsity_pattern_;} + bool has_slack_weight_changed() const {return slack_weight_changed_;} + bool has_v_changed() const {return v_changed_;} + bool has_ybus_some_coeffs_zero() const {return ybus_some_coeffs_zero_;} + + protected: + bool change_dimension_; + bool pv_changed_; + bool pq_changed_; + bool slack_participate_changed_; + bool need_reset_solver_; // some matrices change size, needs to be computed + bool need_recompute_sbus_; // some coeff of sbus changed, need to recompute it + bool need_recompute_ybus_; // some coeff of ybus changed, but not its sparsity pattern + bool v_changed_; + bool slack_weight_changed_; + bool ybus_some_coeffs_zero_; // tells that some coeff of ybus might have been set to 0. (and ybus compressed again, so these coeffs are really completely hidden) + bool ybus_change_sparsity_pattern_; // sparsity pattern of ybus changed (and so are its coeff), or ybus change of dimension +}; + #endif // UTILS_H diff --git a/src/BaseMultiplePowerflow.cpp b/src/batch_algorithm/BaseBatchSolverSynch.cpp similarity index 88% rename from src/BaseMultiplePowerflow.cpp rename to src/batch_algorithm/BaseBatchSolverSynch.cpp index 6793869..23cc935 100644 --- a/src/BaseMultiplePowerflow.cpp +++ b/src/batch_algorithm/BaseBatchSolverSynch.cpp @@ -6,12 +6,12 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "BaseMultiplePowerflow.h" +#include "BaseBatchSolverSynch.h" /** V is modified at each call ! **/ -bool BaseMultiplePowerflow::compute_one_powerflow(const Eigen::SparseMatrix & Ybus, +bool BaseBatchSolverSynch::compute_one_powerflow(const Eigen::SparseMatrix & Ybus, CplxVect & V, const CplxVect & Sbus, const Eigen::VectorXi & slack_ids, @@ -22,6 +22,7 @@ bool BaseMultiplePowerflow::compute_one_powerflow(const Eigen::SparseMatrix RealMat; typedef Eigen::Matrix CplxMat; - BaseMultiplePowerflow(const GridModel & init_grid_model): + BaseBatchSolverSynch(const GridModel & init_grid_model): _grid_model(init_grid_model), n_line_(init_grid_model.nb_powerline()), n_trafos_(init_grid_model.nb_trafo()), @@ -40,14 +42,17 @@ class BaseMultiplePowerflow CplxVect V = CplxVect::Constant(nb_bus, 1.04); // const auto & Vtmp = init_grid_model.get_V(); // for(int i = 0; i < Vtmp.size(); ++i) V[i] = Vtmp[i]; + _grid_model.tell_solver_need_reset(); _grid_model.dc_pf(V, 10, 1e-5); _grid_model.ac_pf(V, 10, 1e-5); // assign the right solver type + _solver_control.tell_none_changed(); _solver.change_solver(_grid_model.get_solver_type()); + _solver.tell_solver_control(_solver_control); } - BaseMultiplePowerflow(const BaseMultiplePowerflow&) = delete; + BaseBatchSolverSynch(const BaseBatchSolverSynch&) = delete; // solver "control" void change_solver(const SolverType & type){ @@ -235,5 +240,9 @@ class BaseMultiplePowerflow double _timer_compute_P; double _timer_solver; + // solver control + SolverControl _solver_control; + }; + #endif // BASEMULTIPLEPOWERFLOW_H diff --git a/src/SecurityAnalysis.cpp b/src/batch_algorithm/ContingencyAnalysis.cpp similarity index 91% rename from src/SecurityAnalysis.cpp rename to src/batch_algorithm/ContingencyAnalysis.cpp index f4d5c9a..1118289 100644 --- a/src/SecurityAnalysis.cpp +++ b/src/batch_algorithm/ContingencyAnalysis.cpp @@ -6,11 +6,12 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "SecurityAnalysis.h" +#include "ContingencyAnalysis.h" + #include #include /* isfinite */ -bool SecurityAnalysis::check_invertible(const Eigen::SparseMatrix & Ybus) const{ +bool ContingencyAnalysis::check_invertible(const Eigen::SparseMatrix & Ybus) const{ std::vector visited(Ybus.cols(), false); std::vector already_added(Ybus.cols(), false); std::queue neighborhood; @@ -43,7 +44,7 @@ bool SecurityAnalysis::check_invertible(const Eigen::SparseMatrix & Y return ok; } -void SecurityAnalysis::init_li_coeffs(bool ac_solver_used){ +void ContingencyAnalysis::init_li_coeffs(bool ac_solver_used){ _li_coeffs.clear(); _li_coeffs.reserve(_li_defaults.size()); const auto & powerlines = _grid_model.get_powerlines_as_data(); @@ -89,7 +90,7 @@ void SecurityAnalysis::init_li_coeffs(bool ac_solver_used){ } } - if(bus_1_id != DataGeneric::_deactivated_bus_id && bus_2_id != DataGeneric::_deactivated_bus_id) + if(bus_1_id != GenericContainer::_deactivated_bus_id && bus_2_id != GenericContainer::_deactivated_bus_id) { // element is connected this_cont_coeffs.push_back({bus_1_id, bus_1_id, y_ff}); @@ -103,7 +104,7 @@ void SecurityAnalysis::init_li_coeffs(bool ac_solver_used){ } -bool SecurityAnalysis::remove_from_Ybus(Eigen::SparseMatrix & Ybus, +bool ContingencyAnalysis::remove_from_Ybus(Eigen::SparseMatrix & Ybus, const std::vector & coeffs) const { for(const auto & coeff_to_remove: coeffs){ @@ -111,7 +112,7 @@ bool SecurityAnalysis::remove_from_Ybus(Eigen::SparseMatrix & Ybus, } return check_invertible(Ybus); } -void SecurityAnalysis::readd_to_Ybus(Eigen::SparseMatrix & Ybus, +void ContingencyAnalysis::readd_to_Ybus(Eigen::SparseMatrix & Ybus, const std::vector & coeffs) const { for(const auto & coeff_to_remove: coeffs){ @@ -119,7 +120,7 @@ void SecurityAnalysis::readd_to_Ybus(Eigen::SparseMatrix & Ybus, } } -void SecurityAnalysis::compute(const CplxVect & Vinit, int max_iter, real_type tol) +void ContingencyAnalysis::compute(const CplxVect & Vinit, int max_iter, real_type tol) { auto timer = CustTimer(); auto timer_preproc = CustTimer(); @@ -159,11 +160,15 @@ void SecurityAnalysis::compute(const CplxVect & Vinit, int max_iter, real_type t Eigen::Index nb_steps = _li_defaults.size(); // init the results matrices - _voltages = BaseMultiplePowerflow::CplxMat::Zero(nb_steps, nb_total_bus); + _voltages = BaseBatchSolverSynch::CplxMat::Zero(nb_steps, nb_total_bus); _amps_flows = RealMat::Zero(0, n_total_); // reset the solver _solver.reset(); + _solver_control.tell_none_changed(); + _solver_control.tell_recompute_ybus(); + // _solver_control.tell_ybus_some_coeffs_zero(); + // ybus does not change sparsity pattern here // compute the right Vinit to send to the solver CplxVect Vinit_solver = extract_Vsolver_from_Vinit(Vinit, nb_buses_solver, nb_total_bus, id_me_to_solver); @@ -210,7 +215,7 @@ void SecurityAnalysis::compute(const CplxVect & Vinit, int max_iter, real_type t _timer_total = timer.duration(); } -void SecurityAnalysis::clean_flows(bool is_amps) +void ContingencyAnalysis::clean_flows(bool is_amps) { auto timer = CustTimer(); Eigen::Index cont_id = 0; diff --git a/src/SecurityAnalysis.h b/src/batch_algorithm/ContingencyAnalysis.h similarity index 89% rename from src/SecurityAnalysis.h rename to src/batch_algorithm/ContingencyAnalysis.h index ce7fe2d..052722e 100644 --- a/src/SecurityAnalysis.h +++ b/src/batch_algorithm/ContingencyAnalysis.h @@ -9,7 +9,7 @@ #ifndef SECURITYANALYSIS_H #define SECURITYANALYSIS_H -#include "BaseMultiplePowerflow.h" +#include "BaseBatchSolverSynch.h" #include struct Coeff{ @@ -19,20 +19,22 @@ struct Coeff{ }; /** -Class to perform a security analysis, which consist of performing some powerflow after some powerlines +Class to perform a contingency analysis (security analysis), which consist of performing some powerflow after some powerlines have been disconnected **/ -class SecurityAnalysis: public BaseMultiplePowerflow +class ContingencyAnalysis: public BaseBatchSolverSynch { public: - SecurityAnalysis(const GridModel & init_grid_model): - BaseMultiplePowerflow(init_grid_model), - _li_defaults(), - _li_coeffs(), - _timer_total(0.), - _timer_modif_Ybus(0.), - _timer_pre_proc(0.) - { } + ContingencyAnalysis(const GridModel & init_grid_model): + BaseBatchSolverSynch(init_grid_model), + _li_defaults(), + _li_coeffs(), + _timer_total(0.), + _timer_modif_Ybus(0.), + _timer_pre_proc(0.) + { } + + ContingencyAnalysis(const ContingencyAnalysis&) = delete; // utilities to add defaults to simulate void add_all_n1(){ @@ -65,7 +67,7 @@ class SecurityAnalysis: public BaseMultiplePowerflow // utilities to remove defaults to simulate (TODO) virtual void clear(){ - BaseMultiplePowerflow::clear(); + BaseBatchSolverSynch::clear(); _li_defaults.clear(); _li_coeffs.clear(); _timer_total = 0.; diff --git a/src/Computers.cpp b/src/batch_algorithm/TimeSeries.cpp similarity index 85% rename from src/Computers.cpp rename to src/batch_algorithm/TimeSeries.cpp index 2641c7b..52b4edb 100644 --- a/src/Computers.cpp +++ b/src/batch_algorithm/TimeSeries.cpp @@ -6,23 +6,23 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "Computers.h" +#include "TimeSeries.h" #include #include -int Computers::compute_Vs(Eigen::Ref gen_p, - Eigen::Ref sgen_p, - Eigen::Ref load_p, - Eigen::Ref load_q, - const CplxVect & Vinit, - const int max_iter, - const real_type tol) +int TimeSeries::compute_Vs(Eigen::Ref gen_p, + Eigen::Ref sgen_p, + Eigen::Ref load_p, + Eigen::Ref load_q, + const CplxVect & Vinit, + const int max_iter, + const real_type tol) { auto timer = CustTimer(); const Eigen::Index nb_total_bus = _grid_model.total_bus(); if(Vinit.size() != nb_total_bus){ std::ostringstream exc_; - exc_ << "Computers::compute_Sbuses: Size of the Vinit should be the same as the total number of buses. Currently: "; + exc_ << "TimeSeries::compute_Sbuses: Size of the Vinit should be the same as the total number of buses. Currently: "; exc_ << "Vinit: " << Vinit.size() << " and there are " << nb_total_bus << " buses."; exc_ << "(fyi: Components of Vinit corresponding to deactivated bus will be ignored anyway, so you can put whatever you want there)."; throw std::runtime_error(exc_.str()); @@ -56,6 +56,8 @@ int Computers::compute_Vs(Eigen::Ref gen_p, const Eigen::VectorXi & slack_ids = ac_solver_used ? _grid_model.get_slack_ids(): _grid_model.get_slack_ids_dc(); const RealVect & slack_weights = _grid_model.get_slack_weights(); _solver.reset(); + _solver_control.tell_none_changed(); + _solver_control.tell_recompute_sbus(); // now build the Sbus _Sbuses = CplxMat::Zero(nb_steps, nb_buses_solver); @@ -70,7 +72,7 @@ int Computers::compute_Vs(Eigen::Ref gen_p, // TODO trafo hack for Sbus ! // init the results matrices - _voltages = BaseMultiplePowerflow::CplxMat::Zero(nb_steps, nb_total_bus); + _voltages = BaseBatchSolverSynch::CplxMat::Zero(nb_steps, nb_total_bus); _amps_flows = RealMat::Zero(0, n_total_); // extract V solver from the given V diff --git a/src/Computers.h b/src/batch_algorithm/TimeSeries.h similarity index 94% rename from src/Computers.h rename to src/batch_algorithm/TimeSeries.h index 091583e..6a20345 100644 --- a/src/Computers.h +++ b/src/batch_algorithm/TimeSeries.h @@ -9,17 +9,17 @@ #ifndef COMPUTERS_H #define COMPUTERS_H -#include "BaseMultiplePowerflow.h" +#include "BaseBatchSolverSynch.h" /** Allws the computation of time series, that is, the same grid topology is used along with time series of injections (productions and loads) to compute powerflows/ **/ -class Computers: public BaseMultiplePowerflow +class TimeSeries: public BaseBatchSolverSynch { public: - Computers(const GridModel & init_grid_model): - BaseMultiplePowerflow(init_grid_model), + TimeSeries(const GridModel & init_grid_model): + BaseBatchSolverSynch(init_grid_model), _Sbuses(), _status(1), // 1: success, 0: failure _compute_flows(true), @@ -27,7 +27,7 @@ class Computers: public BaseMultiplePowerflow _timer_pre_proc(0.) {} - Computers(const Computers&) = delete; + TimeSeries(const TimeSeries&) = delete; // control on whether I compute the flows or not void deactivate_flow_computations() {_compute_flows = false;} @@ -55,7 +55,7 @@ class Computers: public BaseMultiplePowerflow const real_type tol); virtual void clear(){ - BaseMultiplePowerflow::clear(); + BaseBatchSolverSynch::clear(); _Sbuses = CplxMat(); _status = 1; _compute_flows = true; diff --git a/src/DataDCLine.cpp b/src/element_container/DCLineContainer.cpp similarity index 64% rename from src/DataDCLine.cpp rename to src/element_container/DCLineContainer.cpp index 574f7cd..01a683b 100644 --- a/src/DataDCLine.cpp +++ b/src/element_container/DCLineContainer.cpp @@ -6,25 +6,26 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "DataDCLine.h" +#include "DCLineContainer.h" + #include #include -DataDCLine::StateRes DataDCLine::get_state() const +DCLineContainer::StateRes DCLineContainer::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(names_, - from_gen_.get_state(), - to_gen_.get_state(), - loss_percent, - loss_mw, - status); + std::vector status = status_; + DCLineContainer::StateRes res(names_, + from_gen_.get_state(), + to_gen_.get_state(), + loss_percent, + loss_mw, + status); return res; } -void DataDCLine::set_state(DataDCLine::StateRes & my_state){ +void DCLineContainer::set_state(DCLineContainer::StateRes & my_state){ reset_results(); names_ = std::get<0>(my_state); from_gen_.set_state(std::get<1>(my_state)); @@ -37,17 +38,17 @@ void DataDCLine::set_state(DataDCLine::StateRes & my_state){ 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){ +void DCLineContainer::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); @@ -61,7 +62,7 @@ void DataDCLine::init(const Eigen::VectorXi & branch_from_id, to_gen_.init(p_ex, vm_ex_pu, min_q_ex, max_q_ex, branch_to_id); } -void DataDCLine::nb_line_end(std::vector & res) const +void DCLineContainer::nb_line_end(std::vector & res) const { const Eigen::Index nb = from_gen_.nb(); const auto & bus_or_id = get_bus_id_or(); @@ -76,22 +77,21 @@ void DataDCLine::nb_line_end(std::vector & res) const } // TODO DC LINE: one side might be in the connected comp and not the other ! -void DataDCLine::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component) +void DCLineContainer::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component) { const Eigen::Index nb = from_gen_.nb(); const auto & bus_or_id = get_bus_id_or(); const auto & bus_ex_id = get_bus_id_ex(); + SolverControl unused_solver_control; for(Eigen::Index i = 0; i < nb; ++i){ if(!status_[i]) continue; auto bus_or = bus_or_id(i); auto bus_ex = bus_ex_id(i); if(!busbar_in_main_component[bus_or]) { - bool tmp = false; - from_gen_.deactivate(i, tmp); + from_gen_.deactivate(i, unused_solver_control); } if(!busbar_in_main_component[bus_ex]) { - bool tmp = false; - to_gen_.deactivate(i, tmp); + to_gen_.deactivate(i, unused_solver_control); } // if(!busbar_in_main_component[bus_or] || !busbar_in_main_component[bus_ex]){ // bool tmp = false; diff --git a/src/DataDCLine.h b/src/element_container/DCLineContainer.h similarity index 80% rename from src/DataDCLine.h rename to src/element_container/DCLineContainer.h index fbcba63..a64fb1f 100644 --- a/src/DataDCLine.h +++ b/src/element_container/DCLineContainer.h @@ -6,23 +6,22 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef DATADCLINE_H -#define DATADCLINE_H +#ifndef DCLINECONTAINER_H +#define DCLINECONTAINER_H #include -#include "Utils.h" - #include "Eigen/Core" #include "Eigen/Dense" #include "Eigen/SparseCore" #include "Eigen/SparseLU" -#include "DataGeneric.h" -#include "DataGen.h" +#include "Utils.h" +#include "GenericContainer.h" +#include "GeneratorContainer.h" -class DataDCLine : public DataGeneric +class DCLineContainer : public GenericContainer { public: class DCLineInfo @@ -39,8 +38,8 @@ class DataDCLine : public DataGeneric real_type target_vm_ex_pu; real_type loss_pct; real_type loss_mw; - DataGen::GenInfo gen_or; - DataGen::GenInfo gen_ex; + GeneratorContainer::GenInfo gen_or; + GeneratorContainer::GenInfo gen_ex; bool has_res; real_type res_p_or_mw; @@ -52,7 +51,7 @@ class DataDCLine : public DataGeneric real_type res_v_ex_kv; real_type res_theta_ex_deg; - DCLineInfo(const DataDCLine & r_data_dcline, int my_id): + DCLineInfo(const DCLineContainer & r_data_dcline, int my_id): id(-1), name(""), connected(false), @@ -109,13 +108,13 @@ class DataDCLine : public DataGeneric typedef DCLineInfo DataInfo; private: - typedef DataConstIterator DataDCLineConstIterator; + typedef GenericContainerConstIterator DCLineConstIterator; public: typedef std::tuple< std::vector, - DataGen::StateRes, - DataGen::StateRes, + GeneratorContainer::StateRes, + GeneratorContainer::StateRes, std::vector, // loss_percent std::vector, // vm_to_pu std::vector // loss_mw @@ -124,9 +123,9 @@ class DataDCLine : public DataGeneric int nb() const { return static_cast(from_gen_.nb()); } // iterator - typedef DataDCLineConstIterator const_iterator_type; - const_iterator_type begin() const {return DataDCLineConstIterator(this, 0); } - const_iterator_type end() const {return DataDCLineConstIterator(this, nb()); } + typedef DCLineConstIterator const_iterator_type; + const_iterator_type begin() const {return DCLineConstIterator(this, 0); } + const_iterator_type end() const {return DCLineConstIterator(this, nb()); } DCLineInfo operator[](int id) const { if(id < 0) @@ -141,11 +140,11 @@ class DataDCLine : public DataGeneric } // underlying generators are not pv when powerline is off - DataDCLine(): from_gen_(false), to_gen_(false) {}; + DCLineContainer(): from_gen_(false), to_gen_(false) {}; // pickle - DataDCLine::StateRes get_state() const; - void set_state(DataDCLine::StateRes & my_state); + DCLineContainer::StateRes get_state() const; + void set_state(DCLineContainer::StateRes & my_state); // TODO min_p, max_p void init(const Eigen::VectorXi & branch_from_id, @@ -162,20 +161,20 @@ class DataDCLine : public DataGeneric ); // accessor / modifiers - void deactivate(int dcline_id, bool & need_reset) { - _deactivate(dcline_id, status_, need_reset); - from_gen_.deactivate(dcline_id, need_reset); - to_gen_.deactivate(dcline_id, need_reset); + void deactivate(int dcline_id, SolverControl & solver_control) { + _deactivate(dcline_id, status_); + from_gen_.deactivate(dcline_id, solver_control); + to_gen_.deactivate(dcline_id, solver_control); } - void reactivate(int dcline_id, bool & need_reset) { - _reactivate(dcline_id, status_, need_reset); - from_gen_.reactivate(dcline_id, need_reset); - to_gen_.reactivate(dcline_id, need_reset); + void reactivate(int dcline_id, SolverControl & solver_control) { + _reactivate(dcline_id, status_); + from_gen_.reactivate(dcline_id, solver_control); + to_gen_.reactivate(dcline_id, solver_control); } - void change_bus_or(int dcline_id, int new_bus_id, bool & need_reset, int nb_bus) { - from_gen_.change_bus(dcline_id, new_bus_id, need_reset, nb_bus);} - void change_bus_ex(int dcline_id, int new_bus_id, bool & need_reset, int nb_bus) { - to_gen_.change_bus(dcline_id, new_bus_id, need_reset, nb_bus);} + void change_bus_or(int dcline_id, int new_bus_id, SolverControl & solver_control, int nb_bus) { + from_gen_.change_bus(dcline_id, new_bus_id, solver_control, nb_bus);} + void change_bus_ex(int dcline_id, int new_bus_id, SolverControl & solver_control, int nb_bus) { + to_gen_.change_bus(dcline_id, new_bus_id, solver_control, nb_bus);} int get_bus_or(int dcline_id) {return from_gen_.get_bus(dcline_id);} int get_bus_ex(int dcline_id) {return to_gen_.get_bus(dcline_id);} @@ -187,10 +186,14 @@ class DataDCLine : public DataGeneric } // for buses only connected through dc line, i don't add them // they are not in the same "connected component" - virtual void get_graph(std::vector > & res) const {}; + virtual void get_graph(std::vector > & res) const {}; virtual void disconnect_if_not_in_main_component(std::vector & busbar_in_main_component); virtual void nb_line_end(std::vector & res) const; - + virtual void update_bus_status(std::vector & bus_status) const { + from_gen_.update_bus_status(bus_status); + to_gen_.update_bus_status(bus_status); + } + real_type get_qmin_or(int dcline_id) {return from_gen_.get_qmin(dcline_id);} real_type get_qmax_or(int dcline_id) {return from_gen_.get_qmax(dcline_id);} real_type get_qmin_ex(int dcline_id) {return to_gen_.get_qmin(dcline_id);} @@ -204,16 +207,16 @@ class DataDCLine : public DataGeneric ; return new_p_ext; } - void change_p(int dcline_id, real_type new_p, bool & need_reset){ - from_gen_.change_p(dcline_id, -1.0 * new_p, need_reset); + void change_p(int dcline_id, real_type new_p, SolverControl & sovler_control){ + from_gen_.change_p(dcline_id, -1.0 * new_p, sovler_control); - to_gen_.change_p(dcline_id, -1.0 * get_to_mw(dcline_id, new_p), need_reset); + to_gen_.change_p(dcline_id, -1.0 * get_to_mw(dcline_id, new_p), sovler_control); } - void change_v_or(int dcline_id, real_type new_v_pu, bool & need_reset){ - from_gen_.change_v(dcline_id, new_v_pu, need_reset); + void change_v_or(int dcline_id, real_type new_v_pu, SolverControl & sovler_control){ + from_gen_.change_v(dcline_id, new_v_pu, sovler_control); } - void change_v_ex(int dcline_id, real_type new_v_pu, bool & need_reset){ - to_gen_.change_v(dcline_id, new_v_pu, need_reset); + void change_v_ex(int dcline_id, real_type new_v_pu, SolverControl & sovler_control){ + to_gen_.change_v(dcline_id, new_v_pu, sovler_control); } // solver stuff @@ -224,7 +227,7 @@ class DataDCLine : public DataGeneric virtual void fillpv(std::vector& bus_pv, std::vector & has_bus_been_added, - Eigen::VectorXi & slack_bus_id_solver, + const Eigen::VectorXi & slack_bus_id_solver, const std::vector & id_grid_to_solver) const { from_gen_.fillpv(bus_pv, has_bus_been_added, slack_bus_id_solver, id_grid_to_solver); to_gen_.fillpv(bus_pv, has_bus_been_added, slack_bus_id_solver, id_grid_to_solver); @@ -297,12 +300,12 @@ class DataDCLine : public DataGeneric protected: // it is modeled as 2 generators that are "linked" together // see https://pandapower.readthedocs.io/en/v2.0.1/elements/dcline.html#electric-model - DataGen from_gen_; - DataGen to_gen_; + GeneratorContainer from_gen_; + GeneratorContainer to_gen_; RealVect loss_percent_; RealVect loss_mw_; std::vector status_; }; -#endif //DATADCLINE_H \ No newline at end of file +#endif //DCLINECONTAINER_H \ No newline at end of file diff --git a/src/DataGen.cpp b/src/element_container/GeneratorContainer.cpp similarity index 66% rename from src/DataGen.cpp rename to src/element_container/GeneratorContainer.cpp index db87472..7598301 100644 --- a/src/DataGen.cpp +++ b/src/element_container/GeneratorContainer.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-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,16 +6,23 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "DataGen.h" +#include "GeneratorContainer.h" #include #include -void DataGen::init(const RealVect & generators_p, - const RealVect & generators_v, - const RealVect & generators_min_q, - const RealVect & generators_max_q, - const Eigen::VectorXi & generators_bus_id) +void GeneratorContainer::init(const RealVect & generators_p, + const RealVect & generators_v, + const RealVect & generators_min_q, + const RealVect & generators_max_q, + const Eigen::VectorXi & generators_bus_id) { + int size = static_cast(generators_p.size()); + GenericContainer::check_size(generators_p, size, "generators_p"); + GenericContainer::check_size(generators_v, size, "generators_v"); + GenericContainer::check_size(generators_min_q, size, "generators_min_q"); + GenericContainer::check_size(generators_max_q, size, "generators_max_q"); + GenericContainer::check_size(generators_bus_id, size, "generators_bus_id"); + p_mw_ = generators_p; vm_pu_ = generators_v; bus_id_ = generators_bus_id; @@ -24,7 +31,7 @@ void DataGen::init(const RealVect & generators_p, if(min_q_.size() != max_q_.size()) { std::ostringstream exc_; - exc_ << "DataGen::init: Impossible to initialize generator with generators_min_q of size "; + exc_ << "GeneratorContainer::init: Impossible to initialize generator with generators_min_q of size "; exc_ << min_q_.size(); exc_ << " and generators_max_q of size "; exc_ << max_q_.size(); @@ -36,7 +43,7 @@ void DataGen::init(const RealVect & generators_p, if (min_q_(gen_id) > max_q_(gen_id)) { std::ostringstream exc_; - exc_ << "DataGen::init: Impossible to initialize generator min_q being above max_q for generator "; + exc_ << "GeneratorContainer::init: Impossible to initialize generator min_q being above max_q for generator "; exc_ << gen_id; throw std::runtime_error(exc_.str()); } @@ -49,22 +56,25 @@ void DataGen::init(const RealVect & generators_p, q_mvar_ = RealVect::Zero(generators_p.size()); } -void DataGen::init_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 - ) +void GeneratorContainer::init_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 + ) { init(generators_p, generators_v, generators_min_q, generators_max_q, generators_bus_id); + int size = static_cast(generators_p.size()); + GenericContainer::check_size(generators_q, size, "generators_q"); + GenericContainer::check_size(voltage_regulator_on, size, "voltage_regulator_on"); voltage_regulator_on_ = voltage_regulator_on; q_mvar_ = generators_q; } -DataGen::StateRes DataGen::get_state() const +GeneratorContainer::StateRes GeneratorContainer::get_state() const { std::vector p_mw(p_mw_.begin(), p_mw_.end()); std::vector vm_pu(vm_pu_.begin(), vm_pu_.end()); @@ -76,13 +86,13 @@ DataGen::StateRes DataGen::get_state() const std::vector slack_bus = gen_slackbus_; std::vector voltage_regulator_on = voltage_regulator_on_; std::vector slack_weight = gen_slack_weight_; - DataGen::StateRes res(names_, turnedoff_gen_pv_, voltage_regulator_on, + GeneratorContainer::StateRes res(names_, turnedoff_gen_pv_, voltage_regulator_on, p_mw, vm_pu, q_mvar, min_q, max_q, bus_id, status, slack_bus, slack_weight); return res; } -void DataGen::set_state(DataGen::StateRes & my_state) +void GeneratorContainer::set_state(GeneratorContainer::StateRes & my_state) { reset_results(); names_ = std::get<0>(my_state); @@ -114,7 +124,7 @@ void DataGen::set_state(DataGen::StateRes & my_state) gen_slack_weight_ = slack_weight; } -RealVect DataGen::get_slack_weights(Eigen::Index nb_bus_solver, const std::vector & id_grid_to_solver){ +RealVect GeneratorContainer::get_slack_weights(Eigen::Index nb_bus_solver, const std::vector & id_grid_to_solver){ const int nb_gen = nb(); int bus_id_me, bus_id_solver; RealVect res = RealVect::Zero(nb_bus_solver); @@ -126,7 +136,7 @@ RealVect DataGen::get_slack_weights(Eigen::Index nb_bus_solver, const std::vecto if(bus_id_solver == _deactivated_bus_id){ // TODO DEBUG MODE: only check in debug mode std::ostringstream exc_; - exc_ << "DataGen::get_slack_weights: Generator with id "; + exc_ << "GeneratorContainer::get_slack_weights: Generator with id "; exc_ << gen_id; exc_ << " is connected to a disconnected bus while being connected to the grid."; throw std::runtime_error(exc_.str()); @@ -139,7 +149,7 @@ RealVect DataGen::get_slack_weights(Eigen::Index nb_bus_solver, const std::vecto return res; } -void DataGen::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const { +void GeneratorContainer::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const { const int nb_gen = nb(); int bus_id_me, bus_id_solver; cplx_type tmp; @@ -152,7 +162,7 @@ void DataGen::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solv if(bus_id_solver == _deactivated_bus_id){ // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataGen::fillSbus: Generator with id "; + exc_ << "GeneratorContainer::fillSbus: Generator with id "; exc_ << gen_id; exc_ << " is connected to a disconnected bus while being connected to the grid."; throw std::runtime_error(exc_.str()); @@ -166,10 +176,10 @@ void DataGen::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solv } } -void DataGen::fillpv(std::vector & bus_pv, - std::vector & has_bus_been_added, - Eigen::VectorXi & slack_bus_id_solver, - const std::vector & id_grid_to_solver) const +void GeneratorContainer::fillpv(std::vector & bus_pv, + std::vector & has_bus_been_added, + const Eigen::VectorXi & slack_bus_id_solver, + const std::vector & id_grid_to_solver) const { const int nb_gen = nb(); int bus_id_me, bus_id_solver; @@ -184,7 +194,7 @@ void DataGen::fillpv(std::vector & bus_pv, if(bus_id_solver == _deactivated_bus_id){ // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataGen::fillpv: Generator with id "; + exc_ << "GeneratorContainer::fillpv: Generator with id "; exc_ << gen_id; exc_ << " is connected to a disconnected bus while being connected to the grid."; throw std::runtime_error(exc_.str()); @@ -197,13 +207,13 @@ void DataGen::fillpv(std::vector & bus_pv, } } -void DataGen::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) +void GeneratorContainer::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_gen = nb(); v_kv_from_vpu(Va, Vm, status_, nb_gen, bus_id_, id_grid_to_solver, bus_vn_kv, res_v_); @@ -211,15 +221,15 @@ void DataGen::compute_results(const Eigen::Ref & Va, res_p_ = p_mw_; } -void DataGen::reset_results(){ +void GeneratorContainer::reset_results(){ res_p_ = RealVect(); // in MW res_q_ = RealVect(); // in MVar res_v_ = RealVect(); // in kV res_theta_ = RealVect(); // in deg - bus_slack_weight_ = RealVect(); + // bus_slack_weight_ = RealVect(); } -void DataGen::get_vm_for_dc(RealVect & Vm){ +void GeneratorContainer::get_vm_for_dc(RealVect & Vm){ const int nb_gen = nb(); int bus_id_me; for(int gen_id = 0; gen_id < nb_gen; ++gen_id){ @@ -235,14 +245,14 @@ void DataGen::get_vm_for_dc(RealVect & Vm){ } } -void DataGen::change_p(int gen_id, real_type new_p, bool & need_reset) +void GeneratorContainer::change_p(int gen_id, real_type new_p, SolverControl & solver_control) { bool my_status = status_.at(gen_id); // and this check that load_id is not out of bound if(!my_status) { // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataGen::change_p: Impossible to change the active value of a disconnected generator (check gen. id "; + exc_ << "GeneratorContainer::change_p: Impossible to change the active value of a disconnected generator (check gen. id "; exc_ << gen_id; exc_ << ")"; throw std::runtime_error(exc_.str()); @@ -254,45 +264,52 @@ void DataGen::change_p(int gen_id, real_type new_p, bool & need_reset) // on the other hand, if all generators are pv then I do not need to refactorize in this case if((p_mw_(gen_id) == 0. && new_p != 0.) || (p_mw_(gen_id) != 0. && new_p == 0.)){ - need_reset = true; + solver_control.tell_pv_changed(); } } - p_mw_(gen_id) = new_p; + if (p_mw_(gen_id) != new_p){ + solver_control.tell_recompute_sbus(); + p_mw_(gen_id) = new_p; + } } -void DataGen::change_q(int gen_id, real_type new_q, bool & need_reset) +void GeneratorContainer::change_q(int gen_id, real_type new_q, SolverControl & solver_control) { bool my_status = status_.at(gen_id); // and this check that load_id is not out of bound if(!my_status) { // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataGen::change_q: Impossible to change the reactive value of a disconnected generator (check gen. id "; + exc_ << "GeneratorContainer::change_q: Impossible to change the reactive value of a disconnected generator (check gen. id "; exc_ << gen_id; exc_ << ")"; throw std::runtime_error(exc_.str()); } // TODO DEBUG MODE : raise an error if generator is regulating voltage, maybe ? // this would have not effect - q_mvar_(gen_id) = new_q; + if (q_mvar_(gen_id) != new_q){ + solver_control.tell_recompute_sbus(); + q_mvar_(gen_id) = new_q; + } } -void DataGen::change_v(int gen_id, real_type new_v_pu, bool & need_reset) +void GeneratorContainer::change_v(int gen_id, real_type new_v_pu, SolverControl & solver_control) { bool my_status = status_.at(gen_id); // and this check that load_id is not out of bound if(!my_status) { // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataGen::change_p: Impossible to change the voltage setpoint of a disconnected generator (check gen. id "; + exc_ << "GeneratorContainer::change_p: Impossible to change the voltage setpoint of a disconnected generator (check gen. id "; exc_ << gen_id; exc_ << ")"; throw std::runtime_error(exc_.str()); } + if (vm_pu_(gen_id) != new_v_pu) solver_control.tell_v_changed(); vm_pu_(gen_id) = new_v_pu; } -void DataGen::set_vm(CplxVect & V, const std::vector & id_grid_to_solver) const +void GeneratorContainer::set_vm(CplxVect & V, const std::vector & id_grid_to_solver) const { const int nb_gen = nb(); int bus_id_me, bus_id_solver; @@ -308,7 +325,7 @@ void DataGen::set_vm(CplxVect & V, const std::vector & id_grid_to_solver) c if(bus_id_solver == _deactivated_bus_id){ // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataGen::set_vm: Generator with id "; + exc_ << "GeneratorContainer::set_vm: Generator with id "; exc_ << gen_id; exc_ << " is connected to a disconnected bus while being connected to the grid."; throw std::runtime_error(exc_.str()); @@ -328,25 +345,29 @@ void DataGen::set_vm(CplxVect & V, const std::vector & id_grid_to_solver) c } } -std::vector DataGen::get_slack_bus_id() const{ - std::vector res; +Eigen::VectorXi GeneratorContainer::get_slack_bus_id() const{ + std::vector tmp; + tmp.reserve(gen_slackbus_.size()); + Eigen::VectorXi res; const auto nb_gen = nb(); for(int gen_id = 0; gen_id < nb_gen; ++gen_id){ if(gen_slackbus_[gen_id]){ const auto my_bus = bus_id_(gen_id); // do not add twice the same "slack bus" - if(!is_in_vect(my_bus, res)) res.push_back(my_bus); + if(!is_in_vect(my_bus, tmp)) tmp.push_back(my_bus); } } + if(tmp.empty()) throw std::runtime_error("GeneratorContainer::get_slack_bus_id: no generator are tagged slack bus for this grid."); + res = Eigen::VectorXi::Map(tmp.data(), tmp.size()); // force the copy of the data apparently return res; } -void DataGen::set_p_slack(const RealVect& node_mismatch, - const std::vector & id_grid_to_solver) +void GeneratorContainer::set_p_slack(const RealVect& node_mismatch, + const std::vector & id_grid_to_solver) { if(bus_slack_weight_.size() == 0){ // TODO DEBUG MODE: perform this check only in debug mode - throw std::runtime_error("Impossible to set the active value of generators for the slack bus"); + throw std::runtime_error("Generator::set_p_slack: Impossible to set the active value of generators for the slack bus: no known slack (you should haved called Generator::get_slack_weights first)"); } const auto nb_gen = nb(); for(int gen_id = 0; gen_id < nb_gen; ++gen_id){ @@ -365,10 +386,10 @@ void DataGen::set_p_slack(const RealVect& node_mismatch, } } -void DataGen::init_q_vector(int nb_bus, - Eigen::VectorXi & total_gen_per_bus, - RealVect & total_q_min_per_bus, - RealVect & total_q_max_per_bus) const // delta_q_per_gen_) // total number of bus on the grid +void GeneratorContainer::init_q_vector(int nb_bus, + Eigen::VectorXi & total_gen_per_bus, + RealVect & total_q_min_per_bus, + RealVect & total_q_max_per_bus) const { const int nb_gen = nb(); for(int gen_id = 0; gen_id < nb_gen; ++gen_id) @@ -385,12 +406,12 @@ void DataGen::init_q_vector(int nb_bus, } } -void DataGen::set_q(const RealVect & reactive_mismatch, - const std::vector & id_grid_to_solver, - bool ac, - const Eigen::VectorXi & total_gen_per_bus, - const RealVect & total_q_min_per_bus, - const RealVect & total_q_max_per_bus) +void GeneratorContainer::set_q(const RealVect & reactive_mismatch, + const std::vector & id_grid_to_solver, + bool ac, + const Eigen::VectorXi & total_gen_per_bus, + const RealVect & total_q_min_per_bus, + const RealVect & total_q_max_per_bus) { const int nb_gen = nb(); res_q_ = RealVect::Constant(nb_gen, 0.); @@ -424,8 +445,8 @@ void DataGen::set_q(const RealVect & reactive_mismatch, } -void DataGen::update_slack_weights(Eigen::Ref > could_be_slack, - bool & need_reset) +void GeneratorContainer::update_slack_weights(Eigen::Ref > could_be_slack, + SolverControl & solver_control) { const int nb_gen = nb(); for(int gen_id = 0; gen_id < nb_gen; ++gen_id) @@ -434,22 +455,22 @@ void DataGen::update_slack_weights(Eigen::Ref 0.){ // gen is properly connected - if(!gen_slackbus_[gen_id]) need_reset = true; // it was not in the slack before, so I need to reset the solver - add_slackbus(gen_id, p_mw_(gen_id)); + if(!gen_slackbus_[gen_id]) solver_control.tell_slack_participate_changed(); // it was not in the slack before, so I need to reset the solver + add_slackbus(gen_id, p_mw_(gen_id), solver_control); }else{ - // gen is now "turned off" - if(gen_slackbus_[gen_id]) need_reset = true; // it was in the slack before, so I need to reset the solver - remove_slackbus(gen_id); + // gen is now "turned off" (p_mw=0.) + if(gen_slackbus_[gen_id]) solver_control.tell_slack_participate_changed(); // it was in the slack before, so I need to reset the solver + remove_slackbus(gen_id, solver_control); } }else{ - if(gen_slackbus_[gen_id]) need_reset = true; // it was in the slack before, I need to reset the solver - remove_slackbus(gen_id); + if(gen_slackbus_[gen_id]) solver_control.tell_slack_participate_changed(); // it was in the slack before, I need to reset the solver + remove_slackbus(gen_id, solver_control); } } } -void DataGen::reconnect_connected_buses(std::vector & bus_status) const { +void GeneratorContainer::reconnect_connected_buses(std::vector & bus_status) const { const int nb_gen = nb(); for(int gen_id = 0; gen_id < nb_gen; ++gen_id) { @@ -458,7 +479,7 @@ void DataGen::reconnect_connected_buses(std::vector & bus_status) const { if(my_bus == _deactivated_bus_id){ // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataGen::reconnect_connected_buses: Generator with id "; + exc_ << "Generator::reconnect_connected_buses: Generator with id "; exc_ << gen_id; exc_ << " is connected to bus '-1' (meaning disconnected) while you said it was disconnected. Have you called `gridmodel.deactivate_gen(...)` ?."; throw std::runtime_error(exc_.str()); @@ -467,26 +488,26 @@ void DataGen::reconnect_connected_buses(std::vector & bus_status) const { } } -void DataGen::gen_p_per_bus(std::vector & res) const +void GeneratorContainer::gen_p_per_bus(std::vector & res) const { const int nb_gen = nb(); for(int gen_id = 0; gen_id < nb_gen; ++gen_id) { if(!status_[gen_id]) continue; const auto my_bus = bus_id_(gen_id); - res[my_bus] += p_mw_(gen_id); + if (p_mw_(gen_id) > 0.) res[my_bus] += p_mw_(gen_id); } } -void DataGen::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component){ +void GeneratorContainer::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component){ const int nb_gen = nb(); + SolverControl unused_solver_control; for(int gen_id = 0; gen_id < nb_gen; ++gen_id) { if(!status_[gen_id]) continue; const auto my_bus = bus_id_(gen_id); if(!busbar_in_main_component[my_bus]){ - bool tmp = false; - deactivate(gen_id, tmp); + deactivate(gen_id, unused_solver_control); } } } diff --git a/src/DataGen.h b/src/element_container/GeneratorContainer.h similarity index 67% rename from src/DataGen.h rename to src/element_container/GeneratorContainer.h index ae72d3d..c8c86e6 100644 --- a/src/DataGen.h +++ b/src/element_container/GeneratorContainer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-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,20 +6,19 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef DATAGEN_H -#define DATAGEN_H +#ifndef GENERATORCONTAINER_H +#define GENERATORCONTAINER_H #include #include -#include "Utils.h" - #include "Eigen/Core" #include "Eigen/Dense" #include "Eigen/SparseCore" #include "Eigen/SparseLU" -#include "DataGeneric.h" +#include "Utils.h" +#include "GenericContainer.h" /** This class represents the list of all generators. @@ -30,7 +29,7 @@ The convention used for the generator is the same as in pandapower: and for modeling of the Ybus matrix: https://pandapower.readthedocs.io/en/latest/elements/gen.html#electric-model **/ -class DataGen: public DataGeneric +class GeneratorContainer: public GenericContainer { public: class GenInfo @@ -57,7 +56,7 @@ class DataGen: public DataGeneric real_type res_v_kv; real_type res_theta_deg; - GenInfo(const DataGen & r_data_gen, int my_id): + GenInfo(const GeneratorContainer & r_data_gen, int my_id): id(-1), name(""), connected(false), @@ -108,7 +107,7 @@ class DataGen: public DataGeneric typedef GenInfo DataInfo; private: - typedef DataConstIterator DataGenConstIterator; + typedef GenericContainerConstIterator GeneratorConstIterator; public: typedef std::tuple< @@ -126,8 +125,8 @@ class DataGen: public DataGeneric std::vector // gen_slack_weight_ > StateRes; - DataGen():turnedoff_gen_pv_(true){}; - DataGen(bool turnedoff_gen_pv):turnedoff_gen_pv_(turnedoff_gen_pv) {}; + GeneratorContainer():turnedoff_gen_pv_(true){}; + GeneratorContainer(bool turnedoff_gen_pv):turnedoff_gen_pv_(turnedoff_gen_pv) {}; // TODO add pmin and pmax here ! void init(const RealVect & generators_p, @@ -149,9 +148,9 @@ class DataGen: public DataGeneric int nb() const { return static_cast(p_mw_.size()); } // iterator - typedef DataGenConstIterator const_iterator_type; - const_iterator_type begin() const {return DataGenConstIterator(this, 0); } - const_iterator_type end() const {return DataGenConstIterator(this, nb()); } + typedef GeneratorConstIterator const_iterator_type; + const_iterator_type begin() const {return GeneratorConstIterator(this, 0); } + const_iterator_type end() const {return GeneratorConstIterator(this, nb()); } GenInfo operator[](int id) const { if(id < 0) @@ -166,33 +165,40 @@ class DataGen: public DataGeneric } // pickle - DataGen::StateRes get_state() const; - void set_state(DataGen::StateRes & my_state ); + GeneratorContainer::StateRes get_state() const; + void set_state(GeneratorContainer::StateRes & my_state ); // slack handling /** we suppose that the data are correct (ie gen_id in the proper range, and weight > 0.) This is checked in GridModel, and not at this stage **/ - void add_slackbus(int gen_id, real_type weight){ + void add_slackbus(int gen_id, real_type weight, SolverControl & solver_control){ + // TODO DEBUG MODE + if(weight <= 0.) throw std::runtime_error("GeneratorContainer::add_slackbus Cannot assign a negative (<=0) weight to the slack bus."); + if(!gen_slackbus_[gen_id]) solver_control.tell_slack_participate_changed(); gen_slackbus_[gen_id] = true; + if(gen_slack_weight_[gen_id] != weight) solver_control.tell_slack_weight_changed(); gen_slack_weight_[gen_id] = weight; - // TODO DEBUG MODE - if(weight <= 0.) throw std::runtime_error("DataGen::add_slackbus Cannot assign a negative weight to the slack bus."); } - void remove_slackbus(int gen_id){ + void remove_slackbus(int gen_id, SolverControl & solver_control){ + if(gen_slackbus_[gen_id]) solver_control.tell_slack_participate_changed(); + if(gen_slack_weight_[gen_id] != 0.) solver_control.tell_slack_weight_changed(); gen_slackbus_[gen_id] = false; gen_slack_weight_[gen_id] = 0.; } void remove_all_slackbus(){ const int nb_gen = nb(); + SolverControl unused_solver_control; for(int gen_id = 0; gen_id < nb_gen; ++gen_id) { - remove_slackbus(gen_id); + remove_slackbus(gen_id, unused_solver_control); } } // returns only the gen_id with the highest p that is connected to this bus ! - int assign_slack_bus(int slack_bus_id, const std::vector & gen_p_per_bus){ + int assign_slack_bus(int slack_bus_id, + const std::vector & gen_p_per_bus, + SolverControl & solver_control){ const int nb_gen = nb(); int res_gen_id = -1; real_type max_p = -1.; @@ -201,14 +207,14 @@ class DataGen: public DataGeneric if(!status_[gen_id]) continue; if(bus_id_(gen_id) != slack_bus_id) continue; const real_type p_mw = p_mw_(gen_id); - add_slackbus(gen_id, p_mw / gen_p_per_bus[slack_bus_id]); + if (p_mw > 0.) add_slackbus(gen_id, p_mw / gen_p_per_bus[slack_bus_id], solver_control); if((p_mw > max_p) || (res_gen_id == -1) ){ res_gen_id = gen_id; max_p = p_mw; } } // TODO DEBUG MODE - if(res_gen_id == -1) throw std::runtime_error("DataGen::assign_slack_bus No generator connected to the desired buses"); + if(res_gen_id == -1) throw std::runtime_error("GeneratorContainer::assign_slack_bus No generator connected to the desired buses"); return res_gen_id; } @@ -217,33 +223,76 @@ class DataGen: public DataGeneric **/ RealVect get_slack_weights(Eigen::Index nb_bus_solver, const std::vector & id_grid_to_solver); - std::vector get_slack_bus_id() const; + Eigen::VectorXi get_slack_bus_id() const; void set_p_slack(const RealVect& node_mismatch, const std::vector & id_grid_to_solver); // modification - void turnedoff_no_pv(){turnedoff_gen_pv_=false;} // turned off generators are not pv - void turnedoff_pv(){turnedoff_gen_pv_=true;} // turned off generators are pv + void turnedoff_no_pv(SolverControl & solver_control){ + solver_control.tell_slack_participate_changed(); + solver_control.tell_slack_weight_changed(); + turnedoff_gen_pv_=false; // turned off generators are not pv. This is NOT the default. + } + void turnedoff_pv(SolverControl & solver_control){ + solver_control.tell_slack_participate_changed(); + solver_control.tell_slack_weight_changed(); + turnedoff_gen_pv_=true; // turned off generators are pv. This is the default. + } bool get_turnedoff_gen_pv() const {return turnedoff_gen_pv_;} void update_slack_weights(Eigen::Ref > could_be_slack, - bool & need_reset); - - void deactivate(int gen_id, bool & need_reset) {_deactivate(gen_id, status_, need_reset);} - void reactivate(int gen_id, bool & need_reset) {_reactivate(gen_id, status_, need_reset);} - void change_bus(int gen_id, int new_bus_id, bool & need_reset, int nb_bus) {_change_bus(gen_id, new_bus_id, bus_id_, need_reset, nb_bus);} + SolverControl & solver_control); + + void deactivate(int gen_id, SolverControl & solver_control) { + if (status_[gen_id]){ + solver_control.tell_recompute_sbus(); + solver_control.tell_pq_changed(); // bus might now be pq + if(voltage_regulator_on_[gen_id]) solver_control.tell_v_changed(); + solver_control.tell_pv_changed(); + if(gen_slack_weight_[gen_id] != 0. || gen_slackbus_[gen_id]){ + solver_control.tell_slack_participate_changed(); + solver_control.tell_slack_weight_changed(); + } + } + _deactivate(gen_id, status_); + } + void reactivate(int gen_id, SolverControl & solver_control) { + if(!status_[gen_id]){ + solver_control.tell_recompute_sbus(); + solver_control.tell_pq_changed(); // bus might now be pv + if(voltage_regulator_on_[gen_id]) solver_control.tell_v_changed(); + solver_control.tell_pv_changed(); + if(gen_slack_weight_[gen_id] != 0. || gen_slackbus_[gen_id]){ + solver_control.tell_slack_participate_changed(); + solver_control.tell_slack_weight_changed(); + } + } + _reactivate(gen_id, status_); + } + void change_bus(int gen_id, int new_bus_id, SolverControl & solver_control, int nb_bus) { + if (new_bus_id != bus_id_[gen_id]){ + if (gen_slack_weight_[gen_id] != 0. || gen_slackbus_[gen_id]) solver_control.has_slack_participate_changed(); + } + _change_bus(gen_id, new_bus_id, bus_id_, solver_control, nb_bus);} int get_bus(int gen_id) {return _get_bus(gen_id, status_, bus_id_);} virtual void reconnect_connected_buses(std::vector & bus_status) const; virtual void disconnect_if_not_in_main_component(std::vector & busbar_in_main_component); - + virtual void update_bus_status(std::vector & bus_status) const { + const int nb_gen = nb(); + for(int gen_id = 0; gen_id < nb_gen; ++gen_id) + { + if(!status_[gen_id]) continue; + bus_status[bus_id_[gen_id]] = true; + } + } real_type get_qmin(int gen_id) {return min_q_.coeff(gen_id);} real_type get_qmax(int gen_id) {return max_q_.coeff(gen_id);} - void change_p(int gen_id, real_type new_p, bool & need_reset); - void change_q(int gen_id, real_type new_q, bool & need_reset); - void change_v(int gen_id, real_type new_v_pu, bool & need_reset); + void change_p(int gen_id, real_type new_p, SolverControl & solver_control); + void change_v(int gen_id, real_type new_v_pu, SolverControl & solver_control); + void change_q(int gen_id, real_type new_q, SolverControl & solver_control); virtual void fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const; virtual void fillpv(std::vector& bus_pv, std::vector & has_bus_been_added, - Eigen::VectorXi & slack_bus_id_solver, + const Eigen::VectorXi & slack_bus_id_solver, const std::vector & id_grid_to_solver) const; void init_q_vector(int nb_bus, Eigen::VectorXi & total_gen_per_bus, @@ -316,4 +365,4 @@ class DataGen: public DataGeneric bool turnedoff_gen_pv_; // are turned off generators (including one with p=0) pv ? }; -#endif //DATAGEN_H +#endif //GENERATORCONTAINER_H diff --git a/src/DataGeneric.cpp b/src/element_container/GenericContainer.cpp similarity index 58% rename from src/DataGeneric.cpp rename to src/element_container/GenericContainer.cpp index 6d246d3..cf6dc51 100644 --- a/src/DataGeneric.cpp +++ b/src/element_container/GenericContainer.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-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,14 +6,15 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "DataGeneric.h" +#include "GenericContainer.h" + #include #include -const int DataGeneric::_deactivated_bus_id = -1; +const int GenericContainer::_deactivated_bus_id = -1; // TODO all functions bellow are generic ! Make a base class for that -void DataGeneric::_get_amps(RealVect & a, const RealVect & p, const RealVect & q, const RealVect & v){ +void GenericContainer::_get_amps(RealVect & a, const RealVect & p, const RealVect & q, const RealVect & v){ const real_type _1_sqrt_3 = 1.0 / std::sqrt(3.); RealVect p2q2 = p.array() * p.array() + q.array() * q.array(); p2q2 = p2q2.array().cwiseSqrt(); @@ -26,24 +27,25 @@ void DataGeneric::_get_amps(RealVect & a, const RealVect & p, const RealVect & q } a = p2q2.array() * _1_sqrt_3 / v_tmp.array(); } -void DataGeneric::_reactivate(int el_id, std::vector & status, bool & need_reset){ + +void GenericContainer::_reactivate(int el_id, std::vector & status){ bool val = status.at(el_id); - if(!val) need_reset = true; // I need to recompute the grid, if a status has changed status.at(el_id) = true; //TODO why it's needed to do that again } -void DataGeneric::_deactivate(int el_id, std::vector & status, bool & need_reset){ + +void GenericContainer::_deactivate(int el_id, std::vector & status){ bool val = status.at(el_id); - if(val) need_reset = true; // I need to recompute the grid, if a status has changed status.at(el_id) = false; //TODO why it's needed to do that again } -void DataGeneric::_change_bus(int el_id, int new_bus_me_id, Eigen::VectorXi & el_bus_ids, bool & need_reset, int nb_bus){ + +void GenericContainer::_change_bus(int el_id, int new_bus_me_id, Eigen::VectorXi & el_bus_ids, SolverControl & solver_control, int nb_bus){ // bus id here "me_id" and NOT "solver_id" // throw error: object id does not exist if(el_id >= el_bus_ids.size()) { // TODO DEBUG MODE: only check in debug mode std::ostringstream exc_; - exc_ << "DataGeneric::_change_bus: Cannot change the bus of element with id "; + exc_ << "GenericContainer::_change_bus: Cannot change the bus of element with id "; exc_ << el_id; exc_ << " while the grid counts "; exc_ << el_bus_ids.size(); @@ -54,7 +56,7 @@ void DataGeneric::_change_bus(int el_id, int new_bus_me_id, Eigen::VectorXi & el { // TODO DEBUG MODE: only check in debug mode std::ostringstream exc_; - exc_ << "DataGeneric::_change_bus: Cannot change the bus of element with id "; + exc_ << "GenericContainer::_change_bus: Cannot change the bus of element with id "; exc_ << el_id; exc_ << " (id should be >= 0)"; throw std::out_of_range(exc_.str()); @@ -65,7 +67,7 @@ void DataGeneric::_change_bus(int el_id, int new_bus_me_id, Eigen::VectorXi & el { // TODO DEBUG MODE: only check in debug mode std::ostringstream exc_; - exc_ << "DataGeneric::_change_bus: Cannot change an element to bus "; + exc_ << "GenericContainer::_change_bus: Cannot change an element to bus "; exc_ << new_bus_me_id; exc_ << " There are only "; exc_ << nb_bus; @@ -76,16 +78,25 @@ void DataGeneric::_change_bus(int el_id, int new_bus_me_id, Eigen::VectorXi & el { // TODO DEBUG MODE: only check in debug mode std::ostringstream exc_; - exc_ << "DataGeneric::_change_bus: new bus id should be >=0 and not "; + exc_ << "GenericContainer::_change_bus: new bus id should be >=0 and not "; exc_ << new_bus_me_id; throw std::out_of_range(exc_.str()); } int & bus_me_id = el_bus_ids(el_id); - if(bus_me_id != new_bus_me_id) need_reset = true; // in this case i changed the bus, i need to recompute the jacobian and reset the solver + + if(bus_me_id != new_bus_me_id) { + // TODO speed: here the dimension changed only if nothing was connected before + solver_control.tell_dimension_changed(); // in this case i changed the bus, i need to recompute the jacobian and reset the solver + + // TODO speed: sparsity pattern might not change if something is already there + solver_control.tell_ybus_change_sparsity_pattern(); + solver_control.tell_recompute_sbus(); // if a bus changed for load / generator + solver_control.tell_recompute_ybus(); // if a bus changed for shunts / line / trafo + } bus_me_id = new_bus_me_id; } -int DataGeneric::_get_bus(int el_id, const std::vector & status_, const Eigen::VectorXi & bus_id_) +int GenericContainer::_get_bus(int el_id, const std::vector & status_, const Eigen::VectorXi & bus_id_) { int res; bool val = status_.at(el_id); // also check if the el_id is out of bound @@ -96,14 +107,15 @@ int DataGeneric::_get_bus(int el_id, const std::vector & status_, const Ei return res; } -void DataGeneric::v_kv_from_vpu(const Eigen::Ref & Va, - const Eigen::Ref & Vm, - const std::vector & status, - int nb_element, - const Eigen::VectorXi & bus_me_id, - const std::vector & id_grid_to_solver, - const RealVect & bus_vn_kv, - RealVect & v){ +void GenericContainer::v_kv_from_vpu(const Eigen::Ref & Va, + const Eigen::Ref & Vm, + const std::vector & status, + int nb_element, + const Eigen::VectorXi & bus_me_id, + const std::vector & id_grid_to_solver, + const RealVect & bus_vn_kv, + RealVect & v) +{ v = RealVect::Constant(nb_element, -1.0); for(int el_id = 0; el_id < nb_element; ++el_id){ // if the element is disconnected, i leave it like that @@ -113,7 +125,7 @@ void DataGeneric::v_kv_from_vpu(const Eigen::Ref & Va, if(bus_solver_id == _deactivated_bus_id){ // TODO DEBUG MODE: only check in debug mode std::ostringstream exc_; - exc_ << "DataGeneric::v_kv_from_vpu: The element of id "; + exc_ << "GenericContainer::v_kv_from_vpu: The element of id "; exc_ << bus_solver_id; exc_ << " is connected to a disconnected bus"; throw std::runtime_error(exc_.str()); @@ -124,14 +136,14 @@ void DataGeneric::v_kv_from_vpu(const Eigen::Ref & Va, } -void DataGeneric::v_deg_from_va(const Eigen::Ref & Va, - const Eigen::Ref & Vm, - const std::vector & status, - int nb_element, - const Eigen::VectorXi & bus_me_id, - const std::vector & id_grid_to_solver, - const RealVect & bus_vn_kv, - RealVect & theta){ +void GenericContainer::v_deg_from_va(const Eigen::Ref & Va, + const Eigen::Ref & Vm, + const std::vector & status, + int nb_element, + const Eigen::VectorXi & bus_me_id, + const std::vector & id_grid_to_solver, + const RealVect & bus_vn_kv, + RealVect & theta){ theta = RealVect::Constant(nb_element, 0.0); for(int el_id = 0; el_id < nb_element; ++el_id){ // if the element is disconnected, i leave it like that @@ -141,7 +153,7 @@ void DataGeneric::v_deg_from_va(const Eigen::Ref & Va, if(bus_solver_id == _deactivated_bus_id){ // TODO DEBUG MODE: only check in debug mode std::ostringstream exc_; - exc_ << "DataGeneric::v_deg_from_va: The element of id "; + exc_ << "GenericContainer::v_deg_from_va: The element of id "; exc_ << bus_solver_id; exc_ << " is connected to a disconnected bus"; throw std::runtime_error(exc_.str()); diff --git a/src/DataGeneric.h b/src/element_container/GenericContainer.h similarity index 83% rename from src/DataGeneric.h rename to src/element_container/GenericContainer.h index 18d1a80..b1fa679 100644 --- a/src/DataGeneric.h +++ b/src/element_container/GenericContainer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-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,23 +6,22 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef DATAGENERIC_H -#define DATAGENERIC_H +#ifndef GENERIC_CONTAINER_H +#define GENERIC_CONTAINER_H #include // for std::find -#include "Utils.h" - #include "Eigen/Core" #include "Eigen/Dense" #include "Eigen/SparseCore" #include "Eigen/SparseLU" +#include "Utils.h" #include "BaseConstants.h" // iterator type template -class DataConstIterator +class GenericContainerConstIterator { protected: typedef typename DataType::DataInfo DataInfo; @@ -34,22 +33,22 @@ class DataConstIterator DataInfo my_info; // functions - DataConstIterator(const DataType * const data_, int id): + GenericContainerConstIterator(const DataType * const data_, int id): _p_data_(data_), my_id(id), my_info(*data_, id) {}; const DataInfo& operator*() const { return my_info; } - bool operator==(const DataConstIterator & other) const { return (my_id == other.my_id) && (_p_data_ == other._p_data_); } - bool operator!=(const DataConstIterator & other) const { return !(*this == other); } - DataConstIterator & operator++() + bool operator==(const GenericContainerConstIterator & other) const { return (my_id == other.my_id) && (_p_data_ == other._p_data_); } + bool operator!=(const GenericContainerConstIterator & other) const { return !(*this == other); } + GenericContainerConstIterator & operator++() { ++my_id; my_info = DataInfo(*_p_data_, my_id); return *this; } - DataConstIterator & operator--() + GenericContainerConstIterator & operator--() { --my_id; my_info = DataInfo(*_p_data_, my_id); @@ -63,7 +62,7 @@ class DataConstIterator /** Base class for every object that can be manipulated **/ -class DataGeneric : public BaseConstants +class GenericContainer : public BaseConstants { public: @@ -88,11 +87,12 @@ class DataGeneric : public BaseConstants virtual void fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const {}; virtual void fillpv(std::vector& bus_pv, std::vector & has_bus_been_added, - Eigen::VectorXi & slack_bus_id_solver, + const Eigen::VectorXi & slack_bus_id_solver, const std::vector & id_grid_to_solver) const {}; virtual void get_q(std::vector& q_by_bus) {}; - + virtual void update_bus_status(std::vector & bus_status) const {}; + void set_p_slack(const RealVect& node_mismatch, const std::vector & id_grid_to_solver) {}; static const int _deactivated_bus_id; @@ -109,7 +109,7 @@ class DataGeneric : public BaseConstants } /**"define" the destructor for compliance with clang (otherwise lots of warnings)**/ - virtual ~DataGeneric() {}; + virtual ~GenericContainer() {}; protected: std::vector names_; @@ -117,9 +117,9 @@ class DataGeneric : public BaseConstants /** activation / deactivation of elements **/ - void _reactivate(int el_id, std::vector & status, bool & need_reset); - void _deactivate(int el_id, std::vector & status, bool & need_reset); - void _change_bus(int el_id, int new_bus_me_id, Eigen::VectorXi & el_bus_ids, bool & need_reset, int nb_bus); + void _reactivate(int el_id, std::vector & status); + void _deactivate(int el_id, std::vector & status); + void _change_bus(int el_id, int new_bus_me_id, Eigen::VectorXi & el_bus_ids, SolverControl & solver_control, int nb_bus); int _get_bus(int el_id, const std::vector & status_, const Eigen::VectorXi & bus_id_); /** @@ -158,7 +158,7 @@ class DataGeneric : public BaseConstants template void check_size(const T & container, intType size, const std::string & container_name) { - if(container.size() != size) throw std::runtime_error(container_name + " do not have the proper size"); + if(static_cast(container.size()) != size) throw std::runtime_error(container_name + " do not have the proper size"); } /** @@ -168,5 +168,5 @@ class DataGeneric : public BaseConstants bool is_in_vect(int val, const T & cont) const {return std::find(cont.begin(), cont.end(), val) != cont.end();} }; -#endif // DATAGENERIC_H +#endif // GENERIC_CONTAINER_H diff --git a/src/DataLine.cpp b/src/element_container/LineContainer.cpp similarity index 80% rename from src/DataLine.cpp rename to src/element_container/LineContainer.cpp index acccf46..efeac7d 100644 --- a/src/DataLine.cpp +++ b/src/element_container/LineContainer.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-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,15 +6,16 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "DataLine.h" +#include "LineContainer.h" + #include -void DataLine::init(const RealVect & branch_r, - const RealVect & branch_x, - const CplxVect & branch_h, - const Eigen::VectorXi & branch_from_id, - const Eigen::VectorXi & branch_to_id - ) +void LineContainer::init(const RealVect & branch_r, + const RealVect & branch_x, + const CplxVect & branch_h, + const Eigen::VectorXi & branch_from_id, + const Eigen::VectorXi & branch_to_id + ) { /** This method initialize the Ybus matrix from the branch matrix. @@ -28,6 +29,13 @@ void DataLine::init(const RealVect & branch_r, //TODO consistency with trafo: have a converter methods to convert this value into pu, and store the pu // in this method + int size = static_cast(branch_r.size()); + GenericContainer::check_size(branch_r, size, "branch_r"); + GenericContainer::check_size(branch_x, size, "branch_x"); + GenericContainer::check_size(branch_h, size, "branch_h"); + GenericContainer::check_size(branch_from_id, size, "branch_from_id"); + GenericContainer::check_size(branch_to_id, size, "branch_to_id"); + bus_or_id_ = branch_from_id; bus_ex_id_ = branch_to_id; powerlines_h_or_ = 0.5 * branch_h; @@ -38,13 +46,13 @@ void DataLine::init(const RealVect & branch_r, _update_model_coeffs(); } -void DataLine::init(const RealVect & branch_r, - const RealVect & branch_x, - const CplxVect & branch_h_or, - const CplxVect & branch_h_ex, - const Eigen::VectorXi & branch_from_id, - const Eigen::VectorXi & branch_to_id - ) +void LineContainer::init(const RealVect & branch_r, + const RealVect & branch_x, + const CplxVect & branch_h_or, + const CplxVect & branch_h_ex, + const Eigen::VectorXi & branch_from_id, + const Eigen::VectorXi & branch_to_id + ) { /** This method initialize the Ybus matrix from the branch matrix. @@ -58,6 +66,14 @@ void DataLine::init(const RealVect & branch_r, //TODO consistency with trafo: have a converter methods to convert this value into pu, and store the pu // in this method + int size = static_cast(branch_r.size()); + GenericContainer::check_size(branch_r, size, "branch_r"); + GenericContainer::check_size(branch_x, size, "branch_x"); + GenericContainer::check_size(branch_h_or, size, "branch_h_or"); + GenericContainer::check_size(branch_h_ex, size, "branch_h_ex"); + GenericContainer::check_size(branch_from_id, size, "branch_from_id"); + GenericContainer::check_size(branch_to_id, size, "branch_to_id"); + bus_or_id_ = branch_from_id; bus_ex_id_ = branch_to_id; powerlines_h_or_ = branch_h_or; @@ -68,7 +84,7 @@ void DataLine::init(const RealVect & branch_r, _update_model_coeffs(); } -DataLine::StateRes DataLine::get_state() const +LineContainer::StateRes LineContainer::get_state() const { std::vector branch_r(powerlines_r_.begin(), powerlines_r_.end()); std::vector branch_x(powerlines_x_.begin(), powerlines_x_.end()); @@ -77,10 +93,10 @@ DataLine::StateRes DataLine::get_state() const std::vector branch_from_id(bus_or_id_.begin(), bus_or_id_.end()); std::vector branch_to_id(bus_ex_id_.begin(), bus_ex_id_.end()); std::vector status = status_; - DataLine::StateRes res(names_, branch_r, branch_x, branch_hor, branch_hex, branch_from_id, branch_to_id, status); + LineContainer::StateRes res(names_, branch_r, branch_x, branch_hor, branch_hex, branch_from_id, branch_to_id, status); return res; } -void DataLine::set_state(DataLine::StateRes & my_state) +void LineContainer::set_state(LineContainer::StateRes & my_state) { reset_results(); names_ = std::get<0>(my_state); @@ -107,7 +123,7 @@ void DataLine::set_state(DataLine::StateRes & my_state) _update_model_coeffs(); } -void DataLine::_update_model_coeffs() +void LineContainer::_update_model_coeffs() { const auto my_size = powerlines_r_.size(); @@ -143,15 +159,15 @@ void DataLine::_update_model_coeffs() } } -void DataLine::fillYbus_spmat(Eigen::SparseMatrix & res, bool ac, const std::vector & id_grid_to_solver) +void LineContainer::fillYbus_spmat(Eigen::SparseMatrix & res, bool ac, const std::vector & id_grid_to_solver) { throw std::runtime_error("You should not use that!"); } -void DataLine::fillYbus(std::vector > & res, - bool ac, - const std::vector & id_grid_to_solver, - real_type sn_mva) const +void LineContainer::fillYbus(std::vector > & res, + bool ac, + const std::vector & id_grid_to_solver, + real_type sn_mva) const { // fill the matrix //TODO template here instead of "if" for ac / dc @@ -169,7 +185,7 @@ void DataLine::fillYbus(std::vector > & res, int bus_or_solver_id = id_grid_to_solver[bus_or_id_me]; if(bus_or_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataLine::fillYbusBranch: the line with id "; + exc_ << "Line::fillYbusBranch: the line with id "; exc_ << line_id; exc_ << " is connected (or side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -178,7 +194,7 @@ void DataLine::fillYbus(std::vector > & res, int bus_ex_solver_id = id_grid_to_solver[bus_ex_id_me]; if(bus_ex_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataLine::fillYbusBranch: the line with id "; + exc_ << "Line::fillYbusBranch: the line with id "; exc_ << line_id; exc_ << " is connected (ex side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -204,11 +220,11 @@ void DataLine::fillYbus(std::vector > & res, } } -void DataLine::fillBp_Bpp(std::vector > & Bp, - std::vector > & Bpp, - const std::vector & id_grid_to_solver, - real_type sn_mva, - FDPFMethod xb_or_bx) const +void LineContainer::fillBp_Bpp(std::vector > & Bp, + std::vector > & Bpp, + const std::vector & id_grid_to_solver, + real_type sn_mva, + FDPFMethod xb_or_bx) const { // For Bp @@ -234,7 +250,7 @@ void DataLine::fillBp_Bpp(std::vector > & Bp, int bus_or_solver_id = id_grid_to_solver[bus_or_id_me]; if(bus_or_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataLine::fillBp_Bpp: the line with id "; + exc_ << "LineContainer::fillBp_Bpp: the line with id "; exc_ << line_id; exc_ << " is connected (or side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -243,7 +259,7 @@ void DataLine::fillBp_Bpp(std::vector > & Bp, int bus_ex_solver_id = id_grid_to_solver[bus_ex_id_me]; if(bus_ex_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataLine::fillBp_Bpp: the line with id "; + exc_ << "LineContainer::fillBp_Bpp: the line with id "; exc_ << line_id; exc_ << " is connected (ex side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -259,7 +275,7 @@ void DataLine::fillBp_Bpp(std::vector > & Bp, ys_bpp = 1. / (0. + my_i * powerlines_x_(line_id)); }else{ std::ostringstream exc_; - exc_ << "DataLine::fillBp_Bpp: unknown method for the FDPF powerflow for line id "; + exc_ << "LineContainer::fillBp_Bpp: unknown method for the FDPF powerflow for line id "; exc_ << line_id; throw std::runtime_error(exc_.str()); } @@ -289,11 +305,11 @@ void DataLine::fillBp_Bpp(std::vector > & Bp, } -void DataLine::fillBf_for_PTDF(std::vector > & Bf, - const std::vector & id_grid_to_solver, - real_type sn_mva, - int nb_powerline, - bool transpose) const +void LineContainer::fillBf_for_PTDF(std::vector > & Bf, + const std::vector & id_grid_to_solver, + real_type sn_mva, + int nb_powerline, + bool transpose) const { const Eigen::Index nb_line = powerlines_r_.size(); @@ -306,7 +322,7 @@ void DataLine::fillBf_for_PTDF(std::vector > & Bf, int bus_or_solver_id = id_grid_to_solver[bus_or_id_me]; if(bus_or_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataLine::fillBf_for_PTDF: the line with id "; + exc_ << "LineContainer::fillBf_for_PTDF: the line with id "; exc_ << line_id; exc_ << " is connected (or side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -315,7 +331,7 @@ void DataLine::fillBf_for_PTDF(std::vector > & Bf, int bus_ex_solver_id = id_grid_to_solver[bus_ex_id_me]; if(bus_ex_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataLine::fillBf_for_PTDF: the line with id "; + exc_ << "LineContainer::fillBf_for_PTDF: the line with id "; exc_ << line_id; exc_ << " is connected (ex side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -337,7 +353,7 @@ void DataLine::fillBf_for_PTDF(std::vector > & Bf, } -void DataLine::reset_results() +void LineContainer::reset_results() { res_powerline_por_ = RealVect(); // in MW res_powerline_qor_ = RealVect(); // in MVar @@ -349,13 +365,13 @@ void DataLine::reset_results() res_powerline_aex_ = RealVect(); // in kA } -void DataLine::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) +void LineContainer::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) { // it needs to be initialized at 0. Eigen::Index nb_element = nb(); @@ -378,7 +394,7 @@ void DataLine::compute_results(const Eigen::Ref & Va, int bus_or_solver_id = id_grid_to_solver[bus_or_id_me]; if(bus_or_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataLine::compute_results: the line with id "; + exc_ << "LineContainer::compute_results: the line with id "; exc_ << line_id; exc_ << " is connected (or side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -387,7 +403,7 @@ void DataLine::compute_results(const Eigen::Ref & Va, int bus_ex_solver_id = id_grid_to_solver[bus_ex_id_me]; if(bus_ex_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataLine::compute_results: the line with id "; + exc_ << "LineContainer::compute_results: the line with id "; exc_ << line_id; exc_ << " is connected (ex side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -440,7 +456,7 @@ void DataLine::compute_results(const Eigen::Ref & Va, _get_amps(res_powerline_aex_, res_powerline_pex_, res_powerline_qex_, res_powerline_vex_); } -void DataLine::reconnect_connected_buses(std::vector & bus_status) const{ +void LineContainer::reconnect_connected_buses(std::vector & bus_status) const{ const auto my_size = powerlines_r_.size(); for(Eigen::Index line_id = 0; line_id < my_size; ++line_id){ @@ -451,7 +467,7 @@ void DataLine::reconnect_connected_buses(std::vector & bus_status) const{ if(bus_or_id_me == _deactivated_bus_id){ // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataLine::reconnect_connected_buses: Line with id "; + exc_ << "LineContainer::reconnect_connected_buses: Line with id "; exc_ << line_id; exc_ << " is connected (origin) to bus '-1' (meaning disconnected) while you said it was disconnected. Have you called `gridmodel.deactivate_powerline(...)` ?."; throw std::runtime_error(exc_.str()); @@ -462,7 +478,7 @@ void DataLine::reconnect_connected_buses(std::vector & bus_status) const{ if(bus_ex_id_me == _deactivated_bus_id){ // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataLine::reconnect_connected_buses: Line with id "; + exc_ << "LineContainer::reconnect_connected_buses: Line with id "; exc_ << line_id; exc_ << " is connected (ext) to bus '-1' (meaning disconnected) while you said it was disconnected. Have you called `gridmodel.deactivate_powerline(...)` ?."; throw std::runtime_error(exc_.str()); @@ -471,7 +487,7 @@ void DataLine::reconnect_connected_buses(std::vector & bus_status) const{ } } -void DataLine::nb_line_end(std::vector & res) const{ +void LineContainer::nb_line_end(std::vector & res) const{ const auto my_size = powerlines_r_.size(); for(Eigen::Index line_id = 0; line_id < my_size; ++line_id){ // don't do anything if the element is disconnected @@ -483,7 +499,7 @@ void DataLine::nb_line_end(std::vector & res) const{ } } -void DataLine::get_graph(std::vector > & res) const +void LineContainer::get_graph(std::vector > & res) const { const auto my_size = powerlines_r_.size(); for(Eigen::Index line_id = 0; line_id < my_size; ++line_id){ @@ -496,16 +512,16 @@ void DataLine::get_graph(std::vector > & res) const } } -void DataLine::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component) +void LineContainer::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component) { const Eigen::Index nb_line = nb(); + SolverControl unused_solver_control; for(Eigen::Index i = 0; i < nb_line; ++i){ if(!status_[i]) continue; auto bus_or = bus_or_id_(i); auto bus_ex = bus_ex_id_(i); if(!busbar_in_main_component[bus_or] || !busbar_in_main_component[bus_ex]){ - bool tmp = false; - deactivate(i, tmp); + deactivate(i, unused_solver_control); } } } diff --git a/src/DataLine.h b/src/element_container/LineContainer.h similarity index 79% rename from src/DataLine.h rename to src/element_container/LineContainer.h index a046dd0..7e6acf4 100644 --- a/src/DataLine.h +++ b/src/element_container/LineContainer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-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,31 +6,24 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef DATALINE_H -#define DATALINE_H +#ifndef LINE_CONTAINER_H +#define LINE_CONTAINER_H #include -#include "Utils.h" - #include "Eigen/Core" #include "Eigen/Dense" #include "Eigen/SparseCore" #include "Eigen/SparseLU" -#include "DataGeneric.h" +#include "Utils.h" +#include "GenericContainer.h" /** This class is a container for all the powerlines on the grid. -The convention used for the generator is the same as in pandapower: -https://pandapower.readthedocs.io/en/latest/elements/line.html - -and for modeling of the Ybus matrix: -https://pandapower.readthedocs.io/en/latest/elements/line.html#electric-model - **/ -class DataLine : public DataGeneric +class LineContainer : public GenericContainer { public: class LineInfo @@ -60,7 +53,7 @@ class DataLine : public DataGeneric real_type res_a_ex_ka; real_type res_theta_ex_deg; - LineInfo(const DataLine & r_data_line, int my_id): + LineInfo(const LineContainer & r_data_line, int my_id): id(my_id), name(""), connected(false), @@ -118,7 +111,7 @@ class DataLine : public DataGeneric typedef LineInfo DataInfo; private: - typedef DataConstIterator DataLineConstIterator; + typedef GenericContainerConstIterator LineConstIterator; public: typedef std::tuple< @@ -132,7 +125,7 @@ class DataLine : public DataGeneric std::vector // status_ > StateRes; - DataLine() {}; + LineContainer() {}; void init(const RealVect & branch_r, const RealVect & branch_x, @@ -150,8 +143,8 @@ class DataLine : public DataGeneric ); // pickle - DataLine::StateRes get_state() const; - void set_state(DataLine::StateRes & my_state ); + LineContainer::StateRes get_state() const; + void set_state(LineContainer::StateRes & my_state ); template void check_size(const T& my_state) { @@ -159,18 +152,18 @@ class DataLine : public DataGeneric unsigned int size_th = 6; if (my_state.size() != size_th) { - std::cout << "LightSim::DataLine state size " << my_state.size() << " instead of "<< size_th << std::endl; + std::cout << "LightSim::LineContainer 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::DataLine"); + throw std::runtime_error("Invalid state when loading LightSim::LineContainer"); } } int nb() const { return static_cast(powerlines_r_.size()); } // make it iterable - typedef DataLineConstIterator const_iterator_type; - const_iterator_type begin() const {return DataLineConstIterator(this, 0); } - const_iterator_type end() const {return DataLineConstIterator(this, nb()); } + typedef LineConstIterator const_iterator_type; + const_iterator_type begin() const {return LineConstIterator(this, 0); } + const_iterator_type end() const {return LineConstIterator(this, nb()); } LineInfo operator[](int id) const { if(id < 0) @@ -187,11 +180,42 @@ class DataLine : public DataGeneric virtual void disconnect_if_not_in_main_component(std::vector & busbar_in_main_component); virtual void nb_line_end(std::vector & res) const; virtual void get_graph(std::vector > & res) const; - - void deactivate(int powerline_id, bool & need_reset) {_deactivate(powerline_id, status_, need_reset);} - void reactivate(int powerline_id, bool & need_reset) {_reactivate(powerline_id, status_, need_reset);} - void change_bus_or(int powerline_id, int new_bus_id, bool & need_reset, int nb_bus) {_change_bus(powerline_id, new_bus_id, bus_or_id_, need_reset, nb_bus);} - void change_bus_ex(int powerline_id, int new_bus_id, bool & need_reset, int nb_bus) {_change_bus(powerline_id, new_bus_id, bus_ex_id_, need_reset, nb_bus);} + virtual void update_bus_status(std::vector & bus_status) const { + const int nb_ = nb(); + for(int el_id = 0; el_id < nb_; ++el_id) + { + if(!status_[el_id]) continue; + bus_status[bus_or_id_[el_id]] = true; + bus_status[bus_ex_id_[el_id]] = true; + } + } + + void deactivate(int powerline_id, SolverControl & solver_control) { + // std::cout << "line: deactivate called\n"; + if(status_[powerline_id]){ + solver_control.tell_recompute_ybus(); + // but sparsity pattern do not change here (possibly one more coeff at 0.) + solver_control.tell_ybus_some_coeffs_zero(); + solver_control.tell_dimension_changed(); // if the extremity of the line is alone on a bus, this can happen... + } + _deactivate(powerline_id, status_); + } + void reactivate(int powerline_id, SolverControl & solver_control) { + if(!status_[powerline_id]){ + solver_control.tell_recompute_ybus(); + solver_control.tell_ybus_change_sparsity_pattern(); // sparsity pattern might change: a non zero coeff can pop up + solver_control.tell_dimension_changed(); // if the extremity of the line is alone on a bus, this can happen... + } + _reactivate(powerline_id, status_); + } + void change_bus_or(int powerline_id, int new_bus_id, SolverControl & solver_control, int nb_bus) { + // std::cout << "line: change_bus_or called\n"; + _change_bus(powerline_id, new_bus_id, bus_or_id_, solver_control, nb_bus); + } + void change_bus_ex(int powerline_id, int new_bus_id, SolverControl & solver_control, int nb_bus) { + // std::cout << "line: change_bus_or called\n"; + _change_bus(powerline_id, new_bus_id, bus_ex_id_, solver_control, nb_bus); + } int get_bus_or(int powerline_id) {return _get_bus(powerline_id, status_, bus_or_id_);} int get_bus_ex(int powerline_id) {return _get_bus(powerline_id, status_, bus_ex_id_);} virtual void fillYbus(std::vector > & res, @@ -282,4 +306,4 @@ class DataLine : public DataGeneric CplxVect ydc_tt_; }; -#endif //DATALINE_H +#endif //LINE_CONTAINER_H diff --git a/src/DataLoad.cpp b/src/element_container/LoadContainer.cpp similarity index 60% rename from src/DataLoad.cpp rename to src/element_container/LoadContainer.cpp index e29249b..d9d0a1b 100644 --- a/src/DataLoad.cpp +++ b/src/element_container/LoadContainer.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-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,13 +6,18 @@ // 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 "LoadContainer.h" #include -void DataLoad::init(const RealVect & loads_p, - const RealVect & loads_q, - const Eigen::VectorXi & loads_bus_id) +void LoadContainer::init(const RealVect & loads_p, + const RealVect & loads_q, + const Eigen::VectorXi & loads_bus_id) { + int size = static_cast(loads_p.size()); + GenericContainer::check_size(loads_p, size, "loads_p"); + GenericContainer::check_size(loads_q, size, "loads_q"); + GenericContainer::check_size(loads_bus_id, size, "loads_bus_id"); + p_mw_ = loads_p; q_mvar_ = loads_q; bus_id_ = loads_bus_id; @@ -20,16 +25,17 @@ void DataLoad::init(const RealVect & loads_p, } -DataLoad::StateRes DataLoad::get_state() const +LoadContainer::StateRes LoadContainer::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(names_, p_mw, q_mvar, bus_id, status); + LoadContainer::StateRes res(names_, p_mw, q_mvar, bus_id, status); return res; } -void DataLoad::set_state(DataLoad::StateRes & my_state ) + +void LoadContainer::set_state(LoadContainer::StateRes & my_state ) { reset_results(); names_ = std::get<0>(my_state); @@ -47,7 +53,10 @@ void DataLoad::set_state(DataLoad::StateRes & my_state ) } -void DataLoad::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const { +void LoadContainer::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; @@ -59,7 +68,7 @@ void DataLoad::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_sol 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_ << "LoadContainer::fillSbus: the load with id "; exc_ << load_id; exc_ << " is connected to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -70,13 +79,13 @@ void DataLoad::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_sol } } -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) +void LoadContainer::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_); @@ -85,41 +94,47 @@ void DataLoad::compute_results(const Eigen::Ref & Va, res_q_ = q_mvar_; } -void DataLoad::reset_results(){ +void LoadContainer::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) +void LoadContainer::change_p(int load_id, real_type new_p, SolverControl & solver_control) { 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_ << "LoadContainer::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; + if (p_mw_(load_id) != new_p) { + solver_control.tell_recompute_sbus(); + p_mw_(load_id) = new_p; + } } -void DataLoad::change_q(int load_id, real_type new_q, bool & need_reset) +void LoadContainer::change_q(int load_id, real_type new_q, SolverControl & solver_control) { 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_ << "LoadContainer::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; + if (q_mvar_(load_id) != new_q) { + solver_control.tell_recompute_sbus(); + q_mvar_(load_id) = new_q; + } } -void DataLoad::reconnect_connected_buses(std::vector & bus_status) const { +void LoadContainer::reconnect_connected_buses(std::vector & bus_status) const { const int nb_load = nb(); for(int load_id = 0; load_id < nb_load; ++load_id) { @@ -128,7 +143,7 @@ void DataLoad::reconnect_connected_buses(std::vector & bus_status) const { if(my_bus == _deactivated_bus_id){ // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataLoad::reconnect_connected_buses: Load with id "; + exc_ << "LoadContainer::reconnect_connected_buses: Load with id "; exc_ << load_id; exc_ << " is connected to bus '-1' (meaning disconnected) while you said it was disconnected. Have you called `gridmodel.deactivate_load(...)` ?."; throw std::runtime_error(exc_.str()); @@ -137,15 +152,15 @@ void DataLoad::reconnect_connected_buses(std::vector & bus_status) const { } } -void DataLoad::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component){ +void LoadContainer::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component){ const int nb_el = nb(); + SolverControl unused_solver_control; for(int el_id = 0; el_id < nb_el; ++el_id) { if(!status_[el_id]) continue; const auto my_bus = bus_id_(el_id); if(!busbar_in_main_component[my_bus]){ - bool tmp = false; - deactivate(el_id, tmp); + deactivate(el_id, unused_solver_control); } } } diff --git a/src/DataLoad.h b/src/element_container/LoadContainer.h similarity index 76% rename from src/DataLoad.h rename to src/element_container/LoadContainer.h index f4d7689..0483f3f 100644 --- a/src/DataLoad.h +++ b/src/element_container/LoadContainer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-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,17 +6,17 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef DATALOAD_H -#define DATALOAD_H +#ifndef LOAD_CONTAINER_H +#define LOAD_CONTAINER_H -#include "Utils.h" #include "Eigen/Core" #include "Eigen/Dense" #include "Eigen/SparseCore" #include "Eigen/SparseLU" -#include "DataGeneric.h" +#include "Utils.h" +#include "GenericContainer.h" /** This class is a container for all loads on the grid. @@ -31,7 +31,7 @@ NOTE: this class is also used for the storage units! So storage units are modele which entails that negative storage: the unit is discharging, power is injected in the grid, positive storage: the unit is charging, power is taken from the grid. **/ -class DataLoad : public DataGeneric +class LoadContainer : public GenericContainer { // TODO make a single class for load and shunt and just specialize the part where the // TODO powerflow equations are located (when i update the Y matrix) @@ -56,7 +56,7 @@ class DataLoad : public DataGeneric real_type res_v_kv; real_type res_theta_deg; - LoadInfo(const DataLoad & r_data_load, int my_id): + LoadInfo(const LoadContainer & r_data_load, int my_id): id(-1), name(""), connected(false), @@ -95,12 +95,12 @@ class DataLoad : public DataGeneric typedef LoadInfo DataInfo; private: - typedef DataConstIterator DataLoadConstIterator; + typedef GenericContainerConstIterator LoadContainerConstIterator; public: - typedef DataLoadConstIterator const_iterator_type; - const_iterator_type begin() const {return DataLoadConstIterator(this, 0); } - const_iterator_type end() const {return DataLoadConstIterator(this, nb()); } + typedef LoadContainerConstIterator const_iterator_type; + const_iterator_type begin() const {return LoadContainerConstIterator(this, 0); } + const_iterator_type end() const {return LoadContainerConstIterator(this, nb()); } LoadInfo operator[](int id) const { if(id < 0) @@ -124,11 +124,11 @@ class DataLoad : public DataGeneric std::vector // status > StateRes; - DataLoad() {}; + LoadContainer() {}; // pickle (python) - DataLoad::StateRes get_state() const; - void set_state(DataLoad::StateRes & my_state ); + LoadContainer::StateRes get_state() const; + void set_state(LoadContainer::StateRes & my_state ); void init(const RealVect & loads_p, @@ -138,16 +138,34 @@ class DataLoad : public DataGeneric int nb() const { return static_cast(p_mw_.size()); } - void deactivate(int load_id, bool & need_reset) {_deactivate(load_id, status_, need_reset);} - void reactivate(int load_id, bool & need_reset) {_reactivate(load_id, status_, need_reset);} - void change_bus(int load_id, int new_bus_id, bool & need_reset, int nb_bus) {_change_bus(load_id, new_bus_id, bus_id_, need_reset, nb_bus);} + void deactivate(int load_id, SolverControl & solver_control) { + if(status_[load_id]){ + solver_control.tell_recompute_sbus(); + } + _deactivate(load_id, status_); + } + void reactivate(int load_id, SolverControl & solver_control) { + if(!status_[load_id]){ + solver_control.tell_recompute_sbus(); + } + _reactivate(load_id, status_); + } + void change_bus(int load_id, int new_bus_id, SolverControl & solver_control, int nb_bus) {_change_bus(load_id, new_bus_id, bus_id_, solver_control, nb_bus);} int get_bus(int load_id) {return _get_bus(load_id, status_, bus_id_);} - void change_p(int load_id, real_type new_p, bool & need_reset); - void change_q(int load_id, real_type new_q, bool & need_reset); + void change_p(int load_id, real_type new_p, SolverControl & solver_control); + void change_q(int load_id, real_type new_q, SolverControl & solver_control); virtual void reconnect_connected_buses(std::vector & bus_status) const; virtual void disconnect_if_not_in_main_component(std::vector & busbar_in_main_component); virtual void fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const; + virtual void update_bus_status(std::vector & bus_status) const { + const int nb_ = nb(); + for(int el_id = 0; el_id < nb_; ++el_id) + { + if(!status_[el_id]) continue; + bus_status[bus_id_[el_id]] = true; + } + } void compute_results(const Eigen::Ref & Va, const Eigen::Ref & Vm, @@ -179,4 +197,4 @@ class DataLoad : public DataGeneric RealVect res_theta_; // in degree }; -#endif //DATALOAD_H +#endif //LOAD_CONTAINER_H diff --git a/src/DataSGen.cpp b/src/element_container/SGenContainer.cpp similarity index 58% rename from src/DataSGen.cpp rename to src/element_container/SGenContainer.cpp index 3b46038..6300727 100644 --- a/src/DataSGen.cpp +++ b/src/element_container/SGenContainer.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-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,23 +6,25 @@ // 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) +#include "SGenContainer.h" +#include + +void SGenContainer::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"); + GenericContainer::check_size(sgen_p, size, "sgen_p"); + GenericContainer::check_size(sgen_q, size, "sgen_q"); + GenericContainer::check_size(sgen_pmin, size, "sgen_pmin"); + GenericContainer::check_size(sgen_pmax, size, "sgen_pmax"); + GenericContainer::check_size(sgen_qmin, size, "sgen_qmin"); + GenericContainer::check_size(sgen_qmax, size, "sgen_qmax"); + GenericContainer::check_size(sgen_bus_id, size, "sgen_bus_id"); p_mw_ = sgen_p; q_mvar_ = sgen_q; @@ -34,7 +36,7 @@ void DataSGen::init(const RealVect & sgen_p, status_ = std::vector(sgen_p.size(), true); } -DataSGen::StateRes DataSGen::get_state() const +SGenContainer::StateRes SGenContainer::get_state() const { std::vector p_mw(p_mw_.begin(), p_mw_.end()); std::vector q_mvar(q_mvar_.begin(), q_mvar_.end()); @@ -44,11 +46,11 @@ DataSGen::StateRes DataSGen::get_state() const 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(names_, p_mw, q_mvar, p_min, p_max, q_min, q_max, bus_id, status); + SGenContainer::StateRes res(names_, 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 ) +void SGenContainer::set_state(SGenContainer::StateRes & my_state ) { reset_results(); @@ -62,14 +64,14 @@ void DataSGen::set_state(DataSGen::StateRes & my_state ) std::vector & bus_id = std::get<7>(my_state); std::vector & status = std::get<8>(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"); + GenericContainer::check_size(p_mw, size, "p_mw"); + GenericContainer::check_size(q_mvar, size, "q_mvar"); + GenericContainer::check_size(p_min, size, "p_min"); + GenericContainer::check_size(p_max, size, "p_max"); + GenericContainer::check_size(q_min, size, "q_min"); + GenericContainer::check_size(q_max, size, "q_max"); + GenericContainer::check_size(bus_id, size, "bus_id"); + GenericContainer::check_size(status, size, "status"); p_mw_ = RealVect::Map(&p_mw[0], size); q_mvar_ = RealVect::Map(&q_mvar[0], size); @@ -82,7 +84,7 @@ void DataSGen::set_state(DataSGen::StateRes & my_state ) status_ = status; } -void DataSGen::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const { +void SGenContainer::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; @@ -94,24 +96,23 @@ void DataSGen::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_sol 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_ << "SGenContainer::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); + tmp = {p_mw_(sgen_id), 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) +void SGenContainer::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_); @@ -121,41 +122,47 @@ void DataSGen::compute_results(const Eigen::Ref & Va, else res_q_ = RealVect::Zero(nb_sgen); } -void DataSGen::reset_results(){ +void SGenContainer::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) +void SGenContainer::change_p(int sgen_id, real_type new_p, SolverControl & solver_control) { 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_ << "SGenContainer::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; + if (p_mw_(sgen_id) != new_p){ + solver_control.tell_recompute_sbus(); + p_mw_(sgen_id) = new_p; + } } -void DataSGen::change_q(int sgen_id, real_type new_q, bool & need_reset) +void SGenContainer::change_q(int sgen_id, real_type new_q, SolverControl & solver_control) { 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_ << "SGenContainer::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; + if (q_mvar_(sgen_id) != new_q){ + solver_control.tell_recompute_sbus(); + q_mvar_(sgen_id) = new_q; + } } -void DataSGen::reconnect_connected_buses(std::vector & bus_status) const { +void SGenContainer::reconnect_connected_buses(std::vector & bus_status) const { const int nb_sgen = nb(); for(int sgen_id = 0; sgen_id < nb_sgen; ++sgen_id) { @@ -164,7 +171,7 @@ void DataSGen::reconnect_connected_buses(std::vector & bus_status) const { if(my_bus == _deactivated_bus_id){ // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataSGen::reconnect_connected_buses: Static Generator with id "; + exc_ << "SGenContainer::reconnect_connected_buses: Static Generator with id "; exc_ << sgen_id; exc_ << " is connected to bus '-1' (meaning disconnected) while you said it was disconnected. Have you called `gridmodel.deactivate_sgen(...)` ?."; throw std::runtime_error(exc_.str()); @@ -173,7 +180,7 @@ void DataSGen::reconnect_connected_buses(std::vector & bus_status) const { } } -void DataSGen::gen_p_per_bus(std::vector & res) const +void SGenContainer::gen_p_per_bus(std::vector & res) const { const int nb_gen = nb(); for(int sgen_id = 0; sgen_id < nb_gen; ++sgen_id) @@ -184,15 +191,15 @@ void DataSGen::gen_p_per_bus(std::vector & res) const } } -void DataSGen::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component){ +void SGenContainer::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component){ const int nb_el = nb(); + SolverControl unused_solver_control; for(int el_id = 0; el_id < nb_el; ++el_id) { if(!status_[el_id]) continue; const auto my_bus = bus_id_(el_id); if(!busbar_in_main_component[my_bus]){ - bool tmp = false; - deactivate(el_id, tmp); + deactivate(el_id, unused_solver_control); } } } diff --git a/src/DataSGen.h b/src/element_container/SGenContainer.h similarity index 78% rename from src/DataSGen.h rename to src/element_container/SGenContainer.h index 3d45b93..b50a269 100644 --- a/src/DataSGen.h +++ b/src/element_container/SGenContainer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-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,17 +6,17 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef DATASGEN_H -#define DATASGEN_H +#ifndef SGEN_CONTAINER_H +#define SGEN_CONTAINER_H -#include "Utils.h" #include "Eigen/Core" #include "Eigen/Dense" #include "Eigen/SparseCore" #include "Eigen/SparseLU" -#include "DataGeneric.h" +#include "Utils.h" +#include "GenericContainer.h" /** This class is a container for all static generator (PQ generators) on the grid. @@ -28,7 +28,7 @@ The convention used for the static is the same as in pandapower: and for modeling of the Ybus matrix: https://pandapower.readthedocs.io/en/latest/elements/sgen.html#electric-model **/ -class DataSGen: public DataGeneric +class SGenContainer: public GenericContainer { // TODO make a single class for load and shunt and just specialize the part where the // TODO powerflow equations are located (when i update the Y matrix) @@ -60,7 +60,7 @@ class DataSGen: public DataGeneric real_type res_v_kv; real_type res_theta_deg; - SGenInfo(const DataSGen & r_data_sgen, int my_id): + SGenInfo(const SGenContainer & r_data_sgen, int my_id): id(-1), name(""), connected(false), @@ -108,12 +108,12 @@ class DataSGen: public DataGeneric typedef SGenInfo DataInfo; private: - typedef DataConstIterator DataSGenConstIterator; + typedef GenericContainerConstIterator SGenContainerConstIterator; public: - typedef DataSGenConstIterator const_iterator_type; - const_iterator_type begin() const {return DataSGenConstIterator(this, 0); } - const_iterator_type end() const {return DataSGenConstIterator(this, nb()); } + typedef SGenContainerConstIterator const_iterator_type; + const_iterator_type begin() const {return SGenContainerConstIterator(this, 0); } + const_iterator_type end() const {return SGenContainerConstIterator(this, nb()); } SGenInfo operator[](int id) const { if(id < 0) @@ -140,11 +140,11 @@ class DataSGen: public DataGeneric std::vector // status > StateRes; - DataSGen() {}; + SGenContainer() {}; // pickle (python) - DataSGen::StateRes get_state() const; - void set_state(DataSGen::StateRes & my_state ); + SGenContainer::StateRes get_state() const; + void set_state(SGenContainer::StateRes & my_state ); void init(const RealVect & sgen_p, @@ -158,17 +158,35 @@ class DataSGen: public DataGeneric int nb() const { return static_cast(p_mw_.size()); } - void deactivate(int sgen_id, bool & need_reset) {_deactivate(sgen_id, status_, need_reset);} - void reactivate(int sgen_id, bool & need_reset) {_reactivate(sgen_id, status_, need_reset);} - void change_bus(int sgen_id, int new_bus_id, bool & need_reset, int nb_bus) {_change_bus(sgen_id, new_bus_id, bus_id_, need_reset, nb_bus);} + void deactivate(int sgen_id, SolverControl & solver_control) { + if(status_[sgen_id]){ + solver_control.tell_recompute_sbus(); + } + _deactivate(sgen_id, status_); + } + void reactivate(int sgen_id, SolverControl & solver_control) { + if(!status_[sgen_id]){ + solver_control.tell_recompute_sbus(); + } + _reactivate(sgen_id, status_); + } + void change_bus(int sgen_id, int new_bus_id, SolverControl & solver_control, int nb_bus) {_change_bus(sgen_id, new_bus_id, bus_id_, solver_control, nb_bus);} int get_bus(int sgen_id) {return _get_bus(sgen_id, status_, bus_id_);} - void change_p(int sgen_id, real_type new_p, bool & need_reset); - void change_q(int sgen_id, real_type new_q, bool & need_reset); + void change_p(int sgen_id, real_type new_p, SolverControl & solver_control); + void change_q(int sgen_id, real_type new_q, SolverControl & solver_control); virtual void reconnect_connected_buses(std::vector & bus_status) const; virtual void disconnect_if_not_in_main_component(std::vector & busbar_in_main_component); virtual void fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const ; virtual void gen_p_per_bus(std::vector & res) const; + virtual void update_bus_status(std::vector & bus_status) const { + const int nb_ = nb(); + for(int el_id = 0; el_id < nb_; ++el_id) + { + if(!status_[el_id]) continue; + bus_status[bus_id_[el_id]] = true; + } + } void compute_results(const Eigen::Ref & Va, const Eigen::Ref & Vm, @@ -204,4 +222,4 @@ class DataSGen: public DataGeneric RealVect res_theta_; // in degree }; -#endif //DATASGEN_H +#endif //SGEN_CONTAINER_H diff --git a/src/DataShunt.cpp b/src/element_container/ShuntContainer.cpp similarity index 67% rename from src/DataShunt.cpp rename to src/element_container/ShuntContainer.cpp index 42a03f8..4b5687b 100644 --- a/src/DataShunt.cpp +++ b/src/element_container/ShuntContainer.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-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,30 +6,36 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "DataShunt.h" +#include "ShuntContainer.h" + #include -void DataShunt::init(const RealVect & shunt_p_mw, - const RealVect & shunt_q_mvar, - const Eigen::VectorXi & shunt_bus_id) +void ShuntContainer::init(const RealVect & shunt_p_mw, + const RealVect & shunt_q_mvar, + const Eigen::VectorXi & shunt_bus_id) { + int size = static_cast(shunt_p_mw.size()); + GenericContainer::check_size(shunt_p_mw, size, "shunt_p_mw"); + GenericContainer::check_size(shunt_q_mvar, size, "shunt_q_mvar"); + GenericContainer::check_size(shunt_bus_id, size, "shunt_bus_id"); + p_mw_ = shunt_p_mw; q_mvar_ = shunt_q_mvar; bus_id_ = shunt_bus_id; status_ = std::vector(p_mw_.size(), true); // by default everything is connected } -DataShunt::StateRes DataShunt::get_state() const +ShuntContainer::StateRes ShuntContainer::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_; - DataShunt::StateRes res(names_, p_mw, q_mvar, bus_id, status); + ShuntContainer::StateRes res(names_, p_mw, q_mvar, bus_id, status); return res; } -void DataShunt::set_state(DataShunt::StateRes & my_state ) +void ShuntContainer::set_state(ShuntContainer::StateRes & my_state ) { reset_results(); names_ = std::get<0>(my_state); @@ -46,10 +52,10 @@ void DataShunt::set_state(DataShunt::StateRes & my_state ) status_ = status; } -void DataShunt::fillYbus(std::vector > & res, - bool ac, - const std::vector & id_grid_to_solver, - real_type sn_mva) const +void ShuntContainer::fillYbus(std::vector > & res, + bool ac, + const std::vector & id_grid_to_solver, + real_type sn_mva) const { if(!ac) return; // no shunt in DC @@ -67,7 +73,7 @@ void DataShunt::fillYbus(std::vector > & res, bus_id_solver = id_grid_to_solver[bus_id_me]; if(bus_id_solver == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataShunt::fillYbus: the shunt with id "; + exc_ << "ShuntContainer::fillYbus: the shunt with id "; exc_ << shunt_id; exc_ << " is connected to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -77,11 +83,11 @@ void DataShunt::fillYbus(std::vector > & res, } } -void DataShunt::fillBp_Bpp(std::vector > & Bp, - std::vector > & Bpp, - const std::vector & id_grid_to_solver, - real_type sn_mva, - FDPFMethod xb_or_bx) const +void ShuntContainer::fillBp_Bpp(std::vector > & Bp, + std::vector > & Bpp, + const std::vector & id_grid_to_solver, + real_type sn_mva, + FDPFMethod xb_or_bx) const { const Eigen::Index nb_shunt = static_cast(q_mvar_.size()); real_type tmp; @@ -94,7 +100,7 @@ void DataShunt::fillBp_Bpp(std::vector > & Bp, bus_id_solver = id_grid_to_solver[bus_id_me]; if(bus_id_solver == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataShunt::fillBp_Bpp: the shunt with id "; + exc_ << "ShuntContainer::fillBp_Bpp: the shunt with id "; exc_ << shunt_id; exc_ << " is connected to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -106,7 +112,7 @@ void DataShunt::fillBp_Bpp(std::vector > & Bp, } } -void DataShunt::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const // in DC i need that +void ShuntContainer::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const // in DC i need that { if(ac) return; // in AC I do not do that // std::cout << " ok i use this function" << std::endl; @@ -126,17 +132,17 @@ void DataShunt::fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_so } } -void DataShunt::fillYbus_spmat(Eigen::SparseMatrix & res, bool ac, const std::vector & id_grid_to_solver){ - throw std::runtime_error("DataShunt::fillYbus_spmat: should not be used anymore !"); +void ShuntContainer::fillYbus_spmat(Eigen::SparseMatrix & res, bool ac, const std::vector & id_grid_to_solver){ + throw std::runtime_error("ShuntContainer::fillYbus_spmat: should not be used anymore !"); } -void DataShunt::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) +void ShuntContainer::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_shunt = static_cast(p_mw_.size()); v_kv_from_vpu(Va, Vm, status_, nb_shunt, bus_id_, id_grid_to_solver, bus_vn_kv, res_v_); @@ -148,7 +154,7 @@ void DataShunt::compute_results(const Eigen::Ref & Va, int bus_id_me = bus_id_(shunt_id); int bus_solver_id = id_grid_to_solver[bus_id_me]; if(bus_solver_id == _deactivated_bus_id){ - throw std::runtime_error("DataShunt::compute_results: A shunt is connected to a disconnected bus."); + throw std::runtime_error("ShuntContainer::compute_results: A shunt is connected to a disconnected bus."); } cplx_type E = V(bus_solver_id); cplx_type y = -my_one_ * (p_mw_(shunt_id) + my_i * q_mvar_(shunt_id)) / sn_mva; @@ -160,30 +166,34 @@ void DataShunt::compute_results(const Eigen::Ref & Va, } } -void DataShunt::reset_results(){ +void ShuntContainer::reset_results(){ res_p_ = RealVect(); // in MW res_q_ = RealVect(); // in MVar res_v_ = RealVect(); // in kV } -void DataShunt::change_p(int shunt_id, real_type new_p, bool & need_reset) +void ShuntContainer::change_p(int shunt_id, real_type new_p, SolverControl & solver_control) { bool my_status = status_.at(shunt_id); // and this check that load_id is not out of bound if(!my_status) throw std::runtime_error("Impossible to change the active value of a disconnected shunt"); - if(p_mw_(shunt_id) != new_p) need_reset = true; - p_mw_(shunt_id) = new_p; - + if(p_mw_(shunt_id) != new_p){ + solver_control.tell_recompute_ybus(); + solver_control.tell_recompute_sbus(); // in dc mode sbus is modified + p_mw_(shunt_id) = new_p; + } } -void DataShunt::change_q(int shunt_id, real_type new_q, bool & need_reset) +void ShuntContainer::change_q(int shunt_id, real_type new_q, SolverControl & solver_control) { bool my_status = status_.at(shunt_id); // and this check that load_id is not out of bound if(!my_status) throw std::runtime_error("Impossible to change the reactive value of a disconnected shunt"); - if(q_mvar_(shunt_id) != new_q) need_reset = true; - q_mvar_(shunt_id) = new_q; + if(q_mvar_(shunt_id) != new_q){ + solver_control.tell_recompute_ybus(); + q_mvar_(shunt_id) = new_q; + } } -void DataShunt::reconnect_connected_buses(std::vector & bus_status) const { +void ShuntContainer::reconnect_connected_buses(std::vector & bus_status) const { const int nb_shunt = nb(); for(int shunt_id = 0; shunt_id < nb_shunt; ++shunt_id) { @@ -192,7 +202,7 @@ void DataShunt::reconnect_connected_buses(std::vector & bus_status) const if(my_bus == _deactivated_bus_id){ // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataShunt::reconnect_connected_buses: Shunt with id "; + exc_ << "ShuntContainer::reconnect_connected_buses: Shunt with id "; exc_ << shunt_id; exc_ << " is connected to bus '-1' (meaning disconnected) while you said it was disconnected. Have you called `gridmodel.deactivate_shunt(...)` ?."; throw std::runtime_error(exc_.str()); @@ -201,15 +211,15 @@ void DataShunt::reconnect_connected_buses(std::vector & bus_status) const } } -void DataShunt::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component){ +void ShuntContainer::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component){ const int nb_el = nb(); + SolverControl unused_solver_control; for(int el_id = 0; el_id < nb_el; ++el_id) { if(!status_[el_id]) continue; const auto my_bus = bus_id_(el_id); if(!busbar_in_main_component[my_bus]){ - bool tmp = false; - deactivate(el_id, tmp); + deactivate(el_id, unused_solver_control); } } } diff --git a/src/DataShunt.h b/src/element_container/ShuntContainer.h similarity index 74% rename from src/DataShunt.h rename to src/element_container/ShuntContainer.h index 1096a57..8458acf 100644 --- a/src/DataShunt.h +++ b/src/element_container/ShuntContainer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-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,18 +6,16 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef DATASHUNT_H -#define DATASHUNT_H - -#include "Utils.h" +#ifndef SHUNT_CONTAINER_H +#define SHUNT_CONTAINER_H #include "Eigen/Core" #include "Eigen/Dense" #include "Eigen/SparseCore" #include "Eigen/SparseLU" - -#include "DataGeneric.h" +#include "Utils.h" +#include "GenericContainer.h" /** This class is a container for all shunts on the grid. @@ -28,7 +26,7 @@ The convention used for the shunt is the same as in pandapower: and for modeling of the Ybus matrix: https://pandapower.readthedocs.io/en/latest/elements/shunt.html#electric-model **/ -class DataShunt : public DataGeneric +class ShuntContainer : public GenericContainer { // iterators part public: @@ -50,7 +48,7 @@ class DataShunt : public DataGeneric real_type res_v_kv; real_type res_theta_deg; - ShuntInfo(const DataShunt & r_data_shunt, int my_id): + ShuntInfo(const ShuntContainer & r_data_shunt, int my_id): id(-1), name(""), connected(false), @@ -89,12 +87,12 @@ class DataShunt : public DataGeneric typedef ShuntInfo DataInfo; private: - typedef DataConstIterator DataShuntConstIterator; + typedef GenericContainerConstIterator ShuntContainerConstIterator; public: - typedef DataShuntConstIterator const_iterator_type; - const_iterator_type begin() const {return DataShuntConstIterator(this, 0); } - const_iterator_type end() const {return DataShuntConstIterator(this, nb()); } + typedef ShuntContainerConstIterator const_iterator_type; + const_iterator_type begin() const {return ShuntContainerConstIterator(this, 0); } + const_iterator_type end() const {return ShuntContainerConstIterator(this, nb()); } ShuntInfo operator[](int id) const { if(id < 0) @@ -117,25 +115,37 @@ class DataShunt : public DataGeneric std::vector // status > StateRes; - DataShunt() {}; + ShuntContainer() {}; void init(const RealVect & shunt_p_mw, - const RealVect & shunt_q_mvar, - const Eigen::VectorXi & shunt_bus_id + const RealVect & shunt_q_mvar, + const Eigen::VectorXi & shunt_bus_id ); // pickle (python) - DataShunt::StateRes get_state() const; - void set_state(DataShunt::StateRes & my_state ); + ShuntContainer::StateRes get_state() const; + void set_state(ShuntContainer::StateRes & my_state ); int nb() const { return static_cast(p_mw_.size()); } - void deactivate(int shunt_id, bool & need_reset) {_deactivate(shunt_id, status_, need_reset);} - void reactivate(int shunt_id, bool & need_reset) {_reactivate(shunt_id, status_, need_reset);} - void change_bus(int shunt_id, int new_bus_id, bool & need_reset, int nb_bus) {_change_bus(shunt_id, new_bus_id, bus_id_, need_reset, nb_bus);} - void change_p(int shunt_id, real_type new_p, bool & need_reset); - void change_q(int shunt_id, real_type new_q, bool & need_reset); + void deactivate(int shunt_id, SolverControl & solver_control) { + if(status_[shunt_id]){ + solver_control.tell_recompute_sbus(); // DC + solver_control.tell_recompute_ybus(); // AC + } + _deactivate(shunt_id, status_); + } + void reactivate(int shunt_id, SolverControl & solver_control) { + if(!status_[shunt_id]){ + solver_control.tell_recompute_sbus(); // DC + solver_control.tell_recompute_ybus(); // AC + } + _reactivate(shunt_id, status_); + } + void change_bus(int shunt_id, int new_bus_id, SolverControl & solver_control, int nb_bus) {_change_bus(shunt_id, new_bus_id, bus_id_, solver_control, nb_bus);} + void change_p(int shunt_id, real_type new_p, SolverControl & solver_control); + void change_q(int shunt_id, real_type new_q, SolverControl & solver_control); int get_bus(int shunt_id) {return _get_bus(shunt_id, status_, bus_id_);} virtual void reconnect_connected_buses(std::vector & bus_status) const; virtual void disconnect_if_not_in_main_component(std::vector & busbar_in_main_component); @@ -151,6 +161,14 @@ class DataShunt : public DataGeneric FDPFMethod xb_or_bx) const; virtual void fillYbus_spmat(Eigen::SparseMatrix & res, bool ac, const std::vector & id_grid_to_solver); virtual void fillSbus(CplxVect & Sbus, const std::vector & id_grid_to_solver, bool ac) const; // in DC i need that + virtual void update_bus_status(std::vector & bus_status) const { + const int nb_ = nb(); + for(int el_id = 0; el_id < nb_; ++el_id) + { + if(!status_[el_id]) continue; + bus_status[bus_id_[el_id]] = true; + } + } void compute_results(const Eigen::Ref & Va, const Eigen::Ref & Vm, @@ -181,4 +199,4 @@ class DataShunt : public DataGeneric RealVect res_theta_; // in kV }; -#endif //DATASHUNT_H +#endif //SHUNT_CONTAINER_H diff --git a/src/DataTrafo.cpp b/src/element_container/TrafoContainer.cpp similarity index 79% rename from src/DataTrafo.cpp rename to src/element_container/TrafoContainer.cpp index a526eae..f33e1cd 100644 --- a/src/DataTrafo.cpp +++ b/src/element_container/TrafoContainer.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-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,36 +6,37 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "DataTrafo.h" +#include "TrafoContainer.h" + #include #include -void DataTrafo::init(const RealVect & trafo_r, - 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 - const Eigen::VectorXi & trafo_hv_id, - const Eigen::VectorXi & trafo_lv_id - ) +void TrafoContainer::init(const RealVect & trafo_r, + 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 + const Eigen::VectorXi & trafo_hv_id, + const Eigen::VectorXi & trafo_lv_id + ) { /** INPUT DATA ARE ALREADY PAIR UNIT !! DOES NOT WORK WITH POWERLINES **/ const int size = static_cast(trafo_r.size()); - DataGeneric::check_size(trafo_r, size, "trafo_r"); - DataGeneric::check_size(trafo_x, size, "trafo_x"); - DataGeneric::check_size(trafo_b, size, "trafo_b"); - DataGeneric::check_size(trafo_tap_step_pct, size, "trafo_tap_step_pct"); - DataGeneric::check_size(trafo_tap_pos, size, "trafo_tap_pos"); - DataGeneric::check_size(trafo_shift_degree, size, "trafo_shift_degree"); - DataGeneric::check_size(trafo_tap_hv, static_cast::size_type>(size), "trafo_tap_hv"); - DataGeneric::check_size(trafo_hv_id, size, "trafo_hv_id"); - DataGeneric::check_size(trafo_lv_id, size, "trafo_lv_id"); + GenericContainer::check_size(trafo_r, size, "trafo_r"); + GenericContainer::check_size(trafo_x, size, "trafo_x"); + GenericContainer::check_size(trafo_b, size, "trafo_b"); + GenericContainer::check_size(trafo_tap_step_pct, size, "trafo_tap_step_pct"); + GenericContainer::check_size(trafo_tap_pos, size, "trafo_tap_pos"); + GenericContainer::check_size(trafo_shift_degree, size, "trafo_shift_degree"); + GenericContainer::check_size(trafo_tap_hv, static_cast::size_type>(size), "trafo_tap_hv"); + GenericContainer::check_size(trafo_hv_id, size, "trafo_hv_id"); + GenericContainer::check_size(trafo_lv_id, size, "trafo_lv_id"); //TODO "parrallel" in the pandapower dataframe, like for lines, are not handled. Handle it python side! @@ -55,7 +56,7 @@ void DataTrafo::init(const RealVect & trafo_r, } -DataTrafo::StateRes DataTrafo::get_state() const +TrafoContainer::StateRes TrafoContainer::get_state() const { std::vector branch_r(r_.begin(), r_.end()); std::vector branch_x(x_.begin(), x_.end()); @@ -66,12 +67,12 @@ DataTrafo::StateRes DataTrafo::get_state() const std::vector ratio(ratio_.begin(), ratio_.end()); std::vector shift(shift_.begin(), shift_.end()); std::vector is_tap_hv_side = is_tap_hv_side_; - DataTrafo::StateRes res(names_, branch_r, branch_x, branch_h, bus_hv_id, bus_lv_id, status, ratio, is_tap_hv_side, shift); + TrafoContainer::StateRes res(names_, branch_r, branch_x, branch_h, bus_hv_id, bus_lv_id, status, ratio, is_tap_hv_side, shift); return res; } -void DataTrafo::set_state(DataTrafo::StateRes & my_state) +void TrafoContainer::set_state(TrafoContainer::StateRes & my_state) { reset_results(); @@ -87,15 +88,15 @@ void DataTrafo::set_state(DataTrafo::StateRes & my_state) std::vector & shift = std::get<9>(my_state); auto size = branch_r.size(); - DataGeneric::check_size(branch_r, size, "branch_r"); - DataGeneric::check_size(branch_x, size, "branch_x"); - DataGeneric::check_size(branch_h, size, "branch_h"); - DataGeneric::check_size(bus_hv_id, size, "bus_hv_id"); - DataGeneric::check_size(bus_lv_id, size, "bus_lv_id"); - DataGeneric::check_size(status, size, "status"); - DataGeneric::check_size(ratio, size, "ratio"); - DataGeneric::check_size(is_tap_hv_side, size, "is_tap_hv_side"); - DataGeneric::check_size(shift, size, "shift"); + GenericContainer::check_size(branch_r, size, "branch_r"); + GenericContainer::check_size(branch_x, size, "branch_x"); + GenericContainer::check_size(branch_h, size, "branch_h"); + GenericContainer::check_size(bus_hv_id, size, "bus_hv_id"); + GenericContainer::check_size(bus_lv_id, size, "bus_lv_id"); + GenericContainer::check_size(status, size, "status"); + GenericContainer::check_size(ratio, size, "ratio"); + GenericContainer::check_size(is_tap_hv_side, size, "is_tap_hv_side"); + GenericContainer::check_size(shift, size, "shift"); // now assign the values r_ = RealVect::Map(&branch_r[0], size); @@ -113,7 +114,7 @@ void DataTrafo::set_state(DataTrafo::StateRes & my_state) } -void DataTrafo::_update_model_coeffs() +void TrafoContainer::_update_model_coeffs() { const Eigen::Index my_size = r_.size(); @@ -163,15 +164,17 @@ void DataTrafo::_update_model_coeffs() } } -void DataTrafo::fillYbus_spmat(Eigen::SparseMatrix & res, bool ac, const std::vector & id_grid_to_solver) +void TrafoContainer::fillYbus_spmat(Eigen::SparseMatrix & res, + bool ac, + const std::vector & id_grid_to_solver) { throw std::runtime_error("You should not use that!"); } -void DataTrafo::fillYbus(std::vector > & res, - bool ac, - const std::vector & id_grid_to_solver, - real_type sn_mva) const +void TrafoContainer::fillYbus(std::vector > & res, + bool ac, + const std::vector & id_grid_to_solver, + real_type sn_mva) const { //TODO merge that with fillYbusBranch! //TODO template here instead of "if" for ac / dc @@ -186,7 +189,7 @@ void DataTrafo::fillYbus(std::vector > & res, int bus_hv_solver_id = id_grid_to_solver[bus_hv_id_me]; if(bus_hv_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataTrafo::fillYbus: the trafo with id "; + exc_ << "TrafoContainer::fillYbus: the trafo with id "; exc_ << trafo_id; exc_ << " is connected (hv side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -195,7 +198,7 @@ void DataTrafo::fillYbus(std::vector > & res, int bus_lv_solver_id = id_grid_to_solver[bus_lv_id_me]; if(bus_lv_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataTrafo::fillYbus: the trafo with id "; + exc_ << "TrafoContainer::fillYbus: the trafo with id "; exc_ << trafo_id; exc_ << " is connected (lv side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -221,7 +224,10 @@ void DataTrafo::fillYbus(std::vector > & res, } } -void DataTrafo::hack_Sbus_for_dc_phase_shifter(CplxVect & Sbus, bool ac, const std::vector & id_grid_to_solver){ +void TrafoContainer::hack_Sbus_for_dc_phase_shifter(CplxVect & Sbus, + bool ac, + const std::vector & id_grid_to_solver) +{ if(ac) return; // return; const int nb_trafo = nb(); @@ -235,7 +241,7 @@ void DataTrafo::hack_Sbus_for_dc_phase_shifter(CplxVect & Sbus, bool ac, const s bus_id_solver_lv = id_grid_to_solver[bus_id_me]; if(bus_id_solver_lv == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataTrafo::hack_Sbus_for_dc_phase_shifter: the trafo with id "; + exc_ << "TrafoContainer::hack_Sbus_for_dc_phase_shifter: the trafo with id "; exc_ << trafo_id; exc_ << " is connected (lv side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -244,7 +250,7 @@ void DataTrafo::hack_Sbus_for_dc_phase_shifter(CplxVect & Sbus, bool ac, const s bus_id_solver_hv = id_grid_to_solver[bus_id_me]; if(bus_id_solver_hv == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataTrafo::hack_Sbus_for_dc_phase_shifter: the trafo with id "; + exc_ << "TrafoContainer::hack_Sbus_for_dc_phase_shifter: the trafo with id "; exc_ << trafo_id; exc_ << " is connected (hv side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -254,14 +260,14 @@ void DataTrafo::hack_Sbus_for_dc_phase_shifter(CplxVect & Sbus, bool ac, const s } } -void DataTrafo::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 - ) +void TrafoContainer::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 + ) { // it needs to be initialized at 0. const int nb_element = nb(); @@ -284,7 +290,7 @@ void DataTrafo::compute_results(const Eigen::Ref & Va, int bus_hv_solver_id = id_grid_to_solver[bus_hv_id_me]; if(bus_hv_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataTrafo::compute_results: the trafo with id "; + exc_ << "TrafoContainer::compute_results: the trafo with id "; exc_ << trafo_id; exc_ << " is connected (hv side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -293,7 +299,7 @@ void DataTrafo::compute_results(const Eigen::Ref & Va, int bus_lv_solver_id = id_grid_to_solver[bus_lv_id_me]; if(bus_lv_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataTrafo::compute_results: the trafo with id "; + exc_ << "TrafoContainer::compute_results: the trafo with id "; exc_ << trafo_id; exc_ << " is connected (lv side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -346,7 +352,7 @@ void DataTrafo::compute_results(const Eigen::Ref & Va, _get_amps(res_a_lv_, res_p_lv_, res_q_lv_, res_v_lv_); } -void DataTrafo::reset_results(){ +void TrafoContainer::reset_results(){ res_p_hv_ = RealVect(); // in MW res_q_hv_ = RealVect(); // in MVar res_v_hv_ = RealVect(); // in kV @@ -358,11 +364,11 @@ void DataTrafo::reset_results(){ } -void DataTrafo::fillBp_Bpp(std::vector > & Bp, - std::vector > & Bpp, - const std::vector & id_grid_to_solver, - real_type sn_mva, - FDPFMethod xb_or_bx) const +void TrafoContainer::fillBp_Bpp(std::vector > & Bp, + std::vector > & Bpp, + const std::vector & id_grid_to_solver, + real_type sn_mva, + FDPFMethod xb_or_bx) const { // For Bp @@ -389,7 +395,7 @@ void DataTrafo::fillBp_Bpp(std::vector > & Bp, int bus_or_solver_id = id_grid_to_solver[bus_or_id_me]; if(bus_or_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataTrafo::fillBp_Bpp: the trafo with id "; + exc_ << "TrafoContainer::fillBp_Bpp: the trafo with id "; exc_ << tr_id; exc_ << " is connected (hv side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -398,7 +404,7 @@ void DataTrafo::fillBp_Bpp(std::vector > & Bp, int bus_ex_solver_id = id_grid_to_solver[bus_ex_id_me]; if(bus_ex_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataTrafo::fillBp_Bpp: the trafo with id "; + exc_ << "TrafoContainer::fillBp_Bpp: the trafo with id "; exc_ << tr_id; exc_ << " is connected (lv side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -428,7 +434,7 @@ void DataTrafo::fillBp_Bpp(std::vector > & Bp, ys_bpp = 1. / (0. + my_i * x_(tr_id)); }else{ std::ostringstream exc_; - exc_ << "DataTrafo::fillBp_Bpp: unknown method for the FDPF powerflow for line id "; + exc_ << "TrafoContainer::fillBp_Bpp: unknown method for the FDPF powerflow for line id "; exc_ << tr_id; throw std::runtime_error(exc_.str()); } @@ -459,11 +465,11 @@ void DataTrafo::fillBp_Bpp(std::vector > & Bp, } -void DataTrafo::fillBf_for_PTDF(std::vector > & Bf, - const std::vector & id_grid_to_solver, - real_type sn_mva, - int nb_powerline, - bool transpose) const +void TrafoContainer::fillBf_for_PTDF(std::vector > & Bf, + const std::vector & id_grid_to_solver, + real_type sn_mva, + int nb_powerline, + bool transpose) const { const Eigen::Index nb_trafo = r_.size(); @@ -476,7 +482,7 @@ void DataTrafo::fillBf_for_PTDF(std::vector > & Bf, int bus_or_solver_id = id_grid_to_solver[bus_or_id_me]; if(bus_or_solver_id == _deactivated_bus_id){ std::ostringstream exc_; - exc_ << "DataTrafo::fillBf_for_PTDF: the line with id "; + exc_ << "TrafoContainer::fillBf_for_PTDF: the line with id "; exc_ << tr_id; exc_ << " is connected (hv side) to a disconnected bus while being connected"; throw std::runtime_error(exc_.str()); @@ -508,7 +514,7 @@ void DataTrafo::fillBf_for_PTDF(std::vector > & Bf, } -void DataTrafo::reconnect_connected_buses(std::vector & bus_status) const{ +void TrafoContainer::reconnect_connected_buses(std::vector & bus_status) const{ const Eigen::Index nb_trafo = nb(); for(Eigen::Index trafo_id = 0; trafo_id < nb_trafo; ++trafo_id){ @@ -519,7 +525,7 @@ void DataTrafo::reconnect_connected_buses(std::vector & bus_status) const{ if(bus_or_id_me == _deactivated_bus_id){ // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataTrafo::reconnect_connected_buses: Trafo with id "; + exc_ << "TrafoContainer::reconnect_connected_buses: Trafo with id "; exc_ << trafo_id; exc_ << " is connected (hv) to bus '-1' (meaning disconnected) while you said it was disconnected. Have you called `gridmodel.deactivate_trafo(...)` ?."; throw std::runtime_error(exc_.str()); @@ -530,7 +536,7 @@ void DataTrafo::reconnect_connected_buses(std::vector & bus_status) const{ if(bus_ex_id_me == _deactivated_bus_id){ // TODO DEBUG MODE only this in debug mode std::ostringstream exc_; - exc_ << "DataTrafo::reconnect_connected_buses: Trafo with id "; + exc_ << "TrafoContainer::reconnect_connected_buses: Trafo with id "; exc_ << trafo_id; exc_ << " is connected (lv) to bus '-1' (meaning disconnected) while you said it was disconnected. Have you called `gridmodel.deactivate_trafo(...)` ?."; throw std::runtime_error(exc_.str()); @@ -539,7 +545,7 @@ void DataTrafo::reconnect_connected_buses(std::vector & bus_status) const{ } } -void DataTrafo::nb_line_end(std::vector & res) const{ +void TrafoContainer::nb_line_end(std::vector & res) const{ const Eigen::Index nb_trafo = nb(); for(Eigen::Index trafo_id = 0; trafo_id < nb_trafo; ++trafo_id){ @@ -552,7 +558,7 @@ void DataTrafo::nb_line_end(std::vector & res) const{ } } -void DataTrafo::get_graph(std::vector > & res) const +void TrafoContainer::get_graph(std::vector > & res) const { const auto my_size = nb(); for(Eigen::Index line_id = 0; line_id < my_size; ++line_id){ @@ -565,16 +571,16 @@ void DataTrafo::get_graph(std::vector > & res) const } } -void DataTrafo::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component) +void TrafoContainer::disconnect_if_not_in_main_component(std::vector & busbar_in_main_component) { const Eigen::Index nb_line = nb(); + SolverControl unused_solver_control; for(Eigen::Index i = 0; i < nb_line; ++i){ if(!status_[i]) continue; auto bus_or = bus_hv_id_(i); auto bus_ex = bus_lv_id_(i); if(!busbar_in_main_component[bus_or] || !busbar_in_main_component[bus_ex]){ - bool tmp = false; - deactivate(i, tmp); + deactivate(i, unused_solver_control); } } } diff --git a/src/DataTrafo.h b/src/element_container/TrafoContainer.h similarity index 81% rename from src/DataTrafo.h rename to src/element_container/TrafoContainer.h index 6227979..fbb07f8 100644 --- a/src/DataTrafo.h +++ b/src/element_container/TrafoContainer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2020, RTE (https://www.rte-france.com) +// Copyright (c) 2020-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,18 +6,17 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef DATATRAFO_H -#define DATATRAFO_H +#ifndef TRAFO_CONTAINER_H +#define TRAFO_CONTAINER_H -#include "Utils.h" #include "Eigen/Core" #include "Eigen/Dense" #include "Eigen/SparseCore" #include "Eigen/SparseLU" - -#include "DataGeneric.h" +#include "Utils.h" +#include "GenericContainer.h" /** This class is a container for all transformers on the grid. @@ -30,7 +29,7 @@ The convention used for the transformer is the same as in pandapower: and for modeling of the Ybus matrix: https://pandapower.readthedocs.io/en/latest/elements/trafo.html#electric-model **/ -class DataTrafo : public DataGeneric +class TrafoContainer : public GenericContainer { public: class TrafoInfo @@ -61,7 +60,7 @@ class DataTrafo : public DataGeneric real_type res_a_lv_ka; real_type res_theta_lv_deg; - TrafoInfo(const DataTrafo & r_data_trafo, int my_id): + TrafoInfo(const TrafoContainer & r_data_trafo, int my_id): id(-1), name(""), connected(false), @@ -121,7 +120,7 @@ class DataTrafo : public DataGeneric typedef TrafoInfo DataInfo; private: - typedef DataConstIterator DataTrafoConstIterator; + typedef GenericContainerConstIterator TrafoContainerConstIterator; public: typedef std::tuple< @@ -137,7 +136,7 @@ class DataTrafo : public DataGeneric std::vector // shift_ > StateRes; - DataTrafo() {}; + TrafoContainer() {}; void init(const RealVect & trafo_r, const RealVect & trafo_x, @@ -151,15 +150,15 @@ class DataTrafo : public DataGeneric const Eigen::VectorXi & trafo_lv_id ); //pickle - DataTrafo::StateRes get_state() const; - void set_state(DataTrafo::StateRes & my_state ); + TrafoContainer::StateRes get_state() const; + void set_state(TrafoContainer::StateRes & my_state ); int nb() const { return static_cast(r_.size()); } // make it iterable - typedef DataTrafoConstIterator const_iterator_type; - const_iterator_type begin() const {return DataTrafoConstIterator(this, 0); } - const_iterator_type end() const {return DataTrafoConstIterator(this, nb()); } + typedef TrafoContainerConstIterator const_iterator_type; + const_iterator_type begin() const {return TrafoContainerConstIterator(this, 0); } + const_iterator_type end() const {return TrafoContainerConstIterator(this, nb()); } TrafoInfo operator[](int id) const { if(id < 0) @@ -168,16 +167,31 @@ class DataTrafo : public DataGeneric } if(id >= nb()) { - throw std::range_error("Generator out of bound. Not enough transformers on the grid."); + throw std::range_error("Trafo out of bound. Not enough transformers on the grid."); } return TrafoInfo(*this, id); } // method used within lightsim - void deactivate(int trafo_id, bool & need_reset) {_deactivate(trafo_id, status_, need_reset);} - void reactivate(int trafo_id, bool & need_reset) {_reactivate(trafo_id, status_, need_reset);} - void change_bus_hv(int trafo_id, int new_bus_id, bool & need_reset, int nb_bus) {_change_bus(trafo_id, new_bus_id, bus_hv_id_, need_reset, nb_bus);} - void change_bus_lv(int trafo_id, int new_bus_id, bool & need_reset, int nb_bus) {_change_bus(trafo_id, new_bus_id, bus_lv_id_, need_reset, nb_bus);} + void deactivate(int trafo_id, SolverControl & solver_control) { + if(status_[trafo_id]){ + solver_control.tell_recompute_ybus(); + // but sparsity pattern do not change here (possibly one more coeff at 0.) + solver_control.tell_ybus_some_coeffs_zero(); + solver_control.tell_dimension_changed(); // if the extremity of the line is alone on a bus, this can happen... + } + _deactivate(trafo_id, status_); + } + void reactivate(int trafo_id, SolverControl & solver_control) { + if(!status_[trafo_id]){ + solver_control.tell_recompute_ybus(); + solver_control.tell_ybus_change_sparsity_pattern(); // this might change + solver_control.tell_dimension_changed(); // if the extremity of the line is alone on a bus, this can happen... + } + _reactivate(trafo_id, status_); + } + void change_bus_hv(int trafo_id, int new_bus_id, SolverControl & solver_control, int nb_bus) {_change_bus(trafo_id, new_bus_id, bus_hv_id_, solver_control, nb_bus);} + void change_bus_lv(int trafo_id, int new_bus_id, SolverControl & solver_control, int nb_bus) {_change_bus(trafo_id, new_bus_id, bus_lv_id_, solver_control, nb_bus);} int get_bus_hv(int trafo_id) {return _get_bus(trafo_id, status_, bus_hv_id_);} int get_bus_lv(int trafo_id) {return _get_bus(trafo_id, status_, bus_lv_id_);} void reconnect_connected_buses(std::vector & bus_status) const; @@ -202,6 +216,15 @@ class DataTrafo : public DataGeneric int nb_powerline, bool transpose) const; virtual void hack_Sbus_for_dc_phase_shifter(CplxVect & Sbus, bool ac, const std::vector & id_grid_to_solver); // needed for dc mode + virtual void update_bus_status(std::vector & bus_status) const { + const int nb_ = nb(); + for(int el_id = 0; el_id < nb_; ++el_id) + { + if(!status_[el_id]) continue; + bus_status[bus_hv_id_[el_id]] = true; + bus_status[bus_lv_id_[el_id]] = true; + } + } void compute_results(const Eigen::Ref & Va, const Eigen::Ref & Vm, @@ -275,4 +298,4 @@ class DataTrafo : public DataGeneric RealVect dc_x_tau_shift_; }; -#endif //DATATRAFO_H +#endif //TRAFO_CONTAINER_H diff --git a/src/help_fun_msg.cpp b/src/help_fun_msg.cpp index a33c1b6..48f58a9 100644 --- a/src/help_fun_msg.cpp +++ b/src/help_fun_msg.cpp @@ -808,12 +808,12 @@ const std::string DocIterator::has_res = R"mydelimiter( )mydelimiter"; -const std::string DocIterator::DataGen = R"mydelimiter( +const std::string DocIterator::GeneratorContainer = R"mydelimiter( This class allows to iterate through the generators of the :class:`lightsim2grid.gridmodel.GridModel` easily, as if they were in a python list. In lightsim2grid they are modeled as "pv" meanings you give the active production setpoint and voltage magnitude setpoint - (see :attr:`lightsim2grid.elements.DataSGen` for more exotic PQ generators). + (see :attr:`lightsim2grid.elements.SGenContainer` for more exotic PQ generators). The active production value setpoint are modified only for the generators participating to the slack buses (see :attr:`lightsim2grid.elements.GenInfo.is_slack` and :attr:`lightsim2grid.elements.GenInfo.slack_weight`). @@ -847,7 +847,8 @@ const std::string DocIterator::DataGen = R"mydelimiter( )mydelimiter"; const std::string DocIterator::GenInfo = R"mydelimiter( - This class represents what you get from retrieving some elements from :class:`lightsim2grid.elements.DataGen` + This class represents what you get from retrieving some elements from + :class:`lightsim2grid.elements.GeneratorContainer` It allows to read information from each generator of the powergrid. @@ -932,11 +933,12 @@ const std::string DocIterator::max_p_mw = R"mydelimiter( )mydelimiter"; -const std::string DocIterator::DataSGen = R"mydelimiter( +const std::string DocIterator::SGenContainer = R"mydelimiter( This class allows to iterate through the static generators of the :class:`lightsim2grid.gridmodel.GridModel` easily, as if they were in a python list. - In lightsim2grid they are two types of generators the more standard PV generators (see :attr:`lightsim2grid.elements.DataGen`). These + In lightsim2grid they are two types of generators the more standard PV generators (see + :attr:`lightsim2grid.elements.GeneratorContainer`). These are more exotic generators known as PQ, where you give the active production value and reactive production value. It's basically like loads, but using the generator convention (if the value is positive, it means power is taken from the grid to the element) @@ -972,7 +974,8 @@ const std::string DocIterator::DataSGen = R"mydelimiter( )mydelimiter"; const std::string DocIterator::SGenInfo = R"mydelimiter( - This class represents what you get from retrieving some elements from :class:`lightsim2grid.elements.DataSGen` + This class represents what you get from retrieving some elements from + :class:`lightsim2grid.elements.SGenContainer` It allows to read information from each static generator of the powergrid. @@ -1001,7 +1004,7 @@ const std::string DocIterator::SGenInfo = R"mydelimiter( )mydelimiter"; -const std::string DocIterator::DataLoad = R"mydelimiter( +const std::string DocIterator::LoadContainer = R"mydelimiter( This class allows to iterate through the loads **and storage units** of the :class:`lightsim2grid.gridmodel.GridModel` easily, as if they were in a python list. @@ -1049,7 +1052,8 @@ const std::string DocIterator::DataLoad = R"mydelimiter( )mydelimiter"; const std::string DocIterator::LoadInfo = R"mydelimiter( - This class represents what you get from retrieving some elements from :class:`lightsim2grid.elements.DataLoad`. + This class represents what you get from retrieving some elements from + :class:`lightsim2grid.elements.LoadContainer`. We remind the reader that storage units are also modeled as load in lightsim2grid. It allows to read information from each load / storage unit of the powergrid. @@ -1088,7 +1092,7 @@ const std::string DocIterator::LoadInfo = R"mydelimiter( )mydelimiter"; -const std::string DocIterator::DataShunt = R"mydelimiter( +const std::string DocIterator::ShuntContainer = R"mydelimiter( This class allows to iterate through the load of the :class:`lightsim2grid.gridmodel.GridModel` easily, as if they were in a python list. @@ -1122,7 +1126,8 @@ const std::string DocIterator::DataShunt = R"mydelimiter( )mydelimiter"; const std::string DocIterator::ShuntInfo = R"mydelimiter( - This class represents what you get from retrieving the shunts from :class:`lightsim2grid.elements.DataShunt`. + This class represents what you get from retrieving the shunts from + :class:`lightsim2grid.elements.ShuntContainer`. It allows to read information from each shunt of the powergrid. @@ -1150,7 +1155,7 @@ const std::string DocIterator::ShuntInfo = R"mydelimiter( )mydelimiter"; -const std::string DocIterator::DataTrafo = R"mydelimiter( +const std::string DocIterator::TrafoContainer = R"mydelimiter( This class allows to iterate through the transformers of the :class:`lightsim2grid.gridmodel.GridModel` easily, as if they were in a python list. @@ -1184,7 +1189,8 @@ const std::string DocIterator::DataTrafo = R"mydelimiter( )mydelimiter"; const std::string DocIterator::TrafoInfo = R"mydelimiter( - This class represents what you get from retrieving the transformers from :class:`lightsim2grid.elements.DataTrafo`. + This class represents what you get from retrieving the transformers from + :class:`lightsim2grid.elements.TrafoContainer`. It allows to read information from each transformer of the powergrid. @@ -1321,7 +1327,7 @@ const std::string DocIterator::res_a_hv_ka = R"mydelimiter( )mydelimiter" + DocIterator::only_avail_res; -const std::string DocIterator::DataLine = R"mydelimiter( +const std::string DocIterator::LineContainer = R"mydelimiter( This class allows to iterate through the powerlines of the :class:`lightsim2grid.gridmodel.GridModel` easily, as if they were in a python list. @@ -1355,7 +1361,8 @@ const std::string DocIterator::DataLine = R"mydelimiter( )mydelimiter"; const std::string DocIterator::LineInfo = R"mydelimiter( - This class represents what you get from retrieving the powerlines from :class:`lightsim2grid.elements.DataLine`. + This class represents what you get from retrieving the powerlines from + :class:`lightsim2grid.elements.LineContainer`. It allows to read information from each powerline of the powergrid. @@ -1475,7 +1482,7 @@ const std::string DocIterator::res_a_ex_ka = R"mydelimiter( )mydelimiter" + DocIterator::only_avail_res; -const std::string DocIterator::DataDCLine = R"mydelimiter( +const std::string DocIterator::DCLineContainer = R"mydelimiter( This class allows to iterate through the dc lines of the :class:`lightsim2grid.gridmodel.GridModel` easily, as if they were in a python list. @@ -1523,7 +1530,8 @@ const std::string DocIterator::DataDCLine = R"mydelimiter( const std::string DocIterator::DCLineInfo = R"mydelimiter( - This class represents what you get from retrieving the dc powerlines from :class:`lightsim2grid.elements.DataDCLine`. + This class represents what you get from retrieving the dc powerlines from + :class:`lightsim2grid.elements.DCLineContainer`. It allows to read information from each dc powerline of the powergrid. @@ -1789,7 +1797,8 @@ const std::string DocGridModel::get_dc_solver = R"mydelimiter( )mydelimiter"; const std::string DocGridModel::get_lines = R"mydelimiter( - This function allows to retrieve the powerlines (as a :class:`lightsim2grid.elements.DataLine` object, + This function allows to retrieve the powerlines (as a + :class:`lightsim2grid.elements.LineContainer` object, see :ref:`elements-modeled` for more information) Examples @@ -1807,7 +1816,8 @@ const std::string DocGridModel::get_lines = R"mydelimiter( )mydelimiter"; const std::string DocGridModel::get_trafos = R"mydelimiter( - This function allows to retrieve the transformers (as a :class:`lightsim2grid.elements.DataLine` object, + This function allows to retrieve the transformers (as a + :class:`lightsim2grid.elements.LineContainer` object, see :ref:`elements-modeled` for more information) Examples @@ -1825,7 +1835,8 @@ const std::string DocGridModel::get_trafos = R"mydelimiter( )mydelimiter"; const std::string DocGridModel::get_generators = R"mydelimiter( - This function allows to retrieve the (standard) generators (as a :class:`lightsim2grid.elements.DataGen` object, + This function allows to retrieve the (standard) generators (as a + :class:`lightsim2grid.elements.GeneratorContainer` object, see :ref:`elements-modeled` for more information) Examples @@ -1843,7 +1854,8 @@ const std::string DocGridModel::get_generators = R"mydelimiter( )mydelimiter"; const std::string DocGridModel::get_static_generators = R"mydelimiter( - This function allows to retrieve the (more exotic) static generators (as a :class:`lightsim2grid.elements.DataSGen` object, + This function allows to retrieve the (more exotic) static generators (as a + :class:`lightsim2grid.elements.SGenContainer` object, see :ref:`elements-modeled` for more information) Examples @@ -1861,7 +1873,8 @@ const std::string DocGridModel::get_static_generators = R"mydelimiter( )mydelimiter"; const std::string DocGridModel::get_shunts = R"mydelimiter( - This function allows to retrieve the shunts (as a :class:`lightsim2grid.elements.DataShunt` object, + This function allows to retrieve the shunts (as a + :class:`lightsim2grid.elements.ShuntContainer` object, see :ref:`elements-modeled` for more information) Examples @@ -1879,12 +1892,13 @@ const std::string DocGridModel::get_shunts = R"mydelimiter( )mydelimiter"; const std::string DocGridModel::get_storages = R"mydelimiter( - This function allows to retrieve the storage units (as a :class:`lightsim2grid.elements.DataLoad` object, + This function allows to retrieve the storage units (as a + :class:`lightsim2grid.elements.LoadContainer` object, see :ref:`elements-modeled` for more information) .. note:: We want to emphize that, as far as lightsim2grid is concerned, the storage units are modeled as loads. This is why - this function will return a :class:`lightsim2grid.elements.DataLoad`. + this function will return a :class:`lightsim2grid.elements.LoadContainer`. Examples --------- @@ -1901,7 +1915,7 @@ const std::string DocGridModel::get_storages = R"mydelimiter( )mydelimiter"; const std::string DocGridModel::get_loads = R"mydelimiter( - This function allows to retrieve the loads (as a :class:`lightsim2grid.elements.DataLoad` object, + This function allows to retrieve the loads (as a :class:`lightsim2grid.elements.LoadContainer` object, see :ref:`elements-modeled` for more information) Examples @@ -1920,7 +1934,8 @@ const std::string DocGridModel::get_loads = R"mydelimiter( )mydelimiter"; const std::string DocGridModel::get_dclines = R"mydelimiter( - This function allows to retrieve the dc powerlines (as a :class:`lightsim2grid.elements.DataDCLine` object, + This function allows to retrieve the dc powerlines (as a + :class:`lightsim2grid.elements.DCLineContainer` object, see :ref:`elements-modeled` for more information) Examples diff --git a/src/help_fun_msg.h b/src/help_fun_msg.h index 3f503b1..cf98db4 100644 --- a/src/help_fun_msg.h +++ b/src/help_fun_msg.h @@ -90,25 +90,25 @@ struct DocIterator static const std::string h_pu; // specific to generators - static const std::string DataGen; + static const std::string GeneratorContainer; static const std::string GenInfo; static const std::string is_slack; static const std::string slack_weight; // specific to sgens - static const std::string DataSGen; + static const std::string SGenContainer; static const std::string SGenInfo; // specific to loads (and storage units) - static const std::string DataLoad; + static const std::string LoadContainer; static const std::string LoadInfo; // specific to shunts - static const std::string DataShunt; + static const std::string ShuntContainer; static const std::string ShuntInfo; // specific to transformers - static const std::string DataTrafo; + static const std::string TrafoContainer; static const std::string TrafoInfo; static const std::string bus_hv_id; static const std::string bus_lv_id; @@ -127,7 +127,7 @@ struct DocIterator static const std::string res_theta_lv_deg; // specific to powerlines - static const std::string DataLine; + static const std::string LineContainer; static const std::string LineInfo; static const std::string bus_or_id; static const std::string bus_ex_id; @@ -144,7 +144,7 @@ struct DocIterator // specific to dc lines static const std::string dc_line_formula; - static const std::string DataDCLine; + static const std::string DCLineContainer; static const std::string DCLineInfo; static const std::string target_p_or_mw; static const std::string target_vm_or_pu; diff --git a/src/CKTSOSolver.cpp b/src/linear_solvers/CKTSOSolver.cpp similarity index 98% rename from src/CKTSOSolver.cpp rename to src/linear_solvers/CKTSOSolver.cpp index 8d5b734..4c5a01b 100644 --- a/src/CKTSOSolver.cpp +++ b/src/linear_solvers/CKTSOSolver.cpp @@ -87,7 +87,7 @@ ErrorType CKTSOLinearSolver::initialize(Eigen::SparseMatrix & J){ return err; } -ErrorType CKTSOLinearSolver::solve(Eigen::SparseMatrix & J, RealVect & b, bool has_just_been_inialized){ +ErrorType CKTSOLinearSolver::solve(Eigen::SparseMatrix & J, RealVect & b, bool doesnt_need_refactor){ // solves (for x) the linear system J.x = b // with standard use of lightsim2grid, the solver should have already been initialized // J is const even if it does not compile if said const @@ -95,7 +95,7 @@ ErrorType CKTSOLinearSolver::solve(Eigen::SparseMatrix & J, RealVect bool stop = false; RealVect x; ErrorType err = ErrorType::NoError; - if(!has_just_been_inialized){ + if(!doesnt_need_refactor){ ret = solver_->Refactorize(J.valuePtr()); if (ret < 0) { // std::cout << "CKTSOLinearSolver::solve solver_->Refactorize error: " << ret << std::endl; diff --git a/src/CKTSOSolver.h b/src/linear_solvers/CKTSOSolver.h similarity index 98% rename from src/CKTSOSolver.h rename to src/linear_solvers/CKTSOSolver.h index 2ed7e34..af98c93 100644 --- a/src/CKTSOSolver.h +++ b/src/linear_solvers/CKTSOSolver.h @@ -62,7 +62,7 @@ class CKTSOLinearSolver // public api ErrorType reset(); ErrorType initialize(Eigen::SparseMatrix & J); - ErrorType solve(Eigen::SparseMatrix & J, RealVect & b, bool has_just_been_inialized); + ErrorType solve(Eigen::SparseMatrix & J, RealVect & b, bool doesnt_need_refactor); // can this linear solver solve problem where RHS is a matrix static const bool CAN_SOLVE_MAT; diff --git a/src/KLUSolver.cpp b/src/linear_solvers/KLUSolver.cpp similarity index 96% rename from src/KLUSolver.cpp rename to src/linear_solvers/KLUSolver.cpp index fe8efc4..4e05361 100644 --- a/src/KLUSolver.cpp +++ b/src/linear_solvers/KLUSolver.cpp @@ -37,14 +37,14 @@ ErrorType KLULinearSolver::initialize(Eigen::SparseMatrix& J){ return res; } -ErrorType KLULinearSolver::solve(Eigen::SparseMatrix& J, RealVect & b, bool has_just_been_initialized){ +ErrorType KLULinearSolver::solve(Eigen::SparseMatrix& J, RealVect & b, bool doesnt_need_refactor){ // solves (for x) the linear system J.x = b // supposes that the solver has been initialized (call klu_solver.analyze() before calling that) // J is const even if it does not compile if said const int ok; ErrorType err = ErrorType::NoError; bool stop = false; - if(!has_just_been_initialized){ + if(!doesnt_need_refactor){ // if the call to "klu_factor" has been made this iteration, there is no need // to re factor again the matrix // i'm in the case where it has not diff --git a/src/KLUSolver.h b/src/linear_solvers/KLUSolver.h similarity index 98% rename from src/KLUSolver.h rename to src/linear_solvers/KLUSolver.h index 2eefe06..f75b554 100644 --- a/src/KLUSolver.h +++ b/src/linear_solvers/KLUSolver.h @@ -46,7 +46,7 @@ class KLULinearSolver // public api ErrorType reset(); ErrorType initialize(Eigen::SparseMatrix& J); - ErrorType solve(Eigen::SparseMatrix& J, RealVect & b, bool has_just_been_inialized); + ErrorType solve(Eigen::SparseMatrix& J, RealVect & b, bool doesnt_need_refactor); // can this linear solver solve problem where RHS is a matrix static const bool CAN_SOLVE_MAT; diff --git a/src/NICSLUSolver.cpp b/src/linear_solvers/NICSLUSolver.cpp similarity index 98% rename from src/NICSLUSolver.cpp rename to src/linear_solvers/NICSLUSolver.cpp index 357a672..a6f39a1 100644 --- a/src/NICSLUSolver.cpp +++ b/src/linear_solvers/NICSLUSolver.cpp @@ -75,7 +75,7 @@ ErrorType NICSLULinearSolver::initialize(Eigen::SparseMatrix & J){ return err; } -ErrorType NICSLULinearSolver::solve(Eigen::SparseMatrix & J, RealVect & b, bool has_just_been_inialized){ +ErrorType NICSLULinearSolver::solve(Eigen::SparseMatrix & J, RealVect & b, bool doesnt_need_refactor){ // solves (for x) the linear system J.x = b // supposes that the solver has been initialized (call klu_solver.analyze() before calling that) // J is const even if it does not compile if said const @@ -83,7 +83,7 @@ ErrorType NICSLULinearSolver::solve(Eigen::SparseMatrix & J, RealVect bool stop = false; RealVect x; ErrorType err = ErrorType::NoError; - if(!has_just_been_inialized){ + if(!doesnt_need_refactor){ ret = solver_.FactorizeMatrix(J.valuePtr(), nb_thread_); // TODO maybe 0 instead of nb_thread_ here, see https://github.com/chenxm1986/nicslu/blob/master/nicslu202110/demo/demo2.cpp if (ret < 0) { // std::cout << "NICSLULinearSolver::solve solver_.FactorizeMatrix error: " << ret << std::endl; diff --git a/src/NICSLUSolver.h b/src/linear_solvers/NICSLUSolver.h similarity index 98% rename from src/NICSLUSolver.h rename to src/linear_solvers/NICSLUSolver.h index 481b4a0..bb6412c 100644 --- a/src/NICSLUSolver.h +++ b/src/linear_solvers/NICSLUSolver.h @@ -54,7 +54,7 @@ class NICSLULinearSolver // public api ErrorType reset(); ErrorType initialize(Eigen::SparseMatrix & J); - ErrorType solve(Eigen::SparseMatrix & J, RealVect & b, bool has_just_been_inialized); + ErrorType solve(Eigen::SparseMatrix & J, RealVect & b, bool doesnt_need_refactor); // can this linear solver solve problem where RHS is a matrix static const bool CAN_SOLVE_MAT; diff --git a/src/SparseLUSolver.cpp b/src/linear_solvers/SparseLUSolver.cpp similarity index 95% rename from src/SparseLUSolver.cpp rename to src/linear_solvers/SparseLUSolver.cpp index 98c3149..5a1b6ab 100644 --- a/src/SparseLUSolver.cpp +++ b/src/linear_solvers/SparseLUSolver.cpp @@ -23,13 +23,13 @@ ErrorType SparseLULinearSolver::initialize(const Eigen::SparseMatrix return res; } -ErrorType SparseLULinearSolver::solve(const Eigen::SparseMatrix & J, RealVect & b, bool has_just_been_inialized){ +ErrorType SparseLULinearSolver::solve(const Eigen::SparseMatrix & J, RealVect & b, bool doesnt_need_refactor){ // solves (for x) the linear system J.x = b // supposes that the solver has been initialized (call sparselu_solver.analyze() before calling that) // J is const even if it does not compile if said const ErrorType err = ErrorType::NoError; bool stop = false; - if(!has_just_been_inialized){ + if(!doesnt_need_refactor){ // if the call to "klu_factor" has been made this iteration, there is no need // to re factor again the matrix // i'm in the case where it has not diff --git a/src/SparseLUSolver.h b/src/linear_solvers/SparseLUSolver.h similarity index 97% rename from src/SparseLUSolver.h rename to src/linear_solvers/SparseLUSolver.h index 768fd67..710abcc 100644 --- a/src/SparseLUSolver.h +++ b/src/linear_solvers/SparseLUSolver.h @@ -34,7 +34,7 @@ class SparseLULinearSolver // public api ErrorType initialize(const Eigen::SparseMatrix & J); - ErrorType solve(const Eigen::SparseMatrix & J, RealVect & b, bool has_just_been_inialized); + ErrorType solve(const Eigen::SparseMatrix & J, RealVect & b, bool doesnt_need_refactor); ErrorType reset(){ return ErrorType::NoError; } // can this linear solver solve problem where RHS is a matrix diff --git a/src/main.cpp b/src/main.cpp index 2dea615..dd001e1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,8 +13,9 @@ #include "ChooseSolver.h" #include "DataConverter.h" #include "GridModel.h" -#include "Computers.h" -#include "SecurityAnalysis.h" + +#include "batch_algorithm/TimeSeries.h" +#include "batch_algorithm/ContingencyAnalysis.h" #include "help_fun_msg.h" @@ -349,31 +350,31 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) #endif // CKTSO_SOLVER_AVAILABLE (or _READ_THE_DOCS) - py::class_(m, "GaussSeidelSolver", DocSolver::GaussSeidelSolver.c_str()) + py::class_(m, "GaussSeidelSolver", DocSolver::GaussSeidelSolver.c_str()) .def(py::init<>()) - .def("get_Va", &GaussSeidelSolver::get_Va, DocSolver::get_Va.c_str()) // get the voltage angle vector (vector of double) - .def("get_Vm", &GaussSeidelSolver::get_Vm, DocSolver::get_Vm.c_str()) // get the voltage magnitude vector (vector of double) - .def("get_V", &GaussSeidelSolver::get_V, DocSolver::get_V.c_str()) - .def("get_error", &GaussSeidelSolver::get_error, DocSolver::get_error.c_str()) // get the error message, see the definition of "err_" for more information - .def("get_nb_iter", &GaussSeidelSolver::get_nb_iter, DocSolver::get_nb_iter.c_str()) // return the number of iteration performed at the last optimization - .def("reset", &GaussSeidelSolver::reset, DocSolver::reset.c_str()) // reset the solver to its original state - .def("converged", &GaussSeidelSolver::converged, DocSolver::converged.c_str()) // whether the solver has converged - .def("compute_pf", &GaussSeidelSolver::compute_pf, py::call_guard(), DocSolver::compute_pf.c_str()) // compute the powerflow - .def("get_timers", &GaussSeidelSolver::get_timers, DocSolver::get_timers.c_str()) // returns the timers corresponding to times the solver spent in different part - .def("solve", &GaussSeidelSolver::compute_pf, py::call_guard(), DocSolver::compute_pf.c_str()); // perform the newton raphson optimization - - py::class_(m, "GaussSeidelSynchSolver", DocSolver::GaussSeidelSynchSolver.c_str()) + .def("get_Va", &GaussSeidelAlgo::get_Va, DocSolver::get_Va.c_str()) // get the voltage angle vector (vector of double) + .def("get_Vm", &GaussSeidelAlgo::get_Vm, DocSolver::get_Vm.c_str()) // get the voltage magnitude vector (vector of double) + .def("get_V", &GaussSeidelAlgo::get_V, DocSolver::get_V.c_str()) + .def("get_error", &GaussSeidelAlgo::get_error, DocSolver::get_error.c_str()) // get the error message, see the definition of "err_" for more information + .def("get_nb_iter", &GaussSeidelAlgo::get_nb_iter, DocSolver::get_nb_iter.c_str()) // return the number of iteration performed at the last optimization + .def("reset", &GaussSeidelAlgo::reset, DocSolver::reset.c_str()) // reset the solver to its original state + .def("converged", &GaussSeidelAlgo::converged, DocSolver::converged.c_str()) // whether the solver has converged + .def("compute_pf", &GaussSeidelAlgo::compute_pf, py::call_guard(), DocSolver::compute_pf.c_str()) // compute the powerflow + .def("get_timers", &GaussSeidelAlgo::get_timers, DocSolver::get_timers.c_str()) // returns the timers corresponding to times the solver spent in different part + .def("solve", &GaussSeidelAlgo::compute_pf, py::call_guard(), DocSolver::compute_pf.c_str()); // perform the newton raphson optimization + + py::class_(m, "GaussSeidelSynchSolver", DocSolver::GaussSeidelSynchSolver.c_str()) .def(py::init<>()) - .def("get_Va", &GaussSeidelSynchSolver::get_Va, DocSolver::get_Va.c_str()) // get the voltage angle vector (vector of double) - .def("get_Vm", &GaussSeidelSynchSolver::get_Vm, DocSolver::get_Vm.c_str()) // get the voltage magnitude vector (vector of double) - .def("get_V", &GaussSeidelSynchSolver::get_V, DocSolver::get_V.c_str()) - .def("get_error", &GaussSeidelSynchSolver::get_error, DocSolver::get_error.c_str()) // get the error message, see the definition of "err_" for more information - .def("get_nb_iter", &GaussSeidelSynchSolver::get_nb_iter, DocSolver::get_nb_iter.c_str()) // return the number of iteration performed at the last optimization - .def("reset", &GaussSeidelSynchSolver::reset, DocSolver::reset.c_str()) // reset the solver to its original state - .def("converged", &GaussSeidelSynchSolver::converged, DocSolver::converged.c_str()) // whether the solver has converged - .def("compute_pf", &GaussSeidelSynchSolver::compute_pf, py::call_guard(), DocSolver::compute_pf.c_str()) // compute the powerflow - .def("get_timers", &GaussSeidelSynchSolver::get_timers, DocSolver::get_timers.c_str()) // returns the timers corresponding to times the solver spent in different part - .def("solve", &GaussSeidelSynchSolver::compute_pf, py::call_guard(), DocSolver::compute_pf.c_str()); // perform the newton raphson optimization + .def("get_Va", &GaussSeidelSynchAlgo::get_Va, DocSolver::get_Va.c_str()) // get the voltage angle vector (vector of double) + .def("get_Vm", &GaussSeidelSynchAlgo::get_Vm, DocSolver::get_Vm.c_str()) // get the voltage magnitude vector (vector of double) + .def("get_V", &GaussSeidelSynchAlgo::get_V, DocSolver::get_V.c_str()) + .def("get_error", &GaussSeidelSynchAlgo::get_error, DocSolver::get_error.c_str()) // get the error message, see the definition of "err_" for more information + .def("get_nb_iter", &GaussSeidelSynchAlgo::get_nb_iter, DocSolver::get_nb_iter.c_str()) // return the number of iteration performed at the last optimization + .def("reset", &GaussSeidelSynchAlgo::reset, DocSolver::reset.c_str()) // reset the solver to its original state + .def("converged", &GaussSeidelSynchAlgo::converged, DocSolver::converged.c_str()) // whether the solver has converged + .def("compute_pf", &GaussSeidelSynchAlgo::compute_pf, py::call_guard(), DocSolver::compute_pf.c_str()) // compute the powerflow + .def("get_timers", &GaussSeidelSynchAlgo::get_timers, DocSolver::get_timers.c_str()) // returns the timers corresponding to times the solver spent in different part + .def("solve", &GaussSeidelSynchAlgo::compute_pf, py::call_guard(), DocSolver::compute_pf.c_str()); // perform the newton raphson optimization // Only "const" method are exported // it is so that i cannot modify the internal solver of a gridmodel python side @@ -396,192 +397,192 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) .def("get_fdpf_bx_lu", &ChooseSolver::get_fdpf_bx_lu, py::return_value_policy::reference, DocGridModel::_internal_do_not_use.c_str()); // iterator for generators - py::class_(m, "DataGen", DocIterator::DataGen.c_str()) - .def("__len__", [](const DataGen & data) { return data.nb(); }) - .def("__getitem__", [](const DataGen & data, int k){return data[k]; } ) - .def("__iter__", [](const DataGen & data) { + py::class_(m, "GeneratorContainer", DocIterator::GeneratorContainer.c_str()) + .def("__len__", [](const GeneratorContainer & data) { return data.nb(); }) + .def("__getitem__", [](const GeneratorContainer & data, int k){return data[k]; } ) + .def("__iter__", [](const GeneratorContainer & data) { return py::make_iterator(data.begin(), data.end()); }, py::keep_alive<0, 1>()); /* Keep vector alive while iterator is used */ - py::class_(m, "GenInfo", DocIterator::GenInfo.c_str()) - .def_readonly("id", &DataGen::GenInfo::id, DocIterator::id.c_str()) - .def_readonly("name", &DataGen::GenInfo::name, DocIterator::name.c_str()) - .def_readonly("connected", &DataGen::GenInfo::connected, DocIterator::connected.c_str()) - .def_readonly("bus_id", &DataGen::GenInfo::bus_id, DocIterator::bus_id.c_str()) - .def_readonly("is_slack", &DataGen::GenInfo::is_slack, DocIterator::is_slack.c_str()) - .def_readonly("slack_weight", &DataGen::GenInfo::slack_weight, DocIterator::slack_weight.c_str()) - .def_readonly("voltage_regulator_on", &DataGen::GenInfo::voltage_regulator_on, "TODO") - .def_readonly("target_p_mw", &DataGen::GenInfo::target_p_mw, DocIterator::target_p_mw.c_str()) - .def_readonly("target_vm_pu", &DataGen::GenInfo::target_vm_pu, DocIterator::target_vm_pu.c_str()) - .def_readonly("target_q_mvar", &DataGen::GenInfo::target_q_mvar, "TODO") - .def_readonly("min_q_mvar", &DataGen::GenInfo::min_q_mvar, DocIterator::min_q_mvar.c_str()) - .def_readonly("max_q_mvar", &DataGen::GenInfo::max_q_mvar, DocIterator::max_q_mvar.c_str()) - .def_readonly("has_res", &DataGen::GenInfo::has_res, DocIterator::has_res.c_str()) - .def_readonly("res_p_mw", &DataGen::GenInfo::res_p_mw, DocIterator::res_p_mw.c_str()) - .def_readonly("res_q_mvar", &DataGen::GenInfo::res_q_mvar, DocIterator::res_q_mvar.c_str()) - .def_readonly("res_theta_deg", &DataGen::GenInfo::res_theta_deg, DocIterator::res_theta_deg.c_str()) - .def_readonly("res_v_kv", &DataGen::GenInfo::res_v_kv, DocIterator::res_v_kv.c_str()); + py::class_(m, "GenInfo", DocIterator::GenInfo.c_str()) + .def_readonly("id", &GeneratorContainer::GenInfo::id, DocIterator::id.c_str()) + .def_readonly("name", &GeneratorContainer::GenInfo::name, DocIterator::name.c_str()) + .def_readonly("connected", &GeneratorContainer::GenInfo::connected, DocIterator::connected.c_str()) + .def_readonly("bus_id", &GeneratorContainer::GenInfo::bus_id, DocIterator::bus_id.c_str()) + .def_readonly("is_slack", &GeneratorContainer::GenInfo::is_slack, DocIterator::is_slack.c_str()) + .def_readonly("slack_weight", &GeneratorContainer::GenInfo::slack_weight, DocIterator::slack_weight.c_str()) + .def_readonly("voltage_regulator_on", &GeneratorContainer::GenInfo::voltage_regulator_on, "TODO") + .def_readonly("target_p_mw", &GeneratorContainer::GenInfo::target_p_mw, DocIterator::target_p_mw.c_str()) + .def_readonly("target_vm_pu", &GeneratorContainer::GenInfo::target_vm_pu, DocIterator::target_vm_pu.c_str()) + .def_readonly("target_q_mvar", &GeneratorContainer::GenInfo::target_q_mvar, "TODO") + .def_readonly("min_q_mvar", &GeneratorContainer::GenInfo::min_q_mvar, DocIterator::min_q_mvar.c_str()) + .def_readonly("max_q_mvar", &GeneratorContainer::GenInfo::max_q_mvar, DocIterator::max_q_mvar.c_str()) + .def_readonly("has_res", &GeneratorContainer::GenInfo::has_res, DocIterator::has_res.c_str()) + .def_readonly("res_p_mw", &GeneratorContainer::GenInfo::res_p_mw, DocIterator::res_p_mw.c_str()) + .def_readonly("res_q_mvar", &GeneratorContainer::GenInfo::res_q_mvar, DocIterator::res_q_mvar.c_str()) + .def_readonly("res_theta_deg", &GeneratorContainer::GenInfo::res_theta_deg, DocIterator::res_theta_deg.c_str()) + .def_readonly("res_v_kv", &GeneratorContainer::GenInfo::res_v_kv, DocIterator::res_v_kv.c_str()); // iterator for sgens - py::class_(m, "DataSGen", DocIterator::DataSGen.c_str()) - .def("__len__", [](const DataSGen & data) { return data.nb(); }) - .def("__getitem__", [](const DataSGen & data, int k){return data[k]; } ) - .def("__iter__", [](const DataSGen & data) { + py::class_(m, "SGenContainer", DocIterator::SGenContainer.c_str()) + .def("__len__", [](const SGenContainer & data) { return data.nb(); }) + .def("__getitem__", [](const SGenContainer & data, int k){return data[k]; } ) + .def("__iter__", [](const SGenContainer & data) { return py::make_iterator(data.begin(), data.end()); }, py::keep_alive<0, 1>()); /* Keep vector alive while iterator is used */ - py::class_(m, "SGenInfo", DocIterator::SGenInfo.c_str()) - .def_readonly("id", &DataSGen::SGenInfo::id, DocIterator::id.c_str()) - .def_readonly("name", &DataSGen::SGenInfo::name, DocIterator::name.c_str()) - .def_readonly("connected", &DataSGen::SGenInfo::connected, DocIterator::connected.c_str()) - .def_readonly("bus_id", &DataSGen::SGenInfo::bus_id, DocIterator::bus_id.c_str()) - .def_readonly("min_q_mvar", &DataSGen::SGenInfo::min_q_mvar, DocIterator::min_q_mvar.c_str()) - .def_readonly("max_q_mvar", &DataSGen::SGenInfo::max_q_mvar, DocIterator::max_q_mvar.c_str()) - .def_readonly("min_p_mw", &DataSGen::SGenInfo::min_p_mw, DocIterator::min_p_mw.c_str()) - .def_readonly("max_p_mw", &DataSGen::SGenInfo::max_p_mw, DocIterator::max_p_mw.c_str()) - .def_readonly("target_p_mw", &DataSGen::SGenInfo::target_p_mw, DocIterator::target_p_mw.c_str()) - .def_readonly("target_q_mvar", &DataSGen::SGenInfo::target_q_mvar, DocIterator::target_q_mvar.c_str()) - .def_readonly("has_res", &DataSGen::SGenInfo::has_res, DocIterator::has_res.c_str()) - .def_readonly("res_p_mw", &DataSGen::SGenInfo::res_p_mw, DocIterator::res_p_mw.c_str()) - .def_readonly("res_q_mvar", &DataSGen::SGenInfo::res_q_mvar, DocIterator::res_q_mvar.c_str()) - .def_readonly("res_theta_deg", &DataSGen::SGenInfo::res_theta_deg, DocIterator::res_theta_deg.c_str()) - .def_readonly("res_v_kv", &DataSGen::SGenInfo::res_v_kv, DocIterator::res_v_kv.c_str()); + py::class_(m, "SGenInfo", DocIterator::SGenInfo.c_str()) + .def_readonly("id", &SGenContainer::SGenInfo::id, DocIterator::id.c_str()) + .def_readonly("name", &SGenContainer::SGenInfo::name, DocIterator::name.c_str()) + .def_readonly("connected", &SGenContainer::SGenInfo::connected, DocIterator::connected.c_str()) + .def_readonly("bus_id", &SGenContainer::SGenInfo::bus_id, DocIterator::bus_id.c_str()) + .def_readonly("min_q_mvar", &SGenContainer::SGenInfo::min_q_mvar, DocIterator::min_q_mvar.c_str()) + .def_readonly("max_q_mvar", &SGenContainer::SGenInfo::max_q_mvar, DocIterator::max_q_mvar.c_str()) + .def_readonly("min_p_mw", &SGenContainer::SGenInfo::min_p_mw, DocIterator::min_p_mw.c_str()) + .def_readonly("max_p_mw", &SGenContainer::SGenInfo::max_p_mw, DocIterator::max_p_mw.c_str()) + .def_readonly("target_p_mw", &SGenContainer::SGenInfo::target_p_mw, DocIterator::target_p_mw.c_str()) + .def_readonly("target_q_mvar", &SGenContainer::SGenInfo::target_q_mvar, DocIterator::target_q_mvar.c_str()) + .def_readonly("has_res", &SGenContainer::SGenInfo::has_res, DocIterator::has_res.c_str()) + .def_readonly("res_p_mw", &SGenContainer::SGenInfo::res_p_mw, DocIterator::res_p_mw.c_str()) + .def_readonly("res_q_mvar", &SGenContainer::SGenInfo::res_q_mvar, DocIterator::res_q_mvar.c_str()) + .def_readonly("res_theta_deg", &SGenContainer::SGenInfo::res_theta_deg, DocIterator::res_theta_deg.c_str()) + .def_readonly("res_v_kv", &SGenContainer::SGenInfo::res_v_kv, DocIterator::res_v_kv.c_str()); // iterator for loads (and storage units) - py::class_(m, "DataLoad", DocIterator::DataLoad.c_str()) - .def("__len__", [](const DataLoad & data) { return data.nb(); }) - .def("__getitem__", [](const DataLoad & data, int k){return data[k]; } ) - .def("__iter__", [](const DataLoad & data) { + py::class_(m, "LoadContainer", DocIterator::LoadContainer.c_str()) + .def("__len__", [](const LoadContainer & data) { return data.nb(); }) + .def("__getitem__", [](const LoadContainer & data, int k){return data[k]; } ) + .def("__iter__", [](const LoadContainer & data) { return py::make_iterator(data.begin(), data.end()); }, py::keep_alive<0, 1>()); /* Keep vector alive while iterator is used */ - py::class_(m, "LoadInfo", DocIterator::LoadInfo.c_str()) - .def_readonly("id", &DataLoad::LoadInfo::id, DocIterator::id.c_str()) - .def_readonly("name", &DataLoad::LoadInfo::name, DocIterator::name.c_str()) - .def_readonly("connected", &DataLoad::LoadInfo::connected, DocIterator::connected.c_str()) - .def_readonly("bus_id", &DataLoad::LoadInfo::bus_id, DocIterator::bus_id.c_str()) - .def_readonly("target_p_mw", &DataLoad::LoadInfo::target_p_mw, DocIterator::target_p_mw.c_str()) - .def_readonly("target_q_mvar", &DataLoad::LoadInfo::target_q_mvar, DocIterator::target_q_mvar.c_str()) - .def_readonly("has_res", &DataLoad::LoadInfo::has_res, DocIterator::has_res.c_str()) - .def_readonly("res_p_mw", &DataLoad::LoadInfo::res_p_mw, DocIterator::res_p_mw.c_str()) - .def_readonly("res_q_mvar", &DataLoad::LoadInfo::res_q_mvar, DocIterator::res_q_mvar.c_str()) - .def_readonly("res_theta_deg", &DataLoad::LoadInfo::res_theta_deg, DocIterator::res_theta_deg.c_str()) - .def_readonly("res_v_kv", &DataLoad::LoadInfo::res_v_kv, DocIterator::res_v_kv.c_str()); + py::class_(m, "LoadInfo", DocIterator::LoadInfo.c_str()) + .def_readonly("id", &LoadContainer::LoadInfo::id, DocIterator::id.c_str()) + .def_readonly("name", &LoadContainer::LoadInfo::name, DocIterator::name.c_str()) + .def_readonly("connected", &LoadContainer::LoadInfo::connected, DocIterator::connected.c_str()) + .def_readonly("bus_id", &LoadContainer::LoadInfo::bus_id, DocIterator::bus_id.c_str()) + .def_readonly("target_p_mw", &LoadContainer::LoadInfo::target_p_mw, DocIterator::target_p_mw.c_str()) + .def_readonly("target_q_mvar", &LoadContainer::LoadInfo::target_q_mvar, DocIterator::target_q_mvar.c_str()) + .def_readonly("has_res", &LoadContainer::LoadInfo::has_res, DocIterator::has_res.c_str()) + .def_readonly("res_p_mw", &LoadContainer::LoadInfo::res_p_mw, DocIterator::res_p_mw.c_str()) + .def_readonly("res_q_mvar", &LoadContainer::LoadInfo::res_q_mvar, DocIterator::res_q_mvar.c_str()) + .def_readonly("res_theta_deg", &LoadContainer::LoadInfo::res_theta_deg, DocIterator::res_theta_deg.c_str()) + .def_readonly("res_v_kv", &LoadContainer::LoadInfo::res_v_kv, DocIterator::res_v_kv.c_str()); // iterator for shunts - py::class_(m, "DataShunt", DocIterator::DataShunt.c_str()) - .def("__len__", [](const DataShunt & data) { return data.nb(); }) - .def("__getitem__", [](const DataShunt & data, int k){return data[k]; } ) - .def("__iter__", [](const DataShunt & data) { + py::class_(m, "ShuntContainer", DocIterator::ShuntContainer.c_str()) + .def("__len__", [](const ShuntContainer & data) { return data.nb(); }) + .def("__getitem__", [](const ShuntContainer & data, int k){return data[k]; } ) + .def("__iter__", [](const ShuntContainer & data) { return py::make_iterator(data.begin(), data.end()); }, py::keep_alive<0, 1>()); /* Keep vector alive while iterator is used */ - py::class_(m, "ShuntInfo", DocIterator::ShuntInfo.c_str()) - .def_readonly("id", &DataShunt::ShuntInfo::id, DocIterator::id.c_str()) - .def_readonly("name", &DataShunt::ShuntInfo::name, DocIterator::name.c_str()) - .def_readonly("connected", &DataShunt::ShuntInfo::connected, DocIterator::connected.c_str()) - .def_readonly("bus_id", &DataShunt::ShuntInfo::bus_id, DocIterator::bus_id.c_str()) - .def_readonly("target_p_mw", &DataShunt::ShuntInfo::target_p_mw, DocIterator::target_p_mw.c_str()) - .def_readonly("target_q_mvar", &DataShunt::ShuntInfo::target_q_mvar, DocIterator::target_q_mvar.c_str()) - .def_readonly("has_res", &DataShunt::ShuntInfo::has_res, DocIterator::has_res.c_str()) - .def_readonly("res_p_mw", &DataShunt::ShuntInfo::res_p_mw, DocIterator::res_p_mw.c_str()) - .def_readonly("res_q_mvar", &DataShunt::ShuntInfo::res_q_mvar, DocIterator::res_q_mvar.c_str()) - .def_readonly("res_theta_deg", &DataShunt::ShuntInfo::res_theta_deg, DocIterator::res_theta_deg.c_str()) - .def_readonly("res_v_kv", &DataShunt::ShuntInfo::res_v_kv, DocIterator::res_v_kv.c_str()); + py::class_(m, "ShuntInfo", DocIterator::ShuntInfo.c_str()) + .def_readonly("id", &ShuntContainer::ShuntInfo::id, DocIterator::id.c_str()) + .def_readonly("name", &ShuntContainer::ShuntInfo::name, DocIterator::name.c_str()) + .def_readonly("connected", &ShuntContainer::ShuntInfo::connected, DocIterator::connected.c_str()) + .def_readonly("bus_id", &ShuntContainer::ShuntInfo::bus_id, DocIterator::bus_id.c_str()) + .def_readonly("target_p_mw", &ShuntContainer::ShuntInfo::target_p_mw, DocIterator::target_p_mw.c_str()) + .def_readonly("target_q_mvar", &ShuntContainer::ShuntInfo::target_q_mvar, DocIterator::target_q_mvar.c_str()) + .def_readonly("has_res", &ShuntContainer::ShuntInfo::has_res, DocIterator::has_res.c_str()) + .def_readonly("res_p_mw", &ShuntContainer::ShuntInfo::res_p_mw, DocIterator::res_p_mw.c_str()) + .def_readonly("res_q_mvar", &ShuntContainer::ShuntInfo::res_q_mvar, DocIterator::res_q_mvar.c_str()) + .def_readonly("res_theta_deg", &ShuntContainer::ShuntInfo::res_theta_deg, DocIterator::res_theta_deg.c_str()) + .def_readonly("res_v_kv", &ShuntContainer::ShuntInfo::res_v_kv, DocIterator::res_v_kv.c_str()); // iterator for trafos - py::class_(m, "DataTrafo", DocIterator::DataTrafo.c_str()) - .def("__len__", [](const DataTrafo & data) { return data.nb(); }) - .def("__getitem__", [](const DataTrafo & data, int k){return data[k]; } ) - .def("__iter__", [](const DataTrafo & data) { + py::class_(m, "TrafoContainer", DocIterator::TrafoContainer.c_str()) + .def("__len__", [](const TrafoContainer & data) { return data.nb(); }) + .def("__getitem__", [](const TrafoContainer & data, int k){return data[k]; } ) + .def("__iter__", [](const TrafoContainer & data) { return py::make_iterator(data.begin(), data.end()); }, py::keep_alive<0, 1>()); /* Keep vector alive while iterator is used */ - py::class_(m, "TrafoInfo", DocIterator::TrafoInfo.c_str()) - .def_readonly("id", &DataTrafo::TrafoInfo::id, DocIterator::id.c_str()) - .def_readonly("name", &DataTrafo::TrafoInfo::name, DocIterator::name.c_str()) - .def_readonly("connected", &DataTrafo::TrafoInfo::connected, DocIterator::connected.c_str()) - .def_readonly("bus_hv_id", &DataTrafo::TrafoInfo::bus_hv_id, DocIterator::bus_hv_id.c_str()) - .def_readonly("bus_lv_id", &DataTrafo::TrafoInfo::bus_lv_id, DocIterator::bus_lv_id.c_str()) - .def_readonly("r_pu", &DataTrafo::TrafoInfo::r_pu, DocIterator::r_pu.c_str()) - .def_readonly("x_pu", &DataTrafo::TrafoInfo::x_pu, DocIterator::x_pu.c_str()) - .def_readonly("h_pu", &DataTrafo::TrafoInfo::h_pu, DocIterator::h_pu.c_str()) - .def_readonly("is_tap_hv_side", &DataTrafo::TrafoInfo::is_tap_hv_side, DocIterator::is_tap_hv_side.c_str()) - .def_readonly("ratio", &DataTrafo::TrafoInfo::ratio, DocIterator::ratio.c_str()) - .def_readonly("shift_rad", &DataTrafo::TrafoInfo::shift_rad, DocIterator::shift_rad.c_str()) - .def_readonly("has_res", &DataTrafo::TrafoInfo::has_res, DocIterator::has_res.c_str()) - .def_readonly("res_p_hv_mw", &DataTrafo::TrafoInfo::res_p_hv_mw, DocIterator::res_p_hv_mw.c_str()) - .def_readonly("res_q_hv_mvar", &DataTrafo::TrafoInfo::res_q_hv_mvar, DocIterator::res_q_hv_mvar.c_str()) - .def_readonly("res_v_hv_kv", &DataTrafo::TrafoInfo::res_v_hv_kv, DocIterator::res_v_hv_kv.c_str()) - .def_readonly("res_a_hv_ka", &DataTrafo::TrafoInfo::res_a_hv_ka, DocIterator::res_a_hv_ka.c_str()) - .def_readonly("res_p_lv_mw", &DataTrafo::TrafoInfo::res_p_lv_mw, DocIterator::res_p_lv_mw.c_str()) - .def_readonly("res_q_lv_mvar", &DataTrafo::TrafoInfo::res_q_lv_mvar, DocIterator::res_q_lv_mvar.c_str()) - .def_readonly("res_v_lv_kv", &DataTrafo::TrafoInfo::res_v_lv_kv, DocIterator::res_v_lv_kv.c_str()) - .def_readonly("res_a_lv_ka", &DataTrafo::TrafoInfo::res_a_lv_ka, DocIterator::res_a_lv_ka.c_str()) - .def_readonly("res_theta_hv_deg", &DataTrafo::TrafoInfo::res_theta_hv_deg, DocIterator::res_theta_hv_deg.c_str()) - .def_readonly("res_theta_lv_deg", &DataTrafo::TrafoInfo::res_theta_lv_deg, DocIterator::res_theta_lv_deg.c_str()); + py::class_(m, "TrafoInfo", DocIterator::TrafoInfo.c_str()) + .def_readonly("id", &TrafoContainer::TrafoInfo::id, DocIterator::id.c_str()) + .def_readonly("name", &TrafoContainer::TrafoInfo::name, DocIterator::name.c_str()) + .def_readonly("connected", &TrafoContainer::TrafoInfo::connected, DocIterator::connected.c_str()) + .def_readonly("bus_hv_id", &TrafoContainer::TrafoInfo::bus_hv_id, DocIterator::bus_hv_id.c_str()) + .def_readonly("bus_lv_id", &TrafoContainer::TrafoInfo::bus_lv_id, DocIterator::bus_lv_id.c_str()) + .def_readonly("r_pu", &TrafoContainer::TrafoInfo::r_pu, DocIterator::r_pu.c_str()) + .def_readonly("x_pu", &TrafoContainer::TrafoInfo::x_pu, DocIterator::x_pu.c_str()) + .def_readonly("h_pu", &TrafoContainer::TrafoInfo::h_pu, DocIterator::h_pu.c_str()) + .def_readonly("is_tap_hv_side", &TrafoContainer::TrafoInfo::is_tap_hv_side, DocIterator::is_tap_hv_side.c_str()) + .def_readonly("ratio", &TrafoContainer::TrafoInfo::ratio, DocIterator::ratio.c_str()) + .def_readonly("shift_rad", &TrafoContainer::TrafoInfo::shift_rad, DocIterator::shift_rad.c_str()) + .def_readonly("has_res", &TrafoContainer::TrafoInfo::has_res, DocIterator::has_res.c_str()) + .def_readonly("res_p_hv_mw", &TrafoContainer::TrafoInfo::res_p_hv_mw, DocIterator::res_p_hv_mw.c_str()) + .def_readonly("res_q_hv_mvar", &TrafoContainer::TrafoInfo::res_q_hv_mvar, DocIterator::res_q_hv_mvar.c_str()) + .def_readonly("res_v_hv_kv", &TrafoContainer::TrafoInfo::res_v_hv_kv, DocIterator::res_v_hv_kv.c_str()) + .def_readonly("res_a_hv_ka", &TrafoContainer::TrafoInfo::res_a_hv_ka, DocIterator::res_a_hv_ka.c_str()) + .def_readonly("res_p_lv_mw", &TrafoContainer::TrafoInfo::res_p_lv_mw, DocIterator::res_p_lv_mw.c_str()) + .def_readonly("res_q_lv_mvar", &TrafoContainer::TrafoInfo::res_q_lv_mvar, DocIterator::res_q_lv_mvar.c_str()) + .def_readonly("res_v_lv_kv", &TrafoContainer::TrafoInfo::res_v_lv_kv, DocIterator::res_v_lv_kv.c_str()) + .def_readonly("res_a_lv_ka", &TrafoContainer::TrafoInfo::res_a_lv_ka, DocIterator::res_a_lv_ka.c_str()) + .def_readonly("res_theta_hv_deg", &TrafoContainer::TrafoInfo::res_theta_hv_deg, DocIterator::res_theta_hv_deg.c_str()) + .def_readonly("res_theta_lv_deg", &TrafoContainer::TrafoInfo::res_theta_lv_deg, DocIterator::res_theta_lv_deg.c_str()); // iterator for trafos - py::class_(m, "DataLine", DocIterator::DataLine.c_str()) - .def("__len__", [](const DataLine & data) { return data.nb(); }) - .def("__getitem__", [](const DataLine & data, int k){return data[k]; } ) - .def("__iter__", [](const DataLine & data) { + py::class_(m, "LineContainer", DocIterator::LineContainer.c_str()) + .def("__len__", [](const LineContainer & data) { return data.nb(); }) + .def("__getitem__", [](const LineContainer & data, int k){return data[k]; } ) + .def("__iter__", [](const LineContainer & data) { return py::make_iterator(data.begin(), data.end()); }, py::keep_alive<0, 1>()); /* Keep vector alive while iterator is used */ - py::class_(m, "LineInfo", DocIterator::LineInfo.c_str()) - .def_readonly("id", &DataLine::LineInfo::id, DocIterator::id.c_str()) - .def_readonly("name", &DataLine::LineInfo::name, DocIterator::name.c_str()) - .def_readonly("connected", &DataLine::LineInfo::connected, DocIterator::connected.c_str()) - .def_readonly("bus_or_id", &DataLine::LineInfo::bus_or_id, DocIterator::bus_or_id.c_str()) - .def_readonly("bus_ex_id", &DataLine::LineInfo::bus_ex_id, DocIterator::bus_ex_id.c_str()) - .def_readonly("r_pu", &DataLine::LineInfo::r_pu, DocIterator::r_pu.c_str()) - .def_readonly("x_pu", &DataLine::LineInfo::x_pu, DocIterator::x_pu.c_str()) - .def_readonly("h_pu", &DataLine::LineInfo::h_pu, DocIterator::x_pu.c_str()) - .def_readonly("h_or_pu", &DataLine::LineInfo::h_or_pu, DocIterator::h_pu.c_str()) - .def_readonly("h_ex_pu", &DataLine::LineInfo::h_ex_pu, DocIterator::h_pu.c_str()) - .def_readonly("has_res", &DataLine::LineInfo::has_res, DocIterator::has_res.c_str()) - .def_readonly("res_p_or_mw", &DataLine::LineInfo::res_p_or_mw, DocIterator::res_p_or_mw.c_str()) - .def_readonly("res_q_or_mvar", &DataLine::LineInfo::res_q_or_mvar, DocIterator::res_q_or_mvar.c_str()) - .def_readonly("res_v_or_kv", &DataLine::LineInfo::res_v_or_kv, DocIterator::res_v_or_kv.c_str()) - .def_readonly("res_a_or_ka", &DataLine::LineInfo::res_a_or_ka, DocIterator::res_a_or_ka.c_str()) - .def_readonly("res_p_ex_mw", &DataLine::LineInfo::res_p_ex_mw, DocIterator::res_p_ex_mw.c_str()) - .def_readonly("res_q_ex_mvar", &DataLine::LineInfo::res_q_ex_mvar, DocIterator::res_q_ex_mvar.c_str()) - .def_readonly("res_v_ex_kv", &DataLine::LineInfo::res_v_ex_kv, DocIterator::res_v_ex_kv.c_str()) - .def_readonly("res_a_ex_ka", &DataLine::LineInfo::res_a_ex_ka, DocIterator::res_a_ex_ka.c_str()) - .def_readonly("res_theta_or_deg", &DataLine::LineInfo::res_theta_or_deg, DocIterator::res_theta_or_deg.c_str()) - .def_readonly("res_theta_ex_deg", &DataLine::LineInfo::res_theta_ex_deg, DocIterator::res_theta_ex_deg.c_str()); + py::class_(m, "LineInfo", DocIterator::LineInfo.c_str()) + .def_readonly("id", &LineContainer::LineInfo::id, DocIterator::id.c_str()) + .def_readonly("name", &LineContainer::LineInfo::name, DocIterator::name.c_str()) + .def_readonly("connected", &LineContainer::LineInfo::connected, DocIterator::connected.c_str()) + .def_readonly("bus_or_id", &LineContainer::LineInfo::bus_or_id, DocIterator::bus_or_id.c_str()) + .def_readonly("bus_ex_id", &LineContainer::LineInfo::bus_ex_id, DocIterator::bus_ex_id.c_str()) + .def_readonly("r_pu", &LineContainer::LineInfo::r_pu, DocIterator::r_pu.c_str()) + .def_readonly("x_pu", &LineContainer::LineInfo::x_pu, DocIterator::x_pu.c_str()) + .def_readonly("h_pu", &LineContainer::LineInfo::h_pu, DocIterator::x_pu.c_str()) + .def_readonly("h_or_pu", &LineContainer::LineInfo::h_or_pu, DocIterator::h_pu.c_str()) + .def_readonly("h_ex_pu", &LineContainer::LineInfo::h_ex_pu, DocIterator::h_pu.c_str()) + .def_readonly("has_res", &LineContainer::LineInfo::has_res, DocIterator::has_res.c_str()) + .def_readonly("res_p_or_mw", &LineContainer::LineInfo::res_p_or_mw, DocIterator::res_p_or_mw.c_str()) + .def_readonly("res_q_or_mvar", &LineContainer::LineInfo::res_q_or_mvar, DocIterator::res_q_or_mvar.c_str()) + .def_readonly("res_v_or_kv", &LineContainer::LineInfo::res_v_or_kv, DocIterator::res_v_or_kv.c_str()) + .def_readonly("res_a_or_ka", &LineContainer::LineInfo::res_a_or_ka, DocIterator::res_a_or_ka.c_str()) + .def_readonly("res_p_ex_mw", &LineContainer::LineInfo::res_p_ex_mw, DocIterator::res_p_ex_mw.c_str()) + .def_readonly("res_q_ex_mvar", &LineContainer::LineInfo::res_q_ex_mvar, DocIterator::res_q_ex_mvar.c_str()) + .def_readonly("res_v_ex_kv", &LineContainer::LineInfo::res_v_ex_kv, DocIterator::res_v_ex_kv.c_str()) + .def_readonly("res_a_ex_ka", &LineContainer::LineInfo::res_a_ex_ka, DocIterator::res_a_ex_ka.c_str()) + .def_readonly("res_theta_or_deg", &LineContainer::LineInfo::res_theta_or_deg, DocIterator::res_theta_or_deg.c_str()) + .def_readonly("res_theta_ex_deg", &LineContainer::LineInfo::res_theta_ex_deg, DocIterator::res_theta_ex_deg.c_str()); // iterator for dc lines - py::class_(m, "DataDCLine", DocIterator::DataDCLine.c_str()) - .def("__len__", [](const DataDCLine & data) { return data.nb(); }) - .def("__getitem__", [](const DataDCLine & data, int k){return data[k]; } ) - .def("__iter__", [](const DataDCLine & data) { + py::class_(m, "DCLineContainer", DocIterator::DCLineContainer.c_str()) + .def("__len__", [](const DCLineContainer & data) { return data.nb(); }) + .def("__getitem__", [](const DCLineContainer & data, int k){return data[k]; } ) + .def("__iter__", [](const DCLineContainer & data) { return py::make_iterator(data.begin(), data.end()); }, py::keep_alive<0, 1>()); /* Keep vector alive while iterator is used */ - py::class_(m, "DCLineInfo", DocIterator::DCLineInfo.c_str()) - .def_readonly("id", &DataDCLine::DCLineInfo::id, DocIterator::id.c_str()) - .def_readonly("name", &DataDCLine::DCLineInfo::name, DocIterator::name.c_str()) - .def_readonly("connected", &DataDCLine::DCLineInfo::connected, DocIterator::connected.c_str()) - .def_readonly("bus_or_id", &DataDCLine::DCLineInfo::bus_or_id, DocIterator::bus_or_id.c_str()) - .def_readonly("bus_ex_id", &DataDCLine::DCLineInfo::bus_ex_id, DocIterator::bus_ex_id.c_str()) - .def_readonly("target_p_or_mw", &DataDCLine::DCLineInfo::target_p_or_mw, DocIterator::target_p_or_mw.c_str()) - .def_readonly("target_vm_or_pu", &DataDCLine::DCLineInfo::target_vm_or_pu, DocIterator::target_vm_or_pu.c_str()) - .def_readonly("target_vm_ex_pu", &DataDCLine::DCLineInfo::target_vm_ex_pu, DocIterator::target_vm_ex_pu.c_str()) - .def_readonly("loss_pct", &DataDCLine::DCLineInfo::loss_pct, DocIterator::loss_pct.c_str()) - .def_readonly("loss_mw", &DataDCLine::DCLineInfo::loss_mw, DocIterator::loss_mw.c_str()) - .def_readonly("gen_or", &DataDCLine::DCLineInfo::gen_or, DocIterator::gen_or.c_str()) - .def_readonly("gen_ex", &DataDCLine::DCLineInfo::gen_ex, DocIterator::gen_ex.c_str()) - .def_readonly("has_res", &DataDCLine::DCLineInfo::has_res, DocIterator::has_res.c_str()) - .def_readonly("res_p_or_mw", &DataDCLine::DCLineInfo::res_p_or_mw, DocIterator::res_p_or_mw_dcline.c_str()) - .def_readonly("res_p_ex_mw", &DataDCLine::DCLineInfo::res_p_ex_mw, DocIterator::res_p_ex_mw_dcline.c_str()) - .def_readonly("res_q_or_mvar", &DataDCLine::DCLineInfo::res_q_or_mvar, DocIterator::res_q_or_mvar_dcline.c_str()) - .def_readonly("res_q_ex_mvar", &DataDCLine::DCLineInfo::res_q_ex_mvar, DocIterator::res_q_ex_mvar_dcline.c_str()) - .def_readonly("res_v_or_kv", &DataDCLine::DCLineInfo::res_v_or_kv, DocIterator::res_v_or_kv_dcline.c_str()) - .def_readonly("res_v_ex_kv", &DataDCLine::DCLineInfo::res_v_ex_kv, DocIterator::res_v_ex_kv_dcline.c_str()) - .def_readonly("res_theta_or_deg", &DataDCLine::DCLineInfo::res_theta_or_deg, DocIterator::res_theta_or_deg_dcline.c_str()) - .def_readonly("res_theta_ex_deg", &DataDCLine::DCLineInfo::res_theta_ex_deg, DocIterator::res_theta_ex_deg_dcline.c_str()) + py::class_(m, "DCLineInfo", DocIterator::DCLineInfo.c_str()) + .def_readonly("id", &DCLineContainer::DCLineInfo::id, DocIterator::id.c_str()) + .def_readonly("name", &DCLineContainer::DCLineInfo::name, DocIterator::name.c_str()) + .def_readonly("connected", &DCLineContainer::DCLineInfo::connected, DocIterator::connected.c_str()) + .def_readonly("bus_or_id", &DCLineContainer::DCLineInfo::bus_or_id, DocIterator::bus_or_id.c_str()) + .def_readonly("bus_ex_id", &DCLineContainer::DCLineInfo::bus_ex_id, DocIterator::bus_ex_id.c_str()) + .def_readonly("target_p_or_mw", &DCLineContainer::DCLineInfo::target_p_or_mw, DocIterator::target_p_or_mw.c_str()) + .def_readonly("target_vm_or_pu", &DCLineContainer::DCLineInfo::target_vm_or_pu, DocIterator::target_vm_or_pu.c_str()) + .def_readonly("target_vm_ex_pu", &DCLineContainer::DCLineInfo::target_vm_ex_pu, DocIterator::target_vm_ex_pu.c_str()) + .def_readonly("loss_pct", &DCLineContainer::DCLineInfo::loss_pct, DocIterator::loss_pct.c_str()) + .def_readonly("loss_mw", &DCLineContainer::DCLineInfo::loss_mw, DocIterator::loss_mw.c_str()) + .def_readonly("gen_or", &DCLineContainer::DCLineInfo::gen_or, DocIterator::gen_or.c_str()) + .def_readonly("gen_ex", &DCLineContainer::DCLineInfo::gen_ex, DocIterator::gen_ex.c_str()) + .def_readonly("has_res", &DCLineContainer::DCLineInfo::has_res, DocIterator::has_res.c_str()) + .def_readonly("res_p_or_mw", &DCLineContainer::DCLineInfo::res_p_or_mw, DocIterator::res_p_or_mw_dcline.c_str()) + .def_readonly("res_p_ex_mw", &DCLineContainer::DCLineInfo::res_p_ex_mw, DocIterator::res_p_ex_mw_dcline.c_str()) + .def_readonly("res_q_or_mvar", &DCLineContainer::DCLineInfo::res_q_or_mvar, DocIterator::res_q_or_mvar_dcline.c_str()) + .def_readonly("res_q_ex_mvar", &DCLineContainer::DCLineInfo::res_q_ex_mvar, DocIterator::res_q_ex_mvar_dcline.c_str()) + .def_readonly("res_v_or_kv", &DCLineContainer::DCLineInfo::res_v_or_kv, DocIterator::res_v_or_kv_dcline.c_str()) + .def_readonly("res_v_ex_kv", &DCLineContainer::DCLineInfo::res_v_ex_kv, DocIterator::res_v_ex_kv_dcline.c_str()) + .def_readonly("res_theta_or_deg", &DCLineContainer::DCLineInfo::res_theta_or_deg, DocIterator::res_theta_or_deg_dcline.c_str()) + .def_readonly("res_theta_ex_deg", &DCLineContainer::DCLineInfo::res_theta_ex_deg, DocIterator::res_theta_ex_deg_dcline.c_str()) ; // converters @@ -592,11 +593,32 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) .def("get_line_param", &PandaPowerConverter::get_line_param) .def("get_trafo_param", &PandaPowerConverter::get_trafo_param); + py::class_(m, "SolverControl", "TODO") + .def(py::init<>()) + .def("has_dimension_changed", &SolverControl::has_dimension_changed, "TODO") + .def("has_pv_changed", &SolverControl::has_pv_changed, "TODO") + .def("has_pq_changed", &SolverControl::has_pq_changed, "TODO") + .def("has_slack_participate_changed", &SolverControl::has_slack_participate_changed, "TODO") + .def("need_reset_solver", &SolverControl::need_reset_solver, "TODO") + .def("need_recompute_sbus", &SolverControl::need_recompute_sbus, "TODO") + .def("need_recompute_ybus", &SolverControl::need_recompute_ybus, "TODO") + .def("ybus_change_sparsity_pattern", &SolverControl::ybus_change_sparsity_pattern, "TODO") + .def("has_slack_weight_changed", &SolverControl::has_slack_weight_changed, "TODO") + .def("has_v_changed", &SolverControl::has_v_changed, "TODO") + .def("has_ybus_some_coeffs_zero", &SolverControl::has_ybus_some_coeffs_zero, "TODO") + ; + py::class_(m, "GridModel", DocGridModel::GridModel.c_str()) .def(py::init<>()) .def("copy", &GridModel::copy) .def_property("_ls_to_orig", &GridModel::get_ls_to_orig, &GridModel::set_ls_to_orig, "remember the conversion from bus index in lightsim2grid to bus index in original file format (*eg* pandapower of pypowsybl).") .def_property("_orig_to_ls", &GridModel::get_orig_to_ls, &GridModel::set_orig_to_ls, "remember the conversion from bus index in original file format (*eg* pandapower of pypowsybl) to bus index in lightsim2grid.") + .def_property("_max_nb_bus_per_sub", + &GridModel::get_max_nb_bus_per_sub, + &GridModel::set_max_nb_bus_per_sub, + "do not modify it after loading !") + .def_property_readonly("timer_last_ac_pf", &GridModel::timer_last_ac_pf, "TODO") + .def_property_readonly("timer_last_dc_pf", &GridModel::timer_last_dc_pf, "TODO") // pickle .def(py::pickle( @@ -799,8 +821,12 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) .def("reactivate_result_computation", &GridModel::reactivate_result_computation, DocGridModel::reactivate_result_computation.c_str()) .def("dc_pf", &GridModel::dc_pf, DocGridModel::dc_pf.c_str()) .def("ac_pf", &GridModel::ac_pf, DocGridModel::ac_pf.c_str()) - .def("unset_topo_changed", &GridModel::unset_topo_changed, DocGridModel::_internal_do_not_use.c_str()) - .def("tell_topo_changed", &GridModel::tell_topo_changed, DocGridModel::_internal_do_not_use.c_str()) + .def("unset_changes", &GridModel::unset_changes, DocGridModel::_internal_do_not_use.c_str()) + .def("tell_recompute_ybus", &GridModel::tell_recompute_ybus, DocGridModel::_internal_do_not_use.c_str()) + .def("tell_recompute_sbus", &GridModel::tell_recompute_sbus, DocGridModel::_internal_do_not_use.c_str()) + .def("tell_solver_need_reset", &GridModel::tell_solver_need_reset, DocGridModel::_internal_do_not_use.c_str()) + .def("tell_ybus_change_sparsity_pattern", &GridModel::tell_ybus_change_sparsity_pattern, DocGridModel::_internal_do_not_use.c_str()) + .def("get_solver_control", &GridModel::get_solver_control, "TODO") .def("compute_newton", &GridModel::ac_pf, DocGridModel::ac_pf.c_str()) .def("get_ptdf", &GridModel::get_ptdf, DocGridModel::_internal_do_not_use.c_str()) // TODO PTDF .def("get_Bf", &GridModel::get_Bf, DocGridModel::_internal_do_not_use.c_str()) // TODO PTDF @@ -818,6 +844,7 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) // auxiliary functions .def("set_n_sub", &GridModel::set_n_sub, DocGridModel::_internal_do_not_use.c_str()) + .def("set_max_nb_bus_per_sub", &GridModel::set_max_nb_bus_per_sub, DocGridModel::_internal_do_not_use.c_str()) .def("set_load_pos_topo_vect", &GridModel::set_load_pos_topo_vect, DocGridModel::_internal_do_not_use.c_str()) .def("set_gen_pos_topo_vect", &GridModel::set_gen_pos_topo_vect, DocGridModel::_internal_do_not_use.c_str()) .def("set_line_or_pos_topo_vect", &GridModel::set_line_or_pos_topo_vect, DocGridModel::_internal_do_not_use.c_str()) @@ -838,78 +865,78 @@ PYBIND11_MODULE(lightsim2grid_cpp, m) .def("debug_get_Bpp_python", &GridModel::debug_get_Bpp_python, DocGridModel::_internal_do_not_use.c_str()) ; - py::class_(m, "Computers", DocComputers::Computers.c_str()) + py::class_(m, "TimeSeriesCPP", DocComputers::Computers.c_str()) .def(py::init()) // solver control - .def("change_solver", &Computers::change_solver, DocGridModel::change_solver.c_str()) - .def("available_solvers", &Computers::available_solvers, DocGridModel::available_solvers.c_str()) - .def("get_solver_type", &Computers::get_solver_type, DocGridModel::get_solver_type.c_str()) + .def("change_solver", &TimeSeries::change_solver, DocGridModel::change_solver.c_str()) + .def("available_solvers", &TimeSeries::available_solvers, DocGridModel::available_solvers.c_str()) + .def("get_solver_type", &TimeSeries::get_solver_type, DocGridModel::get_solver_type.c_str()) // timers - .def("total_time", &Computers::total_time, DocComputers::total_time.c_str()) - .def("solver_time", &Computers::solver_time, DocComputers::solver_time.c_str()) - .def("preprocessing_time", &Computers::preprocessing_time, DocComputers::preprocessing_time.c_str()) - .def("amps_computation_time", &Computers::amps_computation_time, DocComputers::amps_computation_time.c_str()) - .def("nb_solved", &Computers::nb_solved, DocComputers::nb_solved.c_str()) + .def("total_time", &TimeSeries::total_time, DocComputers::total_time.c_str()) + .def("solver_time", &TimeSeries::solver_time, DocComputers::solver_time.c_str()) + .def("preprocessing_time", &TimeSeries::preprocessing_time, DocComputers::preprocessing_time.c_str()) + .def("amps_computation_time", &TimeSeries::amps_computation_time, DocComputers::amps_computation_time.c_str()) + .def("nb_solved", &TimeSeries::nb_solved, DocComputers::nb_solved.c_str()) // status - .def("get_status", &Computers::get_status, DocComputers::get_status.c_str()) - .def("clear", &Computers::clear, DocComputers::clear.c_str()) - .def("close", &Computers::clear, DocComputers::clear.c_str()) + .def("get_status", &TimeSeries::get_status, DocComputers::get_status.c_str()) + .def("clear", &TimeSeries::clear, DocComputers::clear.c_str()) + .def("close", &TimeSeries::clear, DocComputers::clear.c_str()) // perform the computations - .def("compute_Vs", &Computers::compute_Vs, py::call_guard(), DocComputers::compute_Vs.c_str()) - .def("compute_flows", &Computers::compute_flows, py::call_guard(), DocComputers::compute_flows.c_str()) - .def("compute_power_flows", &Computers::compute_power_flows, DocComputers::compute_power_flows.c_str()) // need to be done after "compute_Vs" and "compute_flows" + .def("compute_Vs", &TimeSeries::compute_Vs, py::call_guard(), DocComputers::compute_Vs.c_str()) + .def("compute_flows", &TimeSeries::compute_flows, py::call_guard(), DocComputers::compute_flows.c_str()) + .def("compute_power_flows", &TimeSeries::compute_power_flows, DocComputers::compute_power_flows.c_str()) // need to be done after "compute_Vs" and "compute_flows" // results (for now only flow (at each -line origin- or voltages -at each buses) - .def("get_flows", &Computers::get_flows, DocComputers::get_flows.c_str()) // need to be done after "compute_Vs" and "compute_flows" - .def("get_power_flows", &Computers::get_power_flows, DocComputers::get_power_flows.c_str()) // need to be done after "compute_Vs" and "compute_flows" - .def("get_voltages", &Computers::get_voltages, DocComputers::get_voltages.c_str()) // need to be done after "compute_Vs" - .def("get_sbuses", &Computers::get_sbuses, DocComputers::get_sbuses.c_str()) // need to be done after "compute_Vs" + .def("get_flows", &TimeSeries::get_flows, DocComputers::get_flows.c_str()) // need to be done after "compute_Vs" and "compute_flows" + .def("get_power_flows", &TimeSeries::get_power_flows, DocComputers::get_power_flows.c_str()) // need to be done after "compute_Vs" and "compute_flows" + .def("get_voltages", &TimeSeries::get_voltages, DocComputers::get_voltages.c_str()) // need to be done after "compute_Vs" + .def("get_sbuses", &TimeSeries::get_sbuses, DocComputers::get_sbuses.c_str()) // need to be done after "compute_Vs" ; - py::class_(m, "SecurityAnalysisCPP", DocSecurityAnalysis::SecurityAnalysis.c_str()) + py::class_(m, "ContingencyAnalysisCPP", DocSecurityAnalysis::SecurityAnalysis.c_str()) .def(py::init()) // solver control - .def("change_solver", &Computers::change_solver, DocGridModel::change_solver.c_str()) - .def("available_solvers", &Computers::available_solvers, DocGridModel::available_solvers.c_str()) - .def("get_solver_type", &Computers::get_solver_type, DocGridModel::get_solver_type.c_str()) + .def("change_solver", &ContingencyAnalysis::change_solver, DocGridModel::change_solver.c_str()) + .def("available_solvers", &ContingencyAnalysis::available_solvers, DocGridModel::available_solvers.c_str()) + .def("get_solver_type", &ContingencyAnalysis::get_solver_type, DocGridModel::get_solver_type.c_str()) // add some defaults - .def("add_all_n1", &SecurityAnalysis::add_all_n1, DocSecurityAnalysis::add_all_n1.c_str()) - .def("add_n1", &SecurityAnalysis::add_n1, DocSecurityAnalysis::add_n1.c_str()) - .def("add_nk", &SecurityAnalysis::add_nk, DocSecurityAnalysis::add_nk.c_str()) - .def("add_multiple_n1", &SecurityAnalysis::add_multiple_n1, DocSecurityAnalysis::add_multiple_n1.c_str()) + .def("add_all_n1", &ContingencyAnalysis::add_all_n1, DocSecurityAnalysis::add_all_n1.c_str()) + .def("add_n1", &ContingencyAnalysis::add_n1, DocSecurityAnalysis::add_n1.c_str()) + .def("add_nk", &ContingencyAnalysis::add_nk, DocSecurityAnalysis::add_nk.c_str()) + .def("add_multiple_n1", &ContingencyAnalysis::add_multiple_n1, DocSecurityAnalysis::add_multiple_n1.c_str()) // remove some defaults (TODO) - .def("reset", &SecurityAnalysis::clear, DocSecurityAnalysis::clear.c_str()) - .def("clear", &SecurityAnalysis::clear, DocSecurityAnalysis::clear.c_str()) - .def("close", &SecurityAnalysis::clear, DocComputers::clear.c_str()) - .def("remove_n1", &SecurityAnalysis::remove_n1, DocSecurityAnalysis::remove_n1.c_str()) - .def("remove_nk", &SecurityAnalysis::remove_nk, DocSecurityAnalysis::remove_nk.c_str()) - .def("remove_multiple_n1", &SecurityAnalysis::remove_multiple_n1, DocSecurityAnalysis::remove_multiple_n1.c_str()) + .def("reset", &ContingencyAnalysis::clear, DocSecurityAnalysis::clear.c_str()) + .def("clear", &ContingencyAnalysis::clear, DocSecurityAnalysis::clear.c_str()) + .def("close", &ContingencyAnalysis::clear, DocComputers::clear.c_str()) + .def("remove_n1", &ContingencyAnalysis::remove_n1, DocSecurityAnalysis::remove_n1.c_str()) + .def("remove_nk", &ContingencyAnalysis::remove_nk, DocSecurityAnalysis::remove_nk.c_str()) + .def("remove_multiple_n1", &ContingencyAnalysis::remove_multiple_n1, DocSecurityAnalysis::remove_multiple_n1.c_str()) // inspect the class - .def("my_defaults", &SecurityAnalysis::my_defaults_vect, DocSecurityAnalysis::my_defaults_vect.c_str()) + .def("my_defaults", &ContingencyAnalysis::my_defaults_vect, DocSecurityAnalysis::my_defaults_vect.c_str()) // perform the computation - .def("compute", &SecurityAnalysis::compute, py::call_guard(), DocSecurityAnalysis::compute.c_str()) - .def("compute_flows", &SecurityAnalysis::compute_flows, py::call_guard(), DocSecurityAnalysis::compute_flows.c_str()) - .def("compute_power_flows", &SecurityAnalysis::compute_power_flows, DocSecurityAnalysis::compute_power_flows.c_str()) + .def("compute", &ContingencyAnalysis::compute, py::call_guard(), DocSecurityAnalysis::compute.c_str()) + .def("compute_flows", &ContingencyAnalysis::compute_flows, py::call_guard(), DocSecurityAnalysis::compute_flows.c_str()) + .def("compute_power_flows", &ContingencyAnalysis::compute_power_flows, DocSecurityAnalysis::compute_power_flows.c_str()) // results (for now only flow (at each -line origin- or voltages -at each buses) - .def("get_flows", &SecurityAnalysis::get_flows, DocSecurityAnalysis::get_flows.c_str()) - .def("get_voltages", &SecurityAnalysis::get_voltages, DocSecurityAnalysis::get_voltages.c_str()) - .def("get_power_flows", &SecurityAnalysis::get_power_flows, DocSecurityAnalysis::get_power_flows.c_str()) + .def("get_flows", &ContingencyAnalysis::get_flows, DocSecurityAnalysis::get_flows.c_str()) + .def("get_voltages", &ContingencyAnalysis::get_voltages, DocSecurityAnalysis::get_voltages.c_str()) + .def("get_power_flows", &ContingencyAnalysis::get_power_flows, DocSecurityAnalysis::get_power_flows.c_str()) // timers - .def("total_time", &SecurityAnalysis::total_time, DocComputers::total_time.c_str()) - .def("solver_time", &SecurityAnalysis::solver_time, DocComputers::solver_time.c_str()) - .def("preprocessing_time", &SecurityAnalysis::preprocessing_time, DocSecurityAnalysis::preprocessing_time.c_str()) - .def("amps_computation_time", &SecurityAnalysis::amps_computation_time, DocComputers::amps_computation_time.c_str()) - .def("modif_Ybus_time", &SecurityAnalysis::modif_Ybus_time, DocSecurityAnalysis::modif_Ybus_time.c_str()) - .def("nb_solved", &SecurityAnalysis::nb_solved, DocComputers::nb_solved.c_str()) + .def("total_time", &ContingencyAnalysis::total_time, DocComputers::total_time.c_str()) + .def("solver_time", &ContingencyAnalysis::solver_time, DocComputers::solver_time.c_str()) + .def("preprocessing_time", &ContingencyAnalysis::preprocessing_time, DocSecurityAnalysis::preprocessing_time.c_str()) + .def("amps_computation_time", &ContingencyAnalysis::amps_computation_time, DocComputers::amps_computation_time.c_str()) + .def("modif_Ybus_time", &ContingencyAnalysis::modif_Ybus_time, DocSecurityAnalysis::modif_Ybus_time.c_str()) + .def("nb_solved", &ContingencyAnalysis::nb_solved, DocComputers::nb_solved.c_str()) ; } diff --git a/src/BaseSolver.cpp b/src/powerflow_algorithm/BaseAlgo.cpp similarity index 79% rename from src/BaseSolver.cpp rename to src/powerflow_algorithm/BaseAlgo.cpp index 1dea244..1644b58 100644 --- a/src/BaseSolver.cpp +++ b/src/powerflow_algorithm/BaseAlgo.cpp @@ -6,23 +6,28 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "BaseSolver.h" +#include "BaseAlgo.h" #include "GridModel.h" // needs to be included here because of the forward declaration -void BaseSolver::reset(){ + +void BaseAlgo::reset(){ // reset timers reset_timer(); //reset the attribute n_ = -1; Vm_ = RealVect(); // voltage magnitude - Va_= RealVect(); // voltage angle - V_= RealVect(); // voltage angle + Va_ = RealVect(); // voltage angle + V_ = RealVect(); // voltage angle // TODO solver control: see if I could reuse some of these nr_iter_ = 0; // number of iteration performs by the algorithm err_ = ErrorType::NotInitError; //error message: + + _solver_control = SolverControl(); + _solver_control.tell_all_changed(); } -RealVect BaseSolver::_evaluate_Fx(const Eigen::SparseMatrix & Ybus, + +RealVect BaseAlgo::_evaluate_Fx(const Eigen::SparseMatrix & Ybus, const CplxVect & V, const CplxVect & Sbus, const Eigen::VectorXi & pv, @@ -48,7 +53,7 @@ RealVect BaseSolver::_evaluate_Fx(const Eigen::SparseMatrix & Ybus, return res; } -RealVect BaseSolver::_evaluate_Fx(const Eigen::SparseMatrix & Ybus, +RealVect BaseAlgo::_evaluate_Fx(const Eigen::SparseMatrix & Ybus, const CplxVect & V, const CplxVect & Sbus, Eigen::Index slack_id, // id of the ref slack bus @@ -112,17 +117,18 @@ RealVect BaseSolver::_evaluate_Fx(const Eigen::SparseMatrix & Ybus, } -bool BaseSolver::_check_for_convergence(const RealVect & F, +bool BaseAlgo::_check_for_convergence(const RealVect & F, real_type tol) { auto timer = CustTimer(); const auto norm_inf = F.lpNorm(); + // std::cout << "\t\tnorm_inf: " << norm_inf << std::endl; bool res = norm_inf < tol; timer_check_ += timer.duration(); return res; } -bool BaseSolver::_check_for_convergence(const RealVect & p, +bool BaseAlgo::_check_for_convergence(const RealVect & p, const RealVect & q, real_type tol) { @@ -134,7 +140,7 @@ bool BaseSolver::_check_for_convergence(const RealVect & p, return res; } -Eigen::VectorXi BaseSolver::extract_slack_bus_id(const Eigen::VectorXi & pv, +Eigen::VectorXi BaseAlgo::extract_slack_bus_id(const Eigen::VectorXi & pv, const Eigen::VectorXi & pq, unsigned int nb_bus) { @@ -142,43 +148,45 @@ Eigen::VectorXi BaseSolver::extract_slack_bus_id(const Eigen::VectorXi & pv, // pq: list of index of pq nodes // nb_bus: total number of bus in the grid // returns: res: the ids of all the slack buses (by def: not PV and not PQ) - - Eigen::VectorXi res(nb_bus - pv.size() - pq.size()); + int nb_slacks = nb_bus - pv.size() - pq.size(); + if(nb_slacks == 0){ + // TODO DEBUG MODE + throw std::runtime_error("BaseAlgo::extract_slack_bus_id: All buses are tagged as PV or PQ, there can be no slack."); + } + Eigen::VectorXi res(nb_slacks); Eigen::Index i_res = 0; - // run through both pv and pq nodes and declare they are not slack bus std::vector tmp(nb_bus, true); - for(unsigned int k=0; k < pv.size(); ++k) - { - tmp[pv[k]] = false; - } - for(unsigned int k=0; k < pq.size(); ++k) - { - tmp[pq[k]] = false; - } + for(auto pv_i : pv) tmp[pv_i] = false; + for(auto pq_i : pq) tmp[pq_i] = false; + // run through all buses for(unsigned int k=0; k < nb_bus; ++k) { if(tmp[k]) { + if((i_res >= nb_slacks)){ + // TODO DEBUG MODE + throw std::runtime_error("BaseAlgo::extract_slack_bus_id: too many slack found. Maybe a bus is both PV and PQ ?"); + } res[i_res] = k; ++i_res; } } if(res.size() != i_res){ // TODO DEBUG MODE - throw std::runtime_error("BaseSolver::extract_slack_bus_id: No slack bus is found in your grid"); + throw std::runtime_error("BaseAlgo::extract_slack_bus_id: Some slacks are not found in your grid."); } return res; } -void BaseSolver::get_Bf(Eigen::SparseMatrix & Bf) const { +void BaseAlgo::get_Bf(Eigen::SparseMatrix & Bf) const { if(IS_AC) throw std::runtime_error("get_Bf: impossible to use this in AC mode for now"); _gridmodel->fillBf_for_PTDF(Bf); } -void BaseSolver::get_Bf_transpose(Eigen::SparseMatrix & Bf_T) const { +void BaseAlgo::get_Bf_transpose(Eigen::SparseMatrix & Bf_T) const { if(IS_AC) throw std::runtime_error("get_Bf: impossible to use this in AC mode for now"); _gridmodel->fillBf_for_PTDF(Bf_T, true); } diff --git a/src/BaseSolver.h b/src/powerflow_algorithm/BaseAlgo.h similarity index 94% rename from src/BaseSolver.h rename to src/powerflow_algorithm/BaseAlgo.h index 97c7ca4..8556609 100644 --- a/src/BaseSolver.h +++ b/src/powerflow_algorithm/BaseAlgo.h @@ -6,8 +6,8 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef BASESOLVER_H -#define BASESOLVER_H +#ifndef BASEALGO_H +#define BASEALGO_H #include #include @@ -32,17 +32,17 @@ class GridModel; /** -This class represents a solver to compute powerflow. +This class represents a algorithm to compute powerflow. It can be derived for different usecase, for example for DC powerflow, AC powerflow using Newton Raphson method etc. **/ -class BaseSolver : public BaseConstants +class BaseAlgo : public BaseConstants { public: const bool IS_AC; // should be static ideally... public: - BaseSolver(bool is_ac=true): + BaseAlgo(bool is_ac=true): BaseConstants(), IS_AC(is_ac), n_(-1), @@ -52,7 +52,7 @@ class BaseSolver : public BaseConstants timer_check_(0.), timer_total_nr_(0.){}; - virtual ~BaseSolver(){} + virtual ~BaseAlgo(){} void set_gridmodel(const GridModel * gridmodel){ _gridmodel = gridmodel; @@ -98,6 +98,10 @@ class BaseSolver : public BaseConstants real_type tol ) = 0 ; + void tell_solver_control(const SolverControl & solver_control){ + _solver_control = solver_control; + } + virtual void reset(); virtual RealMat get_ptdf(const Eigen::SparseMatrix & dcYbus){ throw std::runtime_error("Impossible to get the PTDF matrix with this solver type."); } @@ -107,8 +111,6 @@ class BaseSolver : public BaseConstants virtual Eigen::SparseMatrix get_bsdf(){ // TODO interface is likely to change throw std::runtime_error("Impossible to get the BSDF matrix with this solver type."); } - - virtual void reset(); protected: virtual void reset_timer(){ @@ -227,12 +229,13 @@ class BaseSolver : public BaseConstants double timer_total_nr_; const GridModel * _gridmodel; // does not have ownership so that's fine (pointer to the base gridmodel, can be used for some powerflow) - + SolverControl _solver_control; + private: // no copy allowed - BaseSolver( const BaseSolver & ) ; - BaseSolver & operator=( const BaseSolver & ) ; + BaseAlgo( const BaseAlgo & ) ; + BaseAlgo & operator=( const BaseAlgo & ) ; }; -#endif // BASESOLVER_H +#endif // BASEALGO_H diff --git a/src/DCSolver.h b/src/powerflow_algorithm/BaseDCAlgo.h similarity index 87% rename from src/DCSolver.h rename to src/powerflow_algorithm/BaseDCAlgo.h index eeb14a2..06e6935 100644 --- a/src/DCSolver.h +++ b/src/powerflow_algorithm/BaseDCAlgo.h @@ -6,23 +6,23 @@ // 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 +#ifndef BASE_DC_ALGO_H +#define BASE_DC_ALGO_H + +#include "BaseAlgo.h" -#include "BaseSolver.h" -// TODO make err_ more explicit: use an enum template -class BaseDCSolver: public BaseSolver +class BaseDCAlgo: public BaseAlgo { public: - BaseDCSolver(): - BaseSolver(false), + BaseDCAlgo(): + BaseAlgo(false), _linear_solver(), need_factorize_(true), sizeYbus_with_slack_(0), sizeYbus_without_slack_(0){}; - ~BaseDCSolver(){} + ~BaseDCAlgo(){} virtual void reset(); @@ -45,8 +45,8 @@ class BaseDCSolver: public BaseSolver private: // no copy allowed - BaseDCSolver( const BaseSolver & ) =delete ; - BaseDCSolver & operator=( const BaseSolver & ) =delete; + BaseDCAlgo( const BaseDCAlgo & ) =delete ; + BaseDCAlgo & operator=( const BaseDCAlgo & ) =delete; protected: void fill_mat_bus_id(int nb_bus_solver); @@ -72,6 +72,6 @@ class BaseDCSolver: public BaseSolver }; -#include "DCSolver.tpp" +#include "BaseDCAlgo.tpp" -#endif // DCSOLVER_H +#endif // BASE_DC_ALGO_H diff --git a/src/DCSolver.tpp b/src/powerflow_algorithm/BaseDCAlgo.tpp similarity index 64% rename from src/DCSolver.tpp rename to src/powerflow_algorithm/BaseDCAlgo.tpp index fee67b2..7de831f 100644 --- a/src/DCSolver.tpp +++ b/src/powerflow_algorithm/BaseDCAlgo.tpp @@ -10,82 +10,115 @@ // 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 - ) +bool BaseDCAlgo::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. + if(!is_linear_solver_valid()) { + // std::cout << "!is_linear_solver_valid()\n"; + return false; + } + BaseAlgo::reset_timer(); + bool doesnt_need_refactor = true; + auto timer = CustTimer(); - BaseSolver::reset_timer(); + if(need_factorize_ || + _solver_control.need_reset_solver() || + _solver_control.has_dimension_changed() || + _solver_control.has_slack_participate_changed() || // the full "ybus without slack" has changed, everything needs to be recomputed_solver_control.ybus_change_sparsity_pattern() + _solver_control.ybus_change_sparsity_pattern() || + _solver_control.has_ybus_some_coeffs_zero() + ){ + reset(); + } + sizeYbus_with_slack_ = static_cast(Ybus.rows()); #ifdef __COUT_TIMES auto timer_preproc = CustTimer(); #endif // __COUT_TIMES - const CplxVect & Sbus_tmp = Sbus; + if(need_factorize_ || + _solver_control.has_pv_changed() || + _solver_control.has_pq_changed()) { - // 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 ! - my_pv_ = retrieve_pv_with_slack(slack_ids, pv); - // const Eigen::VectorXi & my_pv = pv; + // 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 ! + // std::cout << "\tneed to retrieve slack\n"; + my_pv_ = retrieve_pv_with_slack(slack_ids, pv); - // find the slack buses - slack_buses_ids_solver_ = extract_slack_bus_id(my_pv_, pq, sizeYbus_with_slack_); - sizeYbus_without_slack_ = sizeYbus_with_slack_ - slack_buses_ids_solver_.size(); + // find the slack buses + slack_buses_ids_solver_ = extract_slack_bus_id(my_pv_, pq, sizeYbus_with_slack_); + sizeYbus_without_slack_ = sizeYbus_with_slack_ - slack_buses_ids_solver_.size(); - // corresp bus -> solverbus - fill_mat_bus_id(sizeYbus_with_slack_); + // corresp bus -> solverbus + fill_mat_bus_id(sizeYbus_with_slack_); + } // remove the slack bus from Ybus - // and extract only real part - fill_dcYbus_noslack(sizeYbus_with_slack_, Ybus); + if(need_factorize_ || + _solver_control.need_recompute_ybus() || + _solver_control.ybus_change_sparsity_pattern() || + _solver_control.has_ybus_some_coeffs_zero()) { + // std::cout << "\tneed to change Ybus\n"; + fill_dcYbus_noslack(sizeYbus_with_slack_, Ybus); + doesnt_need_refactor = false; // force a call to "factor" the linear solver as the lhs (ybus) changed + // no need to refactor if ybus did not change + } #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; + + // remove the slack bus from Sbus + if(need_factorize_ || _solver_control.need_recompute_sbus()){ + // std::cout << "\tneed to compute Sbus\n"; + dcSbus_noslack_ = RealVect::Constant(sizeYbus_without_slack_, my_zero_); + for (int k=0; k < sizeYbus_with_slack_; ++k){ + if(mat_bus_id_(k) == -1) continue; // I don't add anything to the slack bus + const int col_res = mat_bus_id_(k); + dcSbus_noslack_(col_res) = std::real(Sbus(k)); + } + } + + // initialize the solver if needed if(need_factorize_){ + // std::cout << "\tneed to factorize\n"; ErrorType status_init = _linear_solver.initialize(dcYbus_noslack_); if(status_init != ErrorType::NoError){ err_ = status_init; + // std::cout << "_linear_solver.initialize\n"; return false; } need_factorize_ = false; - just_factorize = true; - } - - // remove the slack bus from Sbus - dcSbus_noslack_ = RealVect::Constant(sizeYbus_without_slack_, my_zero_); - for (int k=0; k < sizeYbus_with_slack_; ++k){ - if(mat_bus_id_(k) == -1) continue; // I don't add anything to the slack bus - const int col_res = mat_bus_id_(k); - dcSbus_noslack_(col_res) = std::real(Sbus_tmp(k)); + doesnt_need_refactor = true; } // solve for theta: Sbus = dcY . theta (make a copy to keep dcSbus_noslack_) RealVect Va_dc_without_slack = dcSbus_noslack_; - ErrorType error = _linear_solver.solve(dcYbus_noslack_, Va_dc_without_slack, just_factorize); + ErrorType error = _linear_solver.solve(dcYbus_noslack_, Va_dc_without_slack, doesnt_need_refactor); if(error != ErrorType::NoError){ err_ = error; timer_total_nr_ += timer.duration(); + // std::cout << "_linear_solver.solve\n"; return false; } @@ -98,8 +131,10 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix Vm_ = RealVect(); Va_ = RealVect(); timer_total_nr_ += timer.duration(); + // std::cout << "_linear_solver.allFinite" << Va_dc_without_slack.array().allFinite() <<", " << Va_dc_without_slack.lpNorm() <<"\n"; return false; } + // std::cout << "\t " << Va_dc_without_slack.lpNorm() << "\n"; #ifdef __COUT_TIMES std::cout << "\t dc solve: " << 1000. * timer_solve.duration() << "ms" << std::endl; @@ -108,7 +143,7 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix // 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 + // see the BaseAlgo.cpp: _evaluate_Fx RealVect Va_dc = RealVect::Constant(sizeYbus_with_slack_, my_zero_); // fill Va from dc approx for (int ybus_id=0; ybus_id < sizeYbus_with_slack_; ++ybus_id){ @@ -135,13 +170,13 @@ bool BaseDCSolver::compute_pf(const Eigen::SparseMatrix #ifdef __COUT_TIMES std::cout << "\t dc postproc: " << 1000. * timer_postproc.duration() << "ms" << std::endl; #endif // __COUT_TIMES - + _solver_control.tell_none_changed(); timer_total_nr_ += timer.duration(); return true; } template -void BaseDCSolver::fill_mat_bus_id(int nb_bus_solver){ +void BaseDCAlgo::fill_mat_bus_id(int nb_bus_solver){ mat_bus_id_ = 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; @@ -154,14 +189,14 @@ void BaseDCSolver::fill_mat_bus_id(int nb_bus_solver){ } template -void BaseDCSolver::fill_dcYbus_noslack(int nb_bus_solver, const Eigen::SparseMatrix & ref_mat){ +void BaseDCAlgo::fill_dcYbus_noslack(int nb_bus_solver, const Eigen::SparseMatrix & ref_mat){ // TODO see if "prune" might work here https://eigen.tuxfamily.org/dox/classEigen_1_1SparseMatrix.html#title29 remove_slack_buses(nb_bus_solver, ref_mat, dcYbus_noslack_); } template template // ref_mat_type should be `real_type` or `cplx_type` -void BaseDCSolver::remove_slack_buses(int nb_bus_solver, const Eigen::SparseMatrix & ref_mat, Eigen::SparseMatrix & res_mat){ +void BaseDCAlgo::remove_slack_buses(int nb_bus_solver, const Eigen::SparseMatrix & ref_mat, Eigen::SparseMatrix & res_mat){ res_mat = Eigen::SparseMatrix(sizeYbus_without_slack_, sizeYbus_without_slack_); // TODO dist slack: -1 or -mat_bus_id_.size() here ???? std::vector > tripletList; tripletList.reserve(ref_mat.nonZeros()); @@ -182,8 +217,8 @@ void BaseDCSolver::remove_slack_buses(int nb_bus_solver, const Eig } template -void BaseDCSolver::reset(){ - BaseSolver::reset(); +void BaseDCAlgo::reset(){ + BaseAlgo::reset(); _linear_solver.reset(); need_factorize_ = true; sizeYbus_with_slack_ = 0; @@ -196,7 +231,7 @@ void BaseDCSolver::reset(){ } template -RealMat BaseDCSolver::get_ptdf(const Eigen::SparseMatrix & dcYbus){ +RealMat BaseDCAlgo::get_ptdf(const Eigen::SparseMatrix & dcYbus){ Eigen::SparseMatrix Bf_T_with_slack; RealMat PTDF; RealVect rhs = RealVect::Zero(sizeYbus_without_slack_); // TODO dist slack: -1 or -mat_bus_id_.size() here ???? @@ -206,7 +241,7 @@ RealMat BaseDCSolver::get_ptdf(const Eigen::SparseMatrix::get_ptdf(const Eigen::SparseMatrix -Eigen::SparseMatrix BaseDCSolver::get_lodf(){ +Eigen::SparseMatrix BaseDCAlgo::get_lodf(){ // TODO return dcYbus_noslack_; } template -Eigen::SparseMatrix BaseDCSolver::get_bsdf(){ +Eigen::SparseMatrix BaseDCAlgo::get_bsdf(){ // TODO return dcYbus_noslack_; diff --git a/src/BaseFDPFSolver.h b/src/powerflow_algorithm/BaseFDPFAlgo.h similarity index 92% rename from src/BaseFDPFSolver.h rename to src/powerflow_algorithm/BaseFDPFAlgo.h index f670eea..22ca7f5 100644 --- a/src/BaseFDPFSolver.h +++ b/src/powerflow_algorithm/BaseFDPFAlgo.h @@ -6,19 +6,19 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef BASEFDPFSOLVER_H -#define BASEFDPFSOLVER_H +#ifndef BASEFDPFALGO_H +#define BASEFDPFALGO_H -#include "BaseSolver.h" +#include "BaseAlgo.h" /** Base class for Fast Decoupled Powerflow based solver **/ template -class BaseFDPFSolver : public BaseSolver +class BaseFDPFAlgo: public BaseAlgo { public: - BaseFDPFSolver():BaseSolver(true), need_factorize_(true) {} + BaseFDPFAlgo():BaseAlgo(true), need_factorize_(true) {} virtual bool compute_pf(const Eigen::SparseMatrix & Ybus, @@ -47,10 +47,12 @@ class BaseFDPFSolver : public BaseSolver virtual void reset() { - BaseSolver::reset(); + BaseAlgo::reset(); // solution of the problem Bp_ = Eigen::SparseMatrix (); // the B prime matrix (size n_pvpq) Bpp_ = Eigen::SparseMatrix(); // the B double prime matrix (size n_pq) + grid_Bp_ = Eigen::SparseMatrix (); // the B prime matrix (size n_pvpq) + grid_Bpp_ = Eigen::SparseMatrix(); // the B double prime matrix (size n_pq) p_ = RealVect(); q_ = RealVect(); need_factorize_ = true; @@ -67,7 +69,7 @@ class BaseFDPFSolver : public BaseSolver protected: virtual void reset_timer(){ - BaseSolver::reset_timer(); + BaseAlgo::reset_timer(); } CplxVect evaluate_mismatch(const Eigen::SparseMatrix & Ybus, @@ -200,6 +202,8 @@ class BaseFDPFSolver : public BaseSolver LinearSolver _linear_solver_Bpp; // solution of the problem + Eigen::SparseMatrix grid_Bp_; + Eigen::SparseMatrix grid_Bpp_; Eigen::SparseMatrix Bp_; // the B prime matrix (size n_pvpq) Eigen::SparseMatrix Bpp_; // the B double prime matrix (size n_pq) RealVect p_; // (size n_pvpq) @@ -213,10 +217,10 @@ class BaseFDPFSolver : public BaseSolver private: // no copy allowed - BaseFDPFSolver( const BaseFDPFSolver & ) =delete ; - BaseFDPFSolver & operator=( const BaseFDPFSolver & ) =delete ; + BaseFDPFAlgo( const BaseFDPFAlgo & ) =delete ; + BaseFDPFAlgo & operator=( const BaseFDPFAlgo & ) =delete ; }; -#include "BaseFDPFSolver.tpp" +#include "BaseFDPFAlgo.tpp" -#endif // BASEFDPFSOLVER_H +#endif // BASEFDPFALGO_H diff --git a/src/BaseFDPFSolver.tpp b/src/powerflow_algorithm/BaseFDPFAlgo.tpp similarity index 66% rename from src/BaseFDPFSolver.tpp rename to src/powerflow_algorithm/BaseFDPFAlgo.tpp index 080d191..acfe9fb 100644 --- a/src/BaseFDPFSolver.tpp +++ b/src/powerflow_algorithm/BaseFDPFAlgo.tpp @@ -9,16 +9,16 @@ // inspired from pypower https://github.com/rwl/PYPOWER/blob/master/pypower/fdpf.py template -bool BaseFDPFSolver::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 - ) +bool BaseFDPFAlgo::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 + ) { /** This method uses the newton raphson algorithm to compute voltage angles and magnitudes at each bus @@ -32,21 +32,31 @@ bool BaseFDPFSolver::compute_pf(const Eigen::SparseMatrix::compute_pf(const Eigen::SparseMatrix grid_Bp; - Eigen::SparseMatrix grid_Bpp; - fillBp_Bpp(grid_Bp, grid_Bpp); - - // fill the solver matrices Bp and Bpp : - // Bp_ = Bp[array([pvpq]).T, pvpq].tocsc() # splu requires a CSC matrix + if(need_factorize_ || + _solver_control.need_reset_solver() || + _solver_control.has_dimension_changed() || + _solver_control.ybus_change_sparsity_pattern() || + _solver_control.has_ybus_some_coeffs_zero() || + _solver_control.need_recompute_ybus() + ){ + // need to extract Bp and Bpp for the whole grid + grid_Bp_ = Eigen::SparseMatrix (); + grid_Bpp_ = Eigen::SparseMatrix(); + fillBp_Bpp(grid_Bp_, grid_Bpp_); + } + + // init "my" matrices + // fill the solver matrices Bp_ and Bpp_ + // Bp_ = Bp[array([pvpq]).T, pvpq].tocsc() // Bpp_ = Bpp[array([pq]).T, pq].tocsc() - const auto n_pvpq = pvpq.size(); - std::vector pvpq_inv(V.size(), -1); - for(int inv_id=0; inv_id < n_pvpq; ++inv_id) pvpq_inv[pvpq(inv_id)] = inv_id; - std::vector pq_inv(V.size(), -1); - for(int inv_id=0; inv_id < n_pq; ++inv_id) pq_inv[pq(inv_id)] = inv_id; - fill_sparse_matrices(grid_Bp, grid_Bpp, pvpq_inv, pq_inv, n_pvpq, n_pq); + if(need_factorize_ || + _solver_control.need_reset_solver() || + _solver_control.has_dimension_changed() || + _solver_control.ybus_change_sparsity_pattern() || + _solver_control.has_ybus_some_coeffs_zero() || + _solver_control.need_recompute_ybus() || + _solver_control.has_slack_participate_changed() || + _solver_control.has_pv_changed() || + _solver_control.has_pq_changed() + ){ + std::vector pvpq_inv(V.size(), -1); + for(int inv_id=0; inv_id < n_pvpq; ++inv_id) pvpq_inv[pvpq(inv_id)] = inv_id; + std::vector pq_inv(V.size(), -1); + for(int inv_id=0; inv_id < n_pq; ++inv_id) pq_inv[pq(inv_id)] = inv_id; + fill_sparse_matrices(grid_Bp_, grid_Bpp_, pvpq_inv, pq_inv, n_pvpq, n_pq); + } V_ = V; // V = V0 Vm_ = V_.array().abs(); // Vm = abs(V) @@ -149,12 +179,12 @@ bool BaseFDPFSolver::compute_pf(const Eigen::SparseMatrix -void BaseFDPFSolver::fill_sparse_matrices(const Eigen::SparseMatrix & grid_Bp, - const Eigen::SparseMatrix & grid_Bpp, - const std::vector & pvpq_inv, - const std::vector & pq_inv, - Eigen::Index n_pvpq, - Eigen::Index n_pq) +void BaseFDPFAlgo::fill_sparse_matrices(const Eigen::SparseMatrix & grid_Bp, + const Eigen::SparseMatrix & grid_Bpp, + const std::vector & pvpq_inv, + const std::vector & pq_inv, + Eigen::Index n_pvpq, + Eigen::Index n_pq) { /** Init Bp_ and Bpp_ such that: @@ -166,7 +196,7 @@ void BaseFDPFSolver::fill_sparse_matrices(const Eigen::Spar } template -void BaseFDPFSolver::aux_fill_sparse_matrices(const Eigen::SparseMatrix & grid_Bp_Bpp, +void BaseFDPFAlgo::aux_fill_sparse_matrices(const Eigen::SparseMatrix & grid_Bp_Bpp, const std::vector & ind_inv, Eigen::Index mat_dim, Eigen::SparseMatrix & res) diff --git a/src/BaseNRSolver.h b/src/powerflow_algorithm/BaseNRAlgo.h similarity index 89% rename from src/BaseNRSolver.h rename to src/powerflow_algorithm/BaseNRAlgo.h index 8b7c5fa..d593092 100644 --- a/src/BaseNRSolver.h +++ b/src/powerflow_algorithm/BaseNRAlgo.h @@ -6,19 +6,19 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef BASENRSOLVER_H -#define BASENRSOLVER_H +#ifndef BASE_NR_ALGO_H +#define BASE_NR_ALGO_H -#include "BaseSolver.h" +#include "BaseAlgo.h" /** Base class for Newton Raphson based solver **/ template -class BaseNRSolver : public BaseSolver +class BaseNRAlgo : public BaseAlgo { public: - BaseNRSolver():BaseSolver(true), need_factorize_(true), timer_initialize_(0.), timer_dSbus_(0.), timer_fillJ_(0.) {} + BaseNRAlgo():BaseAlgo(true), need_factorize_(true), timer_initialize_(0.), timer_dSbus_(0.), timer_fillJ_(0.) {} virtual Eigen::Ref > get_J() const { @@ -56,7 +56,7 @@ class BaseNRSolver : public BaseSolver protected: virtual void reset_timer(){ - BaseSolver::reset_timer(); + BaseAlgo::reset_timer(); timer_dSbus_ = 0.; timer_fillJ_ = 0.; timer_initialize_ = 0.; @@ -136,8 +136,21 @@ class BaseNRSolver : public BaseSolver void fill_value_map(Eigen::Index slack_bus_id, const Eigen::VectorXi & pq, - const Eigen::VectorXi & pvpq); - + const Eigen::VectorXi & pvpq, + bool reset_J); + + void reset_if_needed(){ + if(_solver_control.need_reset_solver() || + _solver_control.has_dimension_changed() || + _solver_control.ybus_change_sparsity_pattern() || + _solver_control.has_ybus_some_coeffs_zero() || + _solver_control.has_slack_participate_changed() || + _solver_control.has_pv_changed() || + _solver_control.has_pq_changed() + ){ + reset(); + } + } protected: // used linear solver LinearSolver _linear_solver; @@ -151,6 +164,8 @@ class BaseNRSolver : public BaseSolver // to store the mapping from the element of J_ in dS_dVm_ and dS_dVa_ // it does not own any memory at all ! std::vector value_map_; + // std::vector col_map_; + // std::vector row_map_; // timers double timer_initialize_; @@ -184,8 +199,8 @@ class BaseNRSolver : public BaseSolver private: // no copy allowed - BaseNRSolver( const BaseNRSolver & ) =delete ; - BaseNRSolver & operator=( const BaseNRSolver & ) =delete ; + BaseNRAlgo( const BaseNRAlgo & ) =delete ; + BaseNRAlgo & operator=( const BaseNRAlgo & ) =delete ; /** helper function to print the max_col left most columns of the J matrix **/ void print_J(int min_col=-1, int max_col=-1) const{ @@ -221,6 +236,6 @@ class BaseNRSolver : public BaseSolver } }; -#include "BaseNRSolver.tpp" +#include "BaseNRAlgo.tpp" -#endif // BASENRSOLVER_H +#endif // BASE_NR_ALGO_H diff --git a/src/BaseNRSolver.tpp b/src/powerflow_algorithm/BaseNRAlgo.tpp similarity index 83% rename from src/BaseNRSolver.tpp rename to src/powerflow_algorithm/BaseNRAlgo.tpp index 9c25925..33ae20e 100644 --- a/src/BaseNRSolver.tpp +++ b/src/powerflow_algorithm/BaseNRAlgo.tpp @@ -6,22 +6,22 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -// #include "BaseNRSolver.h" // now a template class, so this file will be included instead ! +// #include "BaseNRAlgo.h" // now a template class, so this file will be included instead ! // TODO get rid of the pvpq, pv, pq etc and put the jacobian "in the right order" // to ease and make way faster the filling of the sparse matrix J template -bool BaseNRSolver::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 - ) +bool BaseNRAlgo::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 + ) { /** This method uses the newton raphson algorithm to compute voltage angles and magnitudes at each bus @@ -35,22 +35,25 @@ bool BaseNRSolver::compute_pf(const Eigen::SparseMatrix if(Sbus.size() != Ybus.rows() || Sbus.size() != Ybus.cols() ){ // TODO DEBUG MODE std::ostringstream exc_; - exc_ << "BaseNRSolver::compute_pf: Size of the Sbus should be the same as the size of Ybus. Currently: "; - exc_ << "Sbus (" << Sbus.size() << ") and Ybus (" << Ybus.rows() << ", " << Ybus.rows() << ")."; + exc_ << "BaseNRAlgo::compute_pf: Size of the Sbus should be the same as the size of Ybus. Currently: "; + exc_ << "Sbus (" << Sbus.size() << ") and Ybus (" << Ybus.rows() << ", " << Ybus.cols() << ")."; throw std::runtime_error(exc_.str()); } if(V.size() != Ybus.rows() || V.size() != Ybus.cols() ){ // TODO DEBUG MODE std::ostringstream exc_; - exc_ << "BaseNRSolver::compute_pf: Size of V (init voltages) should be the same as the size of Ybus. Currently: "; - exc_ << "V (" << V.size() << ") and Ybus (" << Ybus.rows()<< ", " << Ybus.rows() << ")."; + exc_ << "BaseNRAlgo::compute_pf: Size of V (init voltages) should be the same as the size of Ybus. Currently: "; + exc_ << "V (" << V.size() << ") and Ybus (" << Ybus.rows()<< ", " << Ybus.cols() << ")."; throw std::runtime_error(exc_.str()); } + if(!is_linear_solver_valid()) { + // err_ = ErrorType::NotInitError; + return false; + } reset_timer(); - auto timer = CustTimer(); - if(!is_linear_solver_valid()) return false; - + reset_if_needed(); err_ = ErrorType::NoError; // reset the error if previous error happened + auto timer = CustTimer(); Eigen::VectorXi my_pv = retrieve_pv_with_slack(slack_ids, pv); // retrieve_pv_with_slack (not all), add_slack_to_pv (all) real_type slack_absorbed = std::real(Sbus.sum()); // initial guess for slack_absorbed @@ -85,6 +88,27 @@ bool BaseNRSolver::compute_pf(const Eigen::SparseMatrix bool has_just_been_initialized = false; // to avoid a call to klu_refactor follow a call to klu_factor in the same loop // std::cout << "iter " << nr_iter_ << " dx(0): " << -F(0) << " dx(1): " << -F(1) << std::endl; // std::cout << "slack_absorbed " << slack_absorbed << std::endl; + if(need_factorize_ || + _solver_control.need_reset_solver() || + _solver_control.has_dimension_changed() || + _solver_control.has_slack_participate_changed() || // the full "ybus without slack" has changed, everything needs to be recomputed_solver_control.ybus_change_sparsity_pattern() + _solver_control.ybus_change_sparsity_pattern() || + _solver_control.has_ybus_some_coeffs_zero() || + _solver_control.need_recompute_ybus() || + _solver_control.has_slack_participate_changed() || + _solver_control.has_pv_changed() || + _solver_control.has_pq_changed() + ) + { + value_map_.clear(); // TODO smarter solver: only needed if ybus has changed + // BaseNRAlgo::col_map_.clear(); // TODO smarter solver: only needed if ybus has changed + // BaseNRAlgo::row_map_.clear(); // TODO smarter solver: only needed if ybus has changed + dS_dVm_.resize(0,0); // TODO smarter solver: only needed if ybus has changed + dS_dVa_.resize(0,0); // TODO smarter solver: only needed if ybus has changed + // BaseNRAlgo::dS_dVm_.setZero(); // TODO smarter solver: only needed if ybus has changed + // BaseNRAlgo::dS_dVa_.setZero(); // TODO smarter solver: only needed if ybus has changed + + } while ((!converged) & (nr_iter_ < max_iter)){ nr_iter_++; fill_jacobian_matrix(Ybus, V_, slack_bus_id, slack_weights, pq, pvpq, pq_inv, pvpq_inv); @@ -146,12 +170,13 @@ bool BaseNRSolver::compute_pf(const Eigen::SparseMatrix << "\n\t timer_total_nr_: " << timer_total_nr_ << "\n\n"; #endif // __COUT_TIMES + _solver_control.tell_none_changed(); return res; } template -void BaseNRSolver::reset(){ - BaseSolver::reset(); +void BaseNRAlgo::reset(){ + BaseAlgo::reset(); // reset specific attributes J_ = Eigen::SparseMatrix(); // the jacobian matrix dS_dVm_ = Eigen::SparseMatrix(); @@ -164,8 +189,9 @@ void BaseNRSolver::reset(){ } template -void BaseNRSolver::_dSbus_dV(const Eigen::Ref > & Ybus, - const Eigen::Ref & V){ +void BaseNRAlgo::_dSbus_dV(const Eigen::Ref > & Ybus, + const Eigen::Ref & V){ + // std::cout << "Ybus.nonZeros(): " << Ybus.nonZeros() << std::endl; auto timer = CustTimer(); const auto size_dS = V.size(); const CplxVect Vnorm = V.array() / V.array().abs(); @@ -183,6 +209,12 @@ void BaseNRSolver::_dSbus_dV(const Eigen::Ref >::InnerIterator it(Ybus, col_id); it; ++it) @@ -218,7 +250,7 @@ void BaseNRSolver::_dSbus_dV(const Eigen::Ref -void BaseNRSolver::_get_values_J(int & nb_obj_this_col, +void BaseNRAlgo::_get_values_J(int & nb_obj_this_col, std::vector & inner_index, std::vector & values, const Eigen::Ref > & mat, // ex. dS_dVa_r @@ -245,7 +277,7 @@ void BaseNRSolver::_get_values_J(int & nb_obj_this_col, } template -void BaseNRSolver::_get_values_J(int & nb_obj_this_col, +void BaseNRAlgo::_get_values_J(int & nb_obj_this_col, std::vector & inner_index, std::vector & values, const Eigen::Ref > & mat, // ex. dS_dVa_r @@ -282,15 +314,15 @@ void BaseNRSolver::_get_values_J(int & nb_obj_this_col, } template -void BaseNRSolver::fill_jacobian_matrix(const Eigen::SparseMatrix & Ybus, - const CplxVect & V, - Eigen::Index slack_bus_id, - const RealVect & slack_weights, - const Eigen::VectorXi & pq, - const Eigen::VectorXi & pvpq, - const std::vector & pq_inv, - const std::vector & pvpq_inv - ) +void BaseNRAlgo::fill_jacobian_matrix(const Eigen::SparseMatrix & Ybus, + const CplxVect & V, + Eigen::Index slack_bus_id, + const RealVect & slack_weights, + const Eigen::VectorXi & pq, + const Eigen::VectorXi & pvpq, + const std::vector & pq_inv, + const std::vector & pvpq_inv + ) { /** Remember, J has the shape: @@ -331,6 +363,7 @@ void BaseNRSolver::fill_jacobian_matrix(const Eigen::SparseMatrix< #endif // __COUT_TIMES // first time i initialized the matrix, so i need to compute its sparsity pattern fill_jacobian_matrix_unkown_sparsity_pattern(Ybus, V, slack_bus_id, slack_weights, pq, pvpq, pq_inv, pvpq_inv); + fill_value_map(slack_bus_id, pq, pvpq, false); #ifdef __COUT_TIMES std::cout << "\t\t fill_jacobian_matrix_unkown_sparsity_pattern : " << timer2.duration() << std::endl; #endif // __COUT_TIMES @@ -339,7 +372,8 @@ void BaseNRSolver::fill_jacobian_matrix(const Eigen::SparseMatrix< // properly and faster (approx 3 times faster than the previous one) #ifdef __COUT_TIMES auto timer3 = CustTimer(); - #endif // __COUT_TIMES + #endif // + if (BaseNRAlgo::value_map_.size() == 0) fill_value_map(slack_bus_id, pq, pvpq, true); fill_jacobian_matrix_kown_sparsity_pattern(slack_bus_id, pq, pvpq ); @@ -351,7 +385,7 @@ void BaseNRSolver::fill_jacobian_matrix(const Eigen::SparseMatrix< } template -void BaseNRSolver::fill_jacobian_matrix_unkown_sparsity_pattern( +void BaseNRAlgo::fill_jacobian_matrix_unkown_sparsity_pattern( const Eigen::SparseMatrix & Ybus, const CplxVect & V, Eigen::Index slack_bus_id, @@ -404,7 +438,7 @@ void BaseNRSolver::fill_jacobian_matrix_unkown_sparsity_pattern( // optim : if the matrix was already computed, i don't initialize it, i instead reuse as much as i can // i can do that because the matrix will ALWAYS have the same non zero coefficients. // in this if, i allocate it in a "large enough" place to avoid copy when first filling it - if(J_.cols() != size_j) J_ = Eigen::SparseMatrix(size_j,size_j); + if(J_.cols() != size_j) J_ = Eigen::SparseMatrix(size_j, size_j); std::vector > coeffs; // HERE FOR PERF OPTIM (3) coeffs.reserve(2*(dS_dVa_.nonZeros()+dS_dVm_.nonZeros()) + slack_weights.size()); // HERE FOR PERF OPTIM (3) @@ -512,7 +546,6 @@ void BaseNRSolver::fill_jacobian_matrix_unkown_sparsity_pattern( J_.setFromTriplets(coeffs.begin(), coeffs.end()); // HERE FOR PERF OPTIM (3) // std::cout << "end fill jacobian unknown " << std::endl; J_.makeCompressed(); - fill_value_map(slack_bus_id, pq, pvpq); // std::cout << "end fill_value_map" << std::endl; } @@ -522,14 +555,18 @@ dS_dVa_ and dS_dVm_ to be used to fill J_ it requires that J_ is initialized, in compressed mode. **/ template -void BaseNRSolver::fill_value_map( +void BaseNRAlgo::fill_value_map( Eigen::Index slack_bus_id, const Eigen::VectorXi & pq, - const Eigen::VectorXi & pvpq + const Eigen::VectorXi & pvpq, + bool reset_J ) { const int n_pvpq = static_cast(pvpq.size()); - value_map_ = std::vector (J_.nonZeros()); + value_map_ = std::vector (); + value_map_.reserve(BaseNRAlgo::J_.nonZeros()); + // col_map_ = std::vector (J_.nonZeros()); + // row_map_ = std::vector (J_.nonZeros()); const auto n_row = J_.cols(); unsigned int pos_el = 0; @@ -538,16 +575,18 @@ void BaseNRSolver::fill_value_map( { auto row_id = it.row(); const auto col_id = it.col() - 1; // it's equal to "col_" + if(reset_J) it.valueRef() = 0.; // "forget" previous J value in this setting + if(row_id==0){ // this is the row of the slack bus const Eigen::Index row_id_dS_dVx_r = slack_bus_id; // same for both matrices if(col_id < n_pvpq){ const int col_id_dS_dVa_r = pvpq[col_id]; - value_map_[pos_el] = &dS_dVa_.coeffRef(row_id_dS_dVx_r, col_id_dS_dVa_r); + value_map_.push_back(&dS_dVa_.coeffRef(row_id_dS_dVx_r, col_id_dS_dVa_r)); } else{ const int col_id_dS_dVm_r = pq[col_id - n_pvpq]; - value_map_[pos_el] = &dS_dVm_.coeffRef(row_id_dS_dVx_r, col_id_dS_dVm_r); + value_map_.push_back(&dS_dVm_.coeffRef(row_id_dS_dVx_r, col_id_dS_dVm_r)); } }else{ row_id -= 1; // "do not consider" the row for slack bus (handled above) @@ -557,7 +596,7 @@ void BaseNRSolver::fill_value_map( const int row_id_dS_dVa_r = pvpq[row_id]; const int col_id_dS_dVa_r = pvpq[col_id]; // this_el = dS_dVa_r.coeff(row_id_dS_dVa_r, col_id_dS_dVa_r); - value_map_[pos_el] = &dS_dVa_.coeffRef(row_id_dS_dVa_r, col_id_dS_dVa_r); + value_map_.push_back(&dS_dVa_.coeffRef(row_id_dS_dVa_r, col_id_dS_dVa_r)); // I don't need to perform these checks: if they failed, the element would not be in J_ in the first place // const int is_row_non_null = pq_inv[row_id_dS_dVa_r]; @@ -571,29 +610,31 @@ void BaseNRSolver::fill_value_map( const int row_id_dS_dVa_i = pq[row_id - n_pvpq]; const int col_id_dS_dVa_i = pvpq[col_id]; // this_el = dS_dVa_i.coeff(row_id_dS_dVa_i, col_id_dS_dVa_i); - value_map_[pos_el] = &dS_dVa_.coeffRef(row_id_dS_dVa_i, col_id_dS_dVa_i); + value_map_.push_back(&dS_dVa_.coeffRef(row_id_dS_dVa_i, col_id_dS_dVa_i)); }else if((col_id >= n_pvpq) && (row_id < n_pvpq)){ // this is the J12 part (dS_dVm_r) const int row_id_dS_dVm_r = pvpq[row_id]; const int col_id_dS_dVm_r = pq[col_id - n_pvpq]; // this_el = dS_dVm_r.coeff(row_id_dS_dVm_r, col_id_dS_dVm_r); - value_map_[pos_el] = &dS_dVm_.coeffRef(row_id_dS_dVm_r, col_id_dS_dVm_r); + value_map_.push_back(&dS_dVm_.coeffRef(row_id_dS_dVm_r, col_id_dS_dVm_r)); }else if((col_id >= n_pvpq) && (row_id >= n_pvpq)){ // this is the J22 part (dS_dVm_i) const int row_id_dS_dVm_i = pq[row_id - n_pvpq]; const int col_id_dS_dVm_i = pq[col_id - n_pvpq]; // this_el = dS_dVm_i.coeff(row_id_dS_dVm_i, col_id_dS_dVm_i); - value_map_[pos_el] = &dS_dVm_.coeffRef(row_id_dS_dVm_i, col_id_dS_dVm_i); + value_map_.push_back(&dS_dVm_.coeffRef(row_id_dS_dVm_i, col_id_dS_dVm_i)); } } // go to the next element ++pos_el; } } + dS_dVa_.makeCompressed(); + dS_dVm_.makeCompressed(); } template -void BaseNRSolver::fill_jacobian_matrix_kown_sparsity_pattern( +void BaseNRAlgo::fill_jacobian_matrix_kown_sparsity_pattern( Eigen::Index slack_bus_id, const Eigen::VectorXi & pq, const Eigen::VectorXi & pvpq diff --git a/src/BaseNRSolverSingleSlack.h b/src/powerflow_algorithm/BaseNRSingleSlackAlgo.h similarity index 83% rename from src/BaseNRSolverSingleSlack.h rename to src/powerflow_algorithm/BaseNRSingleSlackAlgo.h index 8cd8522..7ed9ccf 100644 --- a/src/BaseNRSolverSingleSlack.h +++ b/src/powerflow_algorithm/BaseNRSingleSlackAlgo.h @@ -6,21 +6,21 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef BASENRSOLVERSINGLESLACK_H -#define BASENRSOLVERSINGLESLACK_H +#ifndef BASE_NR_SINGLESLACK_ALGO_H +#define BASE_NR_SINGLESLACK_ALGO_H -#include "BaseNRSolver.h" +#include "BaseNRAlgo.h" /** Base class for Newton Raphson based solver (only interesting for single slack) **/ template -class BaseNRSolverSingleSlack : public BaseNRSolver +class BaseNRSingleSlackAlgo : public BaseNRAlgo { public: - BaseNRSolverSingleSlack():BaseNRSolver(){} + BaseNRSingleSlackAlgo():BaseNRAlgo(){} - ~BaseNRSolverSingleSlack(){} + ~BaseNRSingleSlackAlgo(){} virtual bool compute_pf(const Eigen::SparseMatrix & Ybus, @@ -58,10 +58,11 @@ class BaseNRSolverSingleSlack : public BaseNRSolver ); void fill_value_map(const Eigen::VectorXi & pq, - const Eigen::VectorXi & pvpq); + const Eigen::VectorXi & pvpq, + bool reset_J); }; -#include "BaseNRSolverSingleSlack.tpp" +#include "BaseNRSingleSlackAlgo.tpp" -#endif // BASENRSOLVERSINGLESLACK_H +#endif // BASE_NR_SINGLESLACK_ALGO_H diff --git a/src/BaseNRSolverSingleSlack.tpp b/src/powerflow_algorithm/BaseNRSingleSlackAlgo.tpp similarity index 55% rename from src/BaseNRSolverSingleSlack.tpp rename to src/powerflow_algorithm/BaseNRSingleSlackAlgo.tpp index df0a6e8..612f963 100644 --- a/src/BaseNRSolverSingleSlack.tpp +++ b/src/powerflow_algorithm/BaseNRSingleSlackAlgo.tpp @@ -7,19 +7,19 @@ // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. // #include "BaseNRSolverSingleSlack.h" -// #include "BaseNRSolver.h" +// #include "BaseNRAlgo.h" template -bool BaseNRSolverSingleSlack::compute_pf(const Eigen::SparseMatrix & Ybus, - CplxVect & V, - const CplxVect & Sbus, - const Eigen::VectorXi & slack_ids, - const RealVect & slack_weights, // unused here - const Eigen::VectorXi & pv, - const Eigen::VectorXi & pq, - int max_iter, - real_type tol - ) +bool BaseNRSingleSlackAlgo::compute_pf(const Eigen::SparseMatrix & Ybus, + CplxVect & V, + const CplxVect & Sbus, + const Eigen::VectorXi & slack_ids, + const RealVect & slack_weights, // unused here + const Eigen::VectorXi & pv, + const Eigen::VectorXi & pq, + int max_iter, + real_type tol + ) { /** This method uses the newton raphson algorithm to compute voltage angles and magnitudes at each bus @@ -30,26 +30,28 @@ bool BaseNRSolverSingleSlack::compute_pf(const Eigen::SparseMatrix // TODO Ybus (nrow or ncol), pv and pq have value that are between 0 and nrow etc. if(Sbus.size() != Ybus.rows() || Sbus.size() != Ybus.cols() ){ std::ostringstream exc_; - exc_ << "BaseNRSolverSingleSlack::compute_pf: Size of the Sbus should be the same as the size of Ybus. Currently: "; - exc_ << "Sbus (" << Sbus.size() << ") and Ybus (" << Ybus.rows() << ", " << Ybus.rows() << ")."; + exc_ << "BaseNRSingleSlackAlgo::compute_pf: Size of the Sbus should be the same as the size of Ybus. Currently: "; + exc_ << "Sbus (" << Sbus.size() << ") and Ybus (" << Ybus.rows() << ", " << Ybus.cols() << ")."; throw std::runtime_error(exc_.str()); } if(V.size() != Ybus.rows() || V.size() != Ybus.cols() ){ std::ostringstream exc_; - exc_ << "BaseNRSolverSingleSlack::compute_pf: Size of V (init voltages) should be the same as the size of Ybus. Currently: "; - exc_ << "V (" << V.size() << ") and Ybus (" << Ybus.rows()<<", "<::reset_timer(); + if(!BaseNRAlgo::is_linear_solver_valid()){ + return false; + } + BaseNRAlgo::reset_timer(); + BaseNRAlgo::reset_if_needed(); + BaseNRAlgo::err_ = ErrorType::NoError; // reset the error if previous error happened - if(!BaseNRSolver::is_linear_solver_valid()) return false; - - BaseNRSolver::err_ = ErrorType::NoError; // reset the error if previous error happened auto timer = CustTimer(); // initialize once and for all the "inverse" of these vectors - // Eigen::VectorXi my_pv = BaseNRSolver::retrieve_pv_with_slack(slack_ids, pv); + // Eigen::VectorXi my_pv = BaseNRAlgo::retrieve_pv_with_slack(slack_ids, pv); Eigen::VectorXi my_pv = pv; - // Eigen::VectorXi my_pv = pv; // BaseNRSolver::retrieve_pv_with_slack(slack_ids, pv); + // Eigen::VectorXi my_pv = pv; // BaseNRAlgo::retrieve_pv_with_slack(slack_ids, pv); const int n_pv = static_cast(my_pv.size()); const int n_pq = static_cast(pq.size()); @@ -61,26 +63,48 @@ bool BaseNRSolverSingleSlack::compute_pf(const Eigen::SparseMatrix std::vector pq_inv(V.size(), -1); for(int inv_id=0; inv_id < n_pq; ++inv_id) pq_inv[pq(inv_id)] = inv_id; - BaseNRSolver::V_ = V; - BaseNRSolver::Vm_ = BaseNRSolver::V_.array().abs(); // update Vm and Va again in case - BaseNRSolver::Va_ = BaseNRSolver::V_.array().arg(); // we wrapped around with a negative Vm + BaseNRAlgo::V_ = V; + BaseNRAlgo::Vm_ = BaseNRAlgo::V_.array().abs(); // update Vm and Va again in case + BaseNRAlgo::Va_ = BaseNRAlgo::V_.array().arg(); // we wrapped around with a negative Vm // first check, if the problem is already solved, i stop there - RealVect F = BaseNRSolver::_evaluate_Fx(Ybus, V, Sbus, my_pv, pq); - bool converged = BaseNRSolver::_check_for_convergence(F, tol); - BaseNRSolver::nr_iter_ = 0; //current step + RealVect F = BaseNRAlgo::_evaluate_Fx(Ybus, V, Sbus, my_pv, pq); + bool converged = BaseNRAlgo::_check_for_convergence(F, tol); + BaseNRAlgo::nr_iter_ = 0; //current step bool res = true; // have i converged or not bool has_just_been_initialized = false; // to avoid a call to klu_refactor follow a call to klu_factor in the same loop - const cplx_type m_i = BaseNRSolver::my_i; // otherwise it does not compile - - while ((!converged) & (BaseNRSolver::nr_iter_ < max_iter)){ - BaseNRSolver::nr_iter_++; - fill_jacobian_matrix(Ybus, BaseNRSolver::V_, pq, pvpq, pq_inv, pvpq_inv); - if(BaseNRSolver::need_factorize_){ - BaseNRSolver::initialize(); - if(BaseNRSolver::err_ != ErrorType::NoError){ + const cplx_type m_i = BaseNRAlgo::my_i; // otherwise it does not compile + if(BaseNRAlgo::need_factorize_ || + BaseNRAlgo::_solver_control.need_reset_solver() || + BaseNRAlgo::_solver_control.has_dimension_changed() || + BaseNRAlgo::_solver_control.has_slack_participate_changed() || // the full "ybus without slack" has changed, everything needs to be recomputed_solver_control.ybus_change_sparsity_pattern() + BaseNRAlgo::_solver_control.ybus_change_sparsity_pattern() || + BaseNRAlgo::_solver_control.has_ybus_some_coeffs_zero() || + BaseNRAlgo::_solver_control.need_recompute_ybus() || + // BaseNRAlgo:: _solver_control.has_slack_participate_changed() || + BaseNRAlgo::_solver_control.has_pv_changed() || + BaseNRAlgo::_solver_control.has_pq_changed() + ) + { + BaseNRAlgo::value_map_.clear(); // TODO smarter solver: only needed if ybus has changed + // BaseNRAlgo::col_map_.clear(); // TODO smarter solver: only needed if ybus has changed + // BaseNRAlgo::row_map_.clear(); // TODO smarter solver: only needed if ybus has changed + BaseNRAlgo::dS_dVm_.resize(0,0); // TODO smarter solver: only needed if ybus has changed + BaseNRAlgo::dS_dVa_.resize(0,0); // TODO smarter solver: only needed if ybus has changed + // BaseNRAlgo::dS_dVm_.setZero(); // TODO smarter solver: only needed if ybus has changed + // BaseNRAlgo::dS_dVa_.setZero(); // TODO smarter solver: only needed if ybus has changed + + } + while ((!converged) & (BaseNRAlgo::nr_iter_ < max_iter)){ + BaseNRAlgo::nr_iter_++; + // std::cout << "\tnr_iter_ " << BaseNRAlgo::nr_iter_ << std::endl; + fill_jacobian_matrix(Ybus, BaseNRAlgo::V_, pq, pvpq, pq_inv, pvpq_inv); + if(BaseNRAlgo::need_factorize_){ + BaseNRAlgo::initialize(); + if(BaseNRAlgo::err_ != ErrorType::NoError){ // I got an error during the initialization of the linear system, i need to stop here + // std::cout << BaseNRAlgo::err_ << std::endl; res = false; break; } @@ -90,62 +114,68 @@ bool BaseNRSolverSingleSlack::compute_pf(const Eigen::SparseMatrix // std::cout << "no need to factorize" << std::endl; } - BaseNRSolver::solve(F, has_just_been_initialized); + BaseNRAlgo::solve(F, has_just_been_initialized); has_just_been_initialized = false; - if(BaseNRSolver::err_ != ErrorType::NoError){ + if(BaseNRAlgo::err_ != ErrorType::NoError){ // I got an error during the solving of the linear system, i need to stop here + // std::cout << BaseNRAlgo::err_ << std::endl; res = false; break; } // auto dx = -F; - BaseNRSolver::Vm_ = BaseNRSolver::V_.array().abs(); // update Vm and Va again in case - BaseNRSolver::Va_ = BaseNRSolver::V_.array().arg(); // we wrapped around with a negative Vm + BaseNRAlgo::Vm_ = BaseNRAlgo::V_.array().abs(); // update Vm and Va again in case + BaseNRAlgo::Va_ = BaseNRAlgo::V_.array().arg(); // we wrapped around with a negative Vm // update voltage (this should be done consistently with "klu_solver._evaluate_Fx") - if (n_pv > 0) BaseNRSolver::Va_(my_pv) -= F.segment(0, n_pv); + if (n_pv > 0) BaseNRAlgo::Va_(my_pv) -= F.segment(0, n_pv); if (n_pq > 0){ - BaseNRSolver::Va_(pq) -= F.segment(n_pv,n_pq); - BaseNRSolver::Vm_(pq) -= F.segment(n_pv+n_pq, n_pq); + BaseNRAlgo::Va_(pq) -= F.segment(n_pv,n_pq); + BaseNRAlgo::Vm_(pq) -= F.segment(n_pv+n_pq, n_pq); } // TODO change here for not having to cast all the time ... maybe - const RealVect & Vm = BaseNRSolver::Vm_; // I am forced to redefine the type for it to compile properly - const RealVect & Va = BaseNRSolver::Va_; - BaseNRSolver::V_ = Vm.array() * (Va.array().cos().cast() + m_i * Va.array().sin().cast() ); + const RealVect & Vm = BaseNRAlgo::Vm_; // I am forced to redefine the type for it to compile properly + const RealVect & Va = BaseNRAlgo::Va_; + BaseNRAlgo::V_ = Vm.array() * (Va.array().cos().cast() + m_i * Va.array().sin().cast() ); - F = BaseNRSolver::_evaluate_Fx(Ybus, BaseNRSolver::V_, Sbus, my_pv, pq); + F = BaseNRAlgo::_evaluate_Fx(Ybus, BaseNRAlgo::V_, Sbus, my_pv, pq); bool tmp = F.allFinite(); - if(!tmp) break; // divergence due to Nans - converged = BaseNRSolver::_check_for_convergence(F, tol); + if(!tmp){ + BaseNRAlgo::err_ = ErrorType::InifiniteValue; + // std::cout << BaseNRAlgo::err_ << std::endl; + break; // divergence due to Nans + } + converged = BaseNRAlgo::_check_for_convergence(F, tol); } if(!converged){ - if (BaseNRSolver::err_ == ErrorType::NoError) BaseNRSolver::err_ = ErrorType::TooManyIterations; + if (BaseNRAlgo::err_ == ErrorType::NoError) BaseNRAlgo::err_ = ErrorType::TooManyIterations; res = false; } - BaseNRSolver::timer_total_nr_ += timer.duration(); + BaseNRAlgo::timer_total_nr_ += timer.duration(); #ifdef __COUT_TIMES - std::cout << "Computation time: " << "\n\t timer_initialize_: " << BaseNRSolver::timer_initialize_ - << "\n\t timer_dSbus_ (called in _fillJ_): " << BaseNRSolver::timer_dSbus_ - << "\n\t timer_fillJ_: " << BaseNRSolver::timer_fillJ_ - << "\n\t timer_Fx_: " << BaseNRSolver::timer_Fx_ - << "\n\t timer_check_: " << BaseNRSolver::timer_check_ - << "\n\t timer_solve_: " << BaseNRSolver::timer_solve_ - << "\n\t timer_total_nr_: " << BaseNRSolver::timer_total_nr_ + std::cout << "Computation time: " << "\n\t timer_initialize_: " << BaseNRAlgo::timer_initialize_ + << "\n\t timer_dSbus_ (called in _fillJ_): " << BaseNRAlgo::timer_dSbus_ + << "\n\t timer_fillJ_: " << BaseNRAlgo::timer_fillJ_ + << "\n\t timer_Fx_: " << BaseNRAlgo::timer_Fx_ + << "\n\t timer_check_: " << BaseNRAlgo::timer_check_ + << "\n\t timer_solve_: " << BaseNRAlgo::timer_solve_ + << "\n\t timer_total_nr_: " << BaseNRAlgo::timer_total_nr_ << "\n\n"; #endif // __COUT_TIMES + BaseNRAlgo::_solver_control.tell_none_changed(); return res; } template -void BaseNRSolverSingleSlack::fill_jacobian_matrix(const Eigen::SparseMatrix & Ybus, - const CplxVect & V, - const Eigen::VectorXi & pq, - const Eigen::VectorXi & pvpq, - const std::vector & pq_inv, - const std::vector & pvpq_inv - ) +void BaseNRSingleSlackAlgo::fill_jacobian_matrix(const Eigen::SparseMatrix & Ybus, + const CplxVect & V, + const Eigen::VectorXi & pq, + const Eigen::VectorXi & pvpq, + const std::vector & pq_inv, + const std::vector & pvpq_inv + ) { /** J has the shape @@ -160,16 +190,15 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix(const Eigen::Sp **/ auto timer = CustTimer(); - BaseNRSolver::_dSbus_dV(Ybus, V); + BaseNRAlgo::_dSbus_dV(Ybus, V); const int n_pvpq = static_cast(pvpq.size()); const int n_pq = static_cast(pq.size()); const int size_j = n_pvpq + n_pq; - // TODO to gain a bit more time below, try to compute directly, in _dSbus_dV(Ybus, V); // TODO the `dS_dVa_[pvpq, pvpq]` // TODO so that it's easier to retrieve in the next few lines ! - if(BaseNRSolver::J_.cols() != size_j) + if(BaseNRAlgo::J_.cols() != size_j) // if(true) { #ifdef __COUT_TIMES @@ -177,6 +206,8 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix(const Eigen::Sp #endif // __COUT_TIMES // first time i initialized the matrix, so i need to compute its sparsity pattern fill_jacobian_matrix_unkown_sparsity_pattern(Ybus, V, pq, pvpq, pq_inv, pvpq_inv); + fill_value_map(pq, pvpq, false); + // std::cout << "\t\tfill_jacobian_matrix_unkown_sparsity_pattern" << std::endl; #ifdef __COUT_TIMES std::cout << "\t\t fill_jacobian_matrix_unkown_sparsity_pattern : " << timer2.duration() << std::endl; #endif // __COUT_TIMES @@ -186,16 +217,21 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix(const Eigen::Sp #ifdef __COUT_TIMES auto timer3 = CustTimer(); #endif // __COUT_TIMES + if (BaseNRAlgo::value_map_.size() == 0){ + // std::cout << "\t\tfill_value_map called" << std::endl; + fill_value_map(pq, pvpq, true); + } fill_jacobian_matrix_kown_sparsity_pattern(pq, pvpq); + // std::cout << "\t\tfill_jacobian_matrix_kown_sparsity_pattern" << std::endl; #ifdef __COUT_TIMES std::cout << "\t\t fill_jacobian_matrix_kown_sparsity_pattern : " << timer3.duration() << std::endl; #endif // __COUT_TIMES } - BaseNRSolver::timer_fillJ_ += timer.duration(); + BaseNRAlgo::timer_fillJ_ += timer.duration(); } template -void BaseNRSolverSingleSlack::fill_jacobian_matrix_unkown_sparsity_pattern( +void BaseNRSingleSlackAlgo::fill_jacobian_matrix_unkown_sparsity_pattern( const Eigen::SparseMatrix & Ybus, const CplxVect & V, const Eigen::VectorXi & pq, @@ -225,22 +261,22 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix_unkown_sparsity const int n_pq = static_cast(pq.size()); const int size_j = n_pvpq + n_pq; - const Eigen::SparseMatrix dS_dVa_r = BaseNRSolver::dS_dVa_.real(); - const Eigen::SparseMatrix dS_dVa_i = BaseNRSolver::dS_dVa_.imag(); - const Eigen::SparseMatrix dS_dVm_r = BaseNRSolver::dS_dVm_.real(); - const Eigen::SparseMatrix dS_dVm_i = BaseNRSolver::dS_dVm_.imag(); + const Eigen::SparseMatrix dS_dVa_r = BaseNRAlgo::dS_dVa_.real(); + const Eigen::SparseMatrix dS_dVa_i = BaseNRAlgo::dS_dVa_.imag(); + const Eigen::SparseMatrix dS_dVm_r = BaseNRAlgo::dS_dVm_.real(); + const Eigen::SparseMatrix dS_dVm_i = BaseNRAlgo::dS_dVm_.imag(); // Method (1) seems to be faster than the others // optim : if the matrix was already computed, i don't initialize it, i instead reuse as much as i can // i can do that because the matrix will ALWAYS have the same non zero coefficients. // in this if, i allocate it in a "large enough" place to avoid copy when first filling it - if(BaseNRSolver::J_.cols() != size_j) + if(BaseNRAlgo::J_.cols() != size_j) { need_insert = true; - BaseNRSolver::J_ = Eigen::SparseMatrix(size_j,size_j); + BaseNRAlgo::J_ = Eigen::SparseMatrix(size_j, size_j); // pre allocate a large enough matrix - BaseNRSolver::J_.reserve(2*(BaseNRSolver::dS_dVa_.nonZeros()+BaseNRSolver::dS_dVm_.nonZeros())); + BaseNRAlgo::J_.reserve(2*(BaseNRAlgo::dS_dVa_.nonZeros() + BaseNRAlgo::dS_dVm_.nonZeros())); // from an experiment, outerIndexPtr is initialized, with the number of columns // innerIndexPtr and valuePtr are not. } @@ -263,14 +299,14 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix_unkown_sparsity // fill with the first column with the column of dS_dVa[:,pvpq[col_id]] // and check the row order ! - BaseNRSolver::_get_values_J(nb_obj_this_col, inner_index, values, + BaseNRAlgo::_get_values_J(nb_obj_this_col, inner_index, values, dS_dVa_r, pvpq_inv, pvpq, col_id, 0, 0); // fill the rest of the rows with the first column of dS_dVa_imag[:,pq[col_id]] - BaseNRSolver::_get_values_J(nb_obj_this_col, inner_index, values, + BaseNRAlgo::_get_values_J(nb_obj_this_col, inner_index, values, dS_dVa_i, pq_inv, pvpq, col_id, @@ -280,8 +316,8 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix_unkown_sparsity // "efficient" insert of the element in the matrix for(int in_ind=0; in_ind < nb_obj_this_col; ++in_ind){ int row_id = inner_index[in_ind]; - if(need_insert) BaseNRSolver::J_.insert(row_id, col_id) = values[in_ind]; // HERE FOR PERF OPTIM (1) - else BaseNRSolver::J_.coeffRef(row_id, col_id) = values[in_ind]; // HERE FOR PERF OPTIM (1) + if(need_insert) BaseNRAlgo::J_.insert(row_id, col_id) = values[in_ind]; // HERE FOR PERF OPTIM (1) + else BaseNRAlgo::J_.coeffRef(row_id, col_id) = values[in_ind]; // HERE FOR PERF OPTIM (1) // J_.insert(row_id, col_id) = values[in_ind]; // HERE FOR PERF OPTIM (2) // coeffs.push_back(Eigen::Triplet(row_id, col_id, values[in_ind])); // HERE FOR PERF OPTIM (3) } @@ -297,7 +333,7 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix_unkown_sparsity // fill with the first column with the column of dS_dVa[:,pvpq[col_id]] // and check the row order ! - BaseNRSolver::_get_values_J(nb_obj_this_col, inner_index, values, + BaseNRAlgo::_get_values_J(nb_obj_this_col, inner_index, values, dS_dVm_r, pvpq_inv, pq, col_id, @@ -305,7 +341,7 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix_unkown_sparsity 0); // fill the rest of the rows with the first column of dS_dVa_imag[:,pq[col_id]] - BaseNRSolver::_get_values_J(nb_obj_this_col, inner_index, values, + BaseNRAlgo::_get_values_J(nb_obj_this_col, inner_index, values, dS_dVm_i, pq_inv, pq, col_id, @@ -315,15 +351,14 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix_unkown_sparsity // "efficient" insert of the element in the matrix for(int in_ind=0; in_ind < nb_obj_this_col; ++in_ind){ int row_id = inner_index[in_ind]; - if(need_insert) BaseNRSolver::J_.insert(row_id, col_id + n_pvpq) = values[in_ind]; // HERE FOR PERF OPTIM (1) - else BaseNRSolver::J_.coeffRef(row_id, col_id + n_pvpq) = values[in_ind]; // HERE FOR PERF OPTIM (1) + if(need_insert) BaseNRAlgo::J_.insert(row_id, col_id + n_pvpq) = values[in_ind]; // HERE FOR PERF OPTIM (1) + else BaseNRAlgo::J_.coeffRef(row_id, col_id + n_pvpq) = values[in_ind]; // HERE FOR PERF OPTIM (1) // J_.insert(row_id, col_id + n_pvpq) = values[in_ind]; // HERE FOR PERF OPTIM (2) // coeffs.push_back(Eigen::Triplet(row_id, col_id + n_pvpq, values[in_ind])); // HERE FOR PERF OPTIM (3) } } // J_.setFromTriplets(coeffs.begin(), coeffs.end()); // HERE FOR PERF OPTIM (3) - BaseNRSolver::J_.makeCompressed(); - fill_value_map(pq, pvpq); + BaseNRAlgo::J_.makeCompressed(); } /** @@ -332,28 +367,32 @@ dS_dVa_ and dS_dVm_ to be used to fill J_ it requires that J_ is initialized, in compressed mode. **/ template -void BaseNRSolverSingleSlack::fill_value_map( +void BaseNRSingleSlackAlgo::fill_value_map( const Eigen::VectorXi & pq, - const Eigen::VectorXi & pvpq + const Eigen::VectorXi & pvpq, + bool reset_J ) { const int n_pvpq = static_cast(pvpq.size()); - BaseNRSolver::value_map_ = std::vector (BaseNRSolver::J_.nonZeros()); + BaseNRAlgo::value_map_.clear(); + // std::cout << "BaseNRAlgo::J_.nonZeros(): " << BaseNRAlgo::J_.nonZeros() << std::endl; + BaseNRAlgo::value_map_.reserve(BaseNRAlgo::J_.nonZeros()); - const int n_row = static_cast(BaseNRSolver::J_.cols()); + const int n_col = static_cast(BaseNRAlgo::J_.cols()); unsigned int pos_el = 0; - for (int col_=0; col_ < n_row; ++col_){ - for (Eigen::SparseMatrix::InnerIterator it(BaseNRSolver::J_, col_); it; ++it) + for (int col_=0; col_ < n_col; ++col_){ + for (Eigen::SparseMatrix::InnerIterator it(BaseNRAlgo::J_, col_); it; ++it) { const int row_id = static_cast(it.row()); const int col_id = static_cast(it.col()); // it's equal to "col_" + if(reset_J) it.valueRef() = 0.; // "forget" previous J value in this setting // real_type & this_el = J_x_ptr[pos_el]; if((col_id < n_pvpq) && (row_id < n_pvpq)){ // this is the J11 part (dS_dVa_r) const int row_id_dS_dVa_r = pvpq[row_id]; const int col_id_dS_dVa_r = pvpq[col_id]; // this_el = dS_dVa_r.coeff(row_id_dS_dVa_r, col_id_dS_dVa_r); - BaseNRSolver::value_map_[pos_el] = &BaseNRSolver::dS_dVa_.coeffRef(row_id_dS_dVa_r, col_id_dS_dVa_r); + BaseNRAlgo::value_map_.push_back(&BaseNRAlgo::dS_dVa_.coeffRef(row_id_dS_dVa_r, col_id_dS_dVa_r)); // I don't need to perform these checks: if they failed, the element would not be in J_ in the first place // const int is_row_non_null = pq_inv[row_id_dS_dVa_r]; @@ -368,29 +407,31 @@ void BaseNRSolverSingleSlack::fill_value_map( const int row_id_dS_dVa_i = pq[row_id - n_pvpq]; const int col_id_dS_dVa_i = pvpq[col_id]; // this_el = dS_dVa_i.coeff(row_id_dS_dVa_i, col_id_dS_dVa_i); - BaseNRSolver::value_map_[pos_el] = &BaseNRSolver::dS_dVa_.coeffRef(row_id_dS_dVa_i, col_id_dS_dVa_i); + BaseNRAlgo::value_map_.push_back(&BaseNRAlgo::dS_dVa_.coeffRef(row_id_dS_dVa_i, col_id_dS_dVa_i)); }else if((col_id >= n_pvpq) && (row_id < n_pvpq)){ // this is the J12 part (dS_dVm_r) const int row_id_dS_dVm_r = pvpq[row_id]; const int col_id_dS_dVm_r = pq[col_id - n_pvpq]; // this_el = dS_dVm_r.coeff(row_id_dS_dVm_r, col_id_dS_dVm_r); - BaseNRSolver::value_map_[pos_el] = &BaseNRSolver::dS_dVm_.coeffRef(row_id_dS_dVm_r, col_id_dS_dVm_r); + BaseNRAlgo::value_map_.push_back(&BaseNRAlgo::dS_dVm_.coeffRef(row_id_dS_dVm_r, col_id_dS_dVm_r)); }else if((col_id >= n_pvpq) && (row_id >= n_pvpq)){ // this is the J22 part (dS_dVm_i) const int row_id_dS_dVm_i = pq[row_id - n_pvpq]; const int col_id_dS_dVm_i = pq[col_id - n_pvpq]; // this_el = dS_dVm_i.coeff(row_id_dS_dVm_i, col_id_dS_dVm_i); - BaseNRSolver::value_map_[pos_el] = &BaseNRSolver::dS_dVm_.coeffRef(row_id_dS_dVm_i, col_id_dS_dVm_i); + BaseNRAlgo::value_map_.push_back(&BaseNRAlgo::dS_dVm_.coeffRef(row_id_dS_dVm_i, col_id_dS_dVm_i)); } // go to the next element ++pos_el; } } + // BaseNRAlgo::dS_dVa_.makeCompressed(); + // BaseNRAlgo::dS_dVm_.makeCompressed(); } template -void BaseNRSolverSingleSlack::fill_jacobian_matrix_kown_sparsity_pattern( +void BaseNRSingleSlackAlgo::fill_jacobian_matrix_kown_sparsity_pattern( const Eigen::VectorXi & pq, const Eigen::VectorXi & pvpq ) @@ -417,15 +458,15 @@ void BaseNRSolverSingleSlack::fill_jacobian_matrix_kown_sparsity_p const int n_pvpq = static_cast(pvpq.size()); // real_type * J_x_ptr = J_.valuePtr(); - const int n_cols = static_cast(BaseNRSolver::J_.cols()); // equal to nrow + const int n_cols = static_cast(BaseNRAlgo::J_.cols()); // equal to nrow unsigned int pos_el = 0; for (int col_id=0; col_id < n_cols; ++col_id){ - for (Eigen::SparseMatrix::InnerIterator it(BaseNRSolver::J_, col_id); it; ++it) + for (Eigen::SparseMatrix::InnerIterator it(BaseNRAlgo::J_, col_id); it; ++it) { const auto row_id = it.row(); // only one if is necessary (magic !) // top rows are "real" part and bottom rows are imaginary part (you can check) - it.valueRef() = row_id < n_pvpq ? std::real(*BaseNRSolver::value_map_[pos_el]) : std::imag(*BaseNRSolver::value_map_[pos_el]); + it.valueRef() = row_id < n_pvpq ? std::real(*BaseNRAlgo::value_map_[pos_el]) : std::imag(*BaseNRAlgo::value_map_[pos_el]); // go to the next element ++pos_el; } diff --git a/src/GaussSeidelSolver.cpp b/src/powerflow_algorithm/GaussSeidelAlgo.cpp similarity index 96% rename from src/GaussSeidelSolver.cpp rename to src/powerflow_algorithm/GaussSeidelAlgo.cpp index 365d02a..448dc9f 100644 --- a/src/GaussSeidelSolver.cpp +++ b/src/powerflow_algorithm/GaussSeidelAlgo.cpp @@ -6,9 +6,9 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "GaussSeidelSolver.h" +#include "GaussSeidelAlgo.h" -bool GaussSeidelSolver::compute_pf(const Eigen::SparseMatrix & Ybus, +bool GaussSeidelAlgo::compute_pf(const Eigen::SparseMatrix & Ybus, CplxVect & V, const CplxVect & Sbus, const Eigen::VectorXi & slack_ids, @@ -78,7 +78,7 @@ bool GaussSeidelSolver::compute_pf(const Eigen::SparseMatrix & Ybus, return res; } -void GaussSeidelSolver::one_iter(CplxVect & tmp_Sbus, +void GaussSeidelAlgo::one_iter(CplxVect & tmp_Sbus, const Eigen::SparseMatrix & Ybus, const Eigen::VectorXi & pv, const Eigen::VectorXi & pq) diff --git a/src/GaussSeidelSolver.h b/src/powerflow_algorithm/GaussSeidelAlgo.h similarity index 81% rename from src/GaussSeidelSolver.h rename to src/powerflow_algorithm/GaussSeidelAlgo.h index 26e0a46..2939966 100644 --- a/src/GaussSeidelSolver.h +++ b/src/powerflow_algorithm/GaussSeidelAlgo.h @@ -6,17 +6,17 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef GAUSSSEIDELSOLVER_H -#define GAUSSSEIDELSOLVER_H +#ifndef GAUSSSEIDEL_ALGO_H +#define GAUSSSEIDEL_ALGO_H -#include "BaseSolver.h" +#include "BaseAlgo.h" -class GaussSeidelSolver : public BaseSolver +class GaussSeidelAlgo : public BaseAlgo { public: - GaussSeidelSolver():BaseSolver() {}; + GaussSeidelAlgo():BaseAlgo(true) {}; - ~GaussSeidelSolver(){} + ~GaussSeidelAlgo(){} // todo can be factorized Eigen::SparseMatrix get_J(){ @@ -47,9 +47,9 @@ class GaussSeidelSolver : public BaseSolver private: // no copy allowed - GaussSeidelSolver( const GaussSeidelSolver & ) ; - GaussSeidelSolver & operator=( const GaussSeidelSolver & ) ; + GaussSeidelAlgo( const GaussSeidelAlgo & ) =delete; + GaussSeidelAlgo & operator=( const GaussSeidelAlgo & ) =delete; }; -#endif // GAUSSSEIDELSOLVER_H +#endif // GAUSSSEIDEL_ALGO_H diff --git a/src/GaussSeidelSynchSolver.cpp b/src/powerflow_algorithm/GaussSeidelSynchAlgo.cpp similarity index 95% rename from src/GaussSeidelSynchSolver.cpp rename to src/powerflow_algorithm/GaussSeidelSynchAlgo.cpp index 9225709..1a31bdb 100644 --- a/src/GaussSeidelSynchSolver.cpp +++ b/src/powerflow_algorithm/GaussSeidelSynchAlgo.cpp @@ -6,9 +6,9 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#include "GaussSeidelSynchSolver.h" +#include "GaussSeidelSynchAlgo.h" -void GaussSeidelSynchSolver::one_iter(CplxVect & tmp_Sbus, +void GaussSeidelSynchAlgo::one_iter(CplxVect & tmp_Sbus, const Eigen::SparseMatrix & Ybus, const Eigen::VectorXi & pv, const Eigen::VectorXi & pq) diff --git a/src/GaussSeidelSynchSolver.h b/src/powerflow_algorithm/GaussSeidelSynchAlgo.h similarity index 68% rename from src/GaussSeidelSynchSolver.h rename to src/powerflow_algorithm/GaussSeidelSynchAlgo.h index 57607ea..7749f08 100644 --- a/src/GaussSeidelSynchSolver.h +++ b/src/powerflow_algorithm/GaussSeidelSynchAlgo.h @@ -6,21 +6,21 @@ // SPDX-License-Identifier: MPL-2.0 // This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform. -#ifndef GAUSSSEIDELSYNCHSOLVER_H -#define GAUSSSEIDELSYNCHSOLVER_H +#ifndef GAUSSSEIDELSYNCH_ALGO_H +#define GAUSSSEIDELSYNCH_ALGO_H -#include "GaussSeidelSolver.h" +#include "GaussSeidelAlgo.h" /** The gauss seidel method, where all the updates are happening in a synchronous way, instead of in a asynchronous way (like for standard gauss seidel) **/ -class GaussSeidelSynchSolver : public GaussSeidelSolver +class GaussSeidelSynchAlgo: public GaussSeidelAlgo { public: - GaussSeidelSynchSolver():GaussSeidelSolver() {}; + GaussSeidelSynchAlgo():GaussSeidelAlgo() {}; - ~GaussSeidelSynchSolver(){} + ~GaussSeidelSynchAlgo(){} protected: void one_iter(CplxVect & tmp_Sbus, @@ -31,9 +31,9 @@ class GaussSeidelSynchSolver : public GaussSeidelSolver private: // no copy allowed - GaussSeidelSynchSolver( const GaussSeidelSynchSolver & ) ; - GaussSeidelSynchSolver & operator=( const GaussSeidelSynchSolver & ) ; + GaussSeidelSynchAlgo( const GaussSeidelSynchAlgo & ) =delete; + GaussSeidelSynchAlgo & operator=( const GaussSeidelSynchAlgo & )=delete ; }; -#endif // GAUSSSEIDELSYNCHSOLVER_H +#endif // GAUSSSEIDELSYNCH_ALGO_H