diff --git a/lightsim2grid/tests/test_solver_control.py b/lightsim2grid/tests/test_solver_control.py index 447042d..2104eaf 100644 --- a/lightsim2grid/tests/test_solver_control.py +++ b/lightsim2grid/tests/test_solver_control.py @@ -3,12 +3,16 @@ # 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 -# - change load -# - change gen -# - change shunt +# - reconnect trafo X +# - change load X +# - change gen X +# - change shunt X +# - change storage X # - change slack # - change slack weight +# - turnoff_gen_pv # - when it diverges (and then I can make it un converge normally) # - test change bus to -1 and deactivate element has the same impact @@ -21,9 +25,16 @@ import grid2op from grid2op.Action import CompleteAction from lightsim2grid import LightSimBackend +from lightsim2grid.solver import SolverType 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") @@ -32,6 +43,7 @@ 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.tol_solver = 1e-8 # solver @@ -39,6 +51,8 @@ def setUp(self) -> None: 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() @@ -87,20 +101,19 @@ def aux_do_undo_ac(self, 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" - print(f"Looking at {el_id} in {pf_mode}") - + # 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) + 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}" + 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}" @@ -173,6 +186,8 @@ def test_disco_reco_line_ac(self, runpf_fun="_run_ac_pf"): 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"): @@ -194,5 +209,220 @@ def test_disco_reco_trafo_ac(self, runpf_fun="_run_ac_pf"): 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, + ) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 453e3d3..6545d63 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -592,7 +592,6 @@ 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")