Skip to content

Commit

Permalink
completing the test suite
Browse files Browse the repository at this point in the history
  • Loading branch information
BDonnot committed Dec 21, 2023
1 parent 1d80160 commit 4d13a26
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 14 deletions.
169 changes: 165 additions & 4 deletions lightsim2grid/tests/test_solver_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
# - 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
# - 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, NRSingleSlack, GaussSeidel, GaussSeidelSynch, FDPF_XB and FDPFBX
# 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
Expand All @@ -43,9 +44,9 @@ def setUp(self) -> None:
action_class=CompleteAction,
backend=LightSimBackend())
self.gridmodel = self.env.backend._grid
self._aux_setup_grid()
self.v_init = 1.0 * self.env.backend.V
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!

Expand Down Expand Up @@ -586,6 +587,166 @@ 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()
4 changes: 0 additions & 4 deletions src/GridModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -717,8 +717,6 @@ class GridModel : public GenericContainer
{
(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;
solver_control_.tell_dimension_changed();
}
} else{
if(has_changed(el_pos))
Expand All @@ -728,8 +726,6 @@ class GridModel : public GenericContainer
// 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;
solver_control_.tell_dimension_changed();
}
}
}
Expand Down
8 changes: 5 additions & 3 deletions src/element_container/ShuntContainer.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,13 +131,15 @@ class ShuntContainer : public GenericContainer

void deactivate(int shunt_id, SolverControl & solver_control) {
if(status_[shunt_id]){
solver_control.tell_recompute_sbus();
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();
if(!status_[shunt_id]){
solver_control.tell_recompute_sbus(); // DC
solver_control.tell_recompute_ybus(); // AC
}
_reactivate(shunt_id, status_);
}
Expand Down
16 changes: 13 additions & 3 deletions src/powerflow_algorithm/BaseFDPFAlgo.tpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,30 @@ bool BaseFDPFAlgo<LinearSolver, XB_BX>::compute_pf(const Eigen::SparseMatrix<cpl
// TODO DEBUG MODE
std::ostringstream exc_;
exc_ << "BaseFDPFAlgo::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_ << "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_ << "BaseFDPFAlgo::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_ << "V (" << V.size() << ") and Ybus (" << Ybus.rows()<< ", " << Ybus.cols() << ").";
throw std::runtime_error(exc_.str());
}
reset_timer();
auto timer = CustTimer();
if(!is_linear_solver_valid()) return false;
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();
}

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
Expand Down

0 comments on commit 4d13a26

Please sign in to comment.