Skip to content

Commit

Permalink
adding fdpf powerflow with tests see #63 and fix a bug with shunts
Browse files Browse the repository at this point in the history
  • Loading branch information
BDonnot committed Oct 3, 2023
1 parent 617ef97 commit e2c1356
Show file tree
Hide file tree
Showing 26 changed files with 794 additions and 221 deletions.
8 changes: 6 additions & 2 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,30 @@ Change Log

[TODO]
--------
- make an `init` function from pypowsybl
- [refacto] have a structure in cpp for the buses
- [refacto] have the id_grid_to_solver and id_solver_to_grid etc. directly in the solver and NOT in the gridmodel.
- support 3w trafo (as modeled in pandapower)
- improve speed by not performing internal checks
(keep check for boundaries and all for python API instead) [see `TODO DEBUG MODE` in c++ code]
- improve speed
- code parrallelism directly in the `Computer` and `SecurityAnalysisCPP` classes
- a mode to do both `Computer` and `SecurityAnalysisCPP`
- use the "multi slack hack" (see issue #50) for SecurityAnalysis or Computer for example
- code `helm` powerflow method
- possibility to read CGMES files
- possibility to read XIIDM files
- interface with gridpack (to enforce q limits for example)
- maybe have a look at suitesparse "sliplu" tools ?
- easier building (get rid of the "make" part)
- code NR with dense matrices

[0.7.5] 2023-xx-yy
--------------------
- [FIXED] a bug in DC powerflow when asking for computation time: it was not reset to 0. when
multiple powerflows used the same solver
- [FIXED] a bug in AC and DC powerflow when shunts had active values
- [ADDED] possibility to initialize a powergrid based on pypowsybl
- [ADDED] some more algorithm to perform powerflow: Fast Decoupled Powerflow (in BX and XB variant)
see https://github.com/BDonnot/lightsim2grid/issues/63
- [IMPROVED] now shipping `src` and `eigen` directory in the source of
lightsim2grid to allow their installation if wheels are not provided.
- [IMPROVED] in the underlying cpp GridModel powerlines can now have 2
Expand Down
18 changes: 17 additions & 1 deletion lightsim2grid/solver/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
"GaussSeidelSynchSolver",
"SparseLUSolver",
"SparseLUSolverSingleSlack",
"DCSolver"]
"DCSolver",
"FDPF_XB_SparseLUSolver",
"FDPF_BX_SparseLUSolver"]

from lightsim2grid_cpp import SolverType
from lightsim2grid_cpp import ErrorType
Expand All @@ -24,14 +26,20 @@
from lightsim2grid_cpp import SparseLUSolver # SolverType.SparseLU
from lightsim2grid_cpp import SparseLUSolverSingleSlack # SolverType.SparseLUSingleSlack
from lightsim2grid_cpp import DCSolver # SolverType.DC
from lightsim2grid_cpp import FDPF_XB_SparseLUSolver # SolverType.FDPF_XB_SparseLU
from lightsim2grid_cpp import FDPF_BX_SparseLUSolver # SolverType.FDPF_BX_SparseLU

try:
from lightsim2grid_cpp import KLUSolver # SolverType.KLU
from lightsim2grid_cpp import KLUSolverSingleSlack # SolverType.KLUSingleSlack
from lightsim2grid_cpp import KLUDCSolver # SolverType.KLUDC
from lightsim2grid_cpp import FDPF_XB_KLUSolver # SolverType.FDPF_XB_KLU
from lightsim2grid_cpp import FDPF_BX_KLUSolver # SolverType.FDPF_BX_KLU
__all__.append("KLUSolver")
__all__.append("KLUSolverSingleSlack")
__all__.append("KLUDCSolver")
__all__.append("FDPF_XB_KLUSolver")
__all__.append("FDPF_BX_KLUSolver")
except Exception as exc_:
# KLU is not available
pass
Expand All @@ -40,9 +48,13 @@
from lightsim2grid_cpp import NICSLUSolver # SolverType.NICSLU
from lightsim2grid_cpp import NICSLUSolverSingleSlack # SolverType.NICSLUSingleSlack
from lightsim2grid_cpp import NICSLUDCSolver # SolverType.NICSLUDC
from lightsim2grid_cpp import FDPF_XB_NICSLUSolver # SolverType.FDPF_XB_NICSLU
from lightsim2grid_cpp import FDPF_BX_NICSLUSolver # SolverType.FDPF_BX_NICSLU
__all__.append("NICSLUSolver")
__all__.append("NICSLUSolverSingleSlack")
__all__.append("NICSLUDCSolver")
__all__.append("FDPF_XB_NICSLUSolver")
__all__.append("FDPF_BX_NICSLUSolver")
except Exception as exc_:
# NICSLU is not available
pass
Expand All @@ -51,9 +63,13 @@
from lightsim2grid_cpp import CKTSOSolver # SolverType.CKTSO
from lightsim2grid_cpp import CKTSOSolverSingleSlack # SolverType.CKTSOSingleSlack
from lightsim2grid_cpp import CKTSODCSolver # SolverType.CKTSODC
from lightsim2grid_cpp import FDPF_XB_CKTSOSolver # SolverType.FDPF_XB_CKTSO
from lightsim2grid_cpp import FDPF_BX_CKTSOSolver # SolverType.FDPF_BX_CKTSO
__all__.append("CKTSOSolver")
__all__.append("CKTSOSolverSingleSlack")
__all__.append("CKTSODCSolver")
__all__.append("FDPF_XB_CKTSOSolver")
__all__.append("FDPF_BX_CKTSOSolver")
except Exception as exc_:
# NICSLU is not available
pass
4 changes: 4 additions & 0 deletions lightsim2grid/tests/test_DoNothingACDC.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,7 @@ def test_dc(self):

def test_ac(self):
self._aux_test_do_nothing(is_dc=False)


if __name__ == "__main__":
unittest.main()
166 changes: 166 additions & 0 deletions lightsim2grid/tests/test_fdpf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# Copyright (c) 2023, RTE (https://www.rte-france.com)
# See AUTHORS.txt
# This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
# If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
# you can obtain one at http://mozilla.org/MPL/2.0/.
# SPDX-License-Identifier: MPL-2.0
# This file is part of LightSim2grid, LightSim2grid implements a c++ backend targeting the Grid2Op platform.

import warnings
import numpy as np
from scipy.sparse import csc_matrix

from lightsim2grid_cpp import FDPFMethod
from lightsim2grid.gridmodel import init
from lightsim2grid.solver import SolverType

import pandapower.networks as pn
import pandapower as pp
from pandapower.pypower.makeB import makeB
from pandapower.pf.ppci_variables import _get_pf_variables_from_ppci
from pandapower.pd2ppc import _pd2ppc
from pandapower.auxiliary import _init_runpp_options

import unittest



class BaseFDPFTester:
def get_solving_method(self):
return FDPFMethod.XB

def get_algo_pp(self):
# return "fdxb" if self.fdpf_meth == FDPFMethod.XB else "fdbx" # in theory
return "fdbx" if self.fdpf_meth == FDPFMethod.XB else "fdxb" # but.... https://github.com/e2nIEE/pandapower/issues/2142

def get_network(self):
return pn.case14()
# self.net = pn.case118()
# self.net = pn.case300()

def get_pp_options(self):
return dict(algorithm=self.get_algo_pp(),
calculate_voltage_angles="auto",
init="flat",
max_iteration="auto",
tolerance_mva=1e-8,
trafo_model="t",
trafo_loading="current",
enforce_q_lims=False,
check_connectivity=True,
voltage_depend_loads=True,
consider_line_temperature=False,
run_control=False,
distributed_slack=False,
tdpf=False,
tdpf_delay_s=None)

def setUp(self) -> None:
self.net = self.get_network()
with warnings.catch_warnings():
warnings.filterwarnings("ignore")
self.gridmodel = init(self.net)
self.tol = 1e-7
self.tol_solver = 1e-8

# XB
self.fdpf_meth = self.get_solving_method()
self.alg = 2 if self.fdpf_meth == FDPFMethod.XB else 3

# alg = 3 # BX # TODO later
# self.fdpf_meth = FDPFMethod.BX # TODO later
return super().setUp()

def _aux_get_Bp_Bpp(self):
# init pp options
_init_runpp_options(self.net, **self.get_pp_options())

# clear lookups
self.net._pd2ppc_lookups = {"bus": np.array([], dtype=np.int64), "ext_grid": np.array([], dtype=np.int64),
"gen": np.array([], dtype=np.int64), "branch": np.array([], dtype=np.int64)}

# convert pandapower net to ppc
ppc, ppci = _pd2ppc(self.net)
self.net["_ppc"] = ppc
baseMVA, bus, gen, branch, svc, tcsc, ref, pv, pq, *_, gbus, V0, ref_gens = _get_pf_variables_from_ppci(ppci)
pp_Bp, pp_Bpp = makeB(baseMVA, bus, np.real(branch), self.alg)
return pp_Bp, pp_Bpp, pv, pq, V0

def test_bp_bpp(self):
"""test that Bp and Bpp are correct (from the grid)"""
pp_Bp, pp_Bpp, *_= self._aux_get_Bp_Bpp()

# now get the matrices
self.gridmodel.ac_pf(1.04 * np.ones(self.net.bus.shape[0], dtype=complex), 10, 1e-7) # need to init the "model bus id" id_grid_to_solver
ls_Bp = self.gridmodel.debug_get_Bp_python(self.fdpf_meth)
ls_Bpp = self.gridmodel.debug_get_Bpp_python(self.fdpf_meth)

assert np.abs(pp_Bp - ls_Bp).max() <= self.tol, f"error in Bp: max {np.abs(pp_Bp - ls_Bp).max():.2e}"
assert np.abs(pp_Bpp - ls_Bpp).max() <= self.tol, f"error in Bpp: max {np.abs(pp_Bpp - ls_Bpp).max():.2e}"

def test_Bp_Bpp_solver(self):
"""test that Bp and Bpp are correct (from the solver: where only some indices are used)"""
# retrieve the matrix in the solver
self.gridmodel.change_solver(SolverType.FDPF_XB_SparseLU if self.fdpf_meth == FDPFMethod.XB else SolverType.FDPF_BX_SparseLU)
V_ls = self.gridmodel.ac_pf(1.04 * np.ones(self.net.bus.shape[0], dtype=complex), 30, 1e0) # to ensure it can "converge" to be able to retrieve Bp and Bpp
used_solver = self.gridmodel.get_solver().get_fdpf_xb_lu() if self.fdpf_meth == FDPFMethod.XB else self.gridmodel.get_solver().get_fdpf_bx_lu()
ls_Bp = used_solver.debug_get_Bp_python()
ls_Bpp = used_solver.debug_get_Bpp_python()
# recreate the matrices from pandapower
grid_Bp, grid_Bpp, pv, pq, *_ = self._aux_get_Bp_Bpp()
pvpq = np.r_[pv, pq]
pp_Bp = grid_Bp[np.array([pvpq]).T, pvpq].tocsc()
pp_Bpp = grid_Bpp[np.array([pq]).T, pq].tocsc()

# check they match
assert np.abs(pp_Bp - ls_Bp).max() <= self.tol, f"error in Bp: max {np.abs(pp_Bp - ls_Bp).max():.2f}"
assert np.abs(pp_Bpp - ls_Bpp).max() <= self.tol, f"error in Bpp: max {np.abs(pp_Bpp - ls_Bpp).max():.2f}"

def test_solver(self):
self.gridmodel.change_solver(SolverType.FDPF_XB_SparseLU if self.fdpf_meth == FDPFMethod.XB else SolverType.FDPF_BX_SparseLU)
*_, V0 = self._aux_get_Bp_Bpp()
V_ls = self.gridmodel.ac_pf(1. * V0, 30, self.tol_solver * self.gridmodel.get_sn_mva()) # no division by sn_mva in pypower implementation TODO issue !
pp.runpp(self.net, algorithm=self.get_algo_pp(), tol=self.tol_solver, init="flat")
assert V_ls.size > 0, f"lightsim2grid powerflow has diverged {self.gridmodel.get_solver().get_error()}"
nb_iter_ls = self.gridmodel.get_solver().get_nb_iter()
nb_iter_pp = self.net._ppc['iterations']
assert nb_iter_pp == nb_iter_ls, f"mismatch nb_iter {nb_iter_pp} vs {nb_iter_ls} maybe https://github.com/e2nIEE/pandapower/issues/2142 has been fixed ?"
V_pp = self.net.res_bus["vm_pu"].values * np.exp(1j * np.deg2rad(self.net.res_bus["va_degree"].values))
assert np.abs(V_ls - V_pp).max() <= self.tol, f"mismatch in V: {np.abs(V_ls - V_pp).max():.2e}"


class FDPFTester_Case14_XB(BaseFDPFTester, unittest.TestCase):
pass


class FDPFTester_Case14_BX(BaseFDPFTester, unittest.TestCase):
def get_solving_method(self):
return FDPFMethod.BX


class FDPFTester_Case118_XB(BaseFDPFTester, unittest.TestCase):
def get_network(self):
return pn.case118()


class FDPFTester_Case118_BX(BaseFDPFTester, unittest.TestCase):
def get_network(self):
return pn.case118()
def get_solving_method(self):
return FDPFMethod.BX


class FDPFTester_Case300_XB(BaseFDPFTester, unittest.TestCase):
def get_network(self):
return pn.case300()


class FDPFTester_Case300_BX(BaseFDPFTester, unittest.TestCase):
def get_network(self):
return pn.case300()
def get_solving_method(self):
return FDPFMethod.BX


if __name__ == "__main__":
unittest.main()
Loading

0 comments on commit e2c1356

Please sign in to comment.