diff --git a/pandapower/auxiliary.py b/pandapower/auxiliary.py index e9e4e5cf1..e9d20601b 100644 --- a/pandapower/auxiliary.py +++ b/pandapower/auxiliary.py @@ -40,12 +40,12 @@ from pandapower.pypower.idx_brch import F_BUS, T_BUS, BR_STATUS from pandapower.pypower.idx_brch_dc import DC_BR_STATUS, DC_F_BUS, DC_T_BUS -from pandapower.pypower.idx_bus import BUS_I, BUS_TYPE, NONE, PD, QD, VM, VA, REF, VMIN, VMAX, PV +from pandapower.pypower.idx_bus import BUS_I, BUS_TYPE, NONE, PD, QD, VM, VA, REF, PQ, VMIN, VMAX, PV from pandapower.pypower.idx_gen import PMIN, PMAX, QMIN, QMAX from pandapower.pypower.idx_ssc import SSC_STATUS, SSC_BUS, SSC_INTERNAL_BUS from pandapower.pypower.idx_tcsc import TCSC_STATUS, TCSC_F_BUS, TCSC_T_BUS -from pandapower.pypower.idx_vsc import VSC_STATUS, VSC_BUS, VSC_INTERNAL_BUS, VSC_BUS_DC -from .pypower.idx_bus_dc import DC_VMAX, DC_VMIN, DC_BUS_I, DC_BUS_TYPE, DC_NONE, DC_REF, DC_B2B +from pandapower.pypower.idx_vsc import VSC_STATUS, VSC_BUS, VSC_INTERNAL_BUS, VSC_BUS_DC, VSC_MODE_AC, VSC_MODE_AC_SL +from .pypower.idx_bus_dc import DC_VMAX, DC_VMIN, DC_BUS_I, DC_BUS_TYPE, DC_NONE, DC_REF, DC_B2B, DC_P try: from numba import jit @@ -866,7 +866,8 @@ def _select_is_elements_numba(net, isolated_nodes=None, isolated_nodes_dc=None, if controllable_in_service.any(): is_elements["%s_controllable" % element_table] = controllable_in_service element_in_service = element_in_service & ~controllable_in_service - is_elements[element_table] = element_in_service + # if element_table has both bus and bus_dc e.g. "vsc": + is_elements[element_table] = is_elements.get(element_table, True) & element_in_service if len(net.vsc) > 0 and "aux" in net["_pd2ppc_lookups"]: # reasoning: it can be that there are isolated DC buses. But they are only discovered @@ -875,7 +876,24 @@ def _select_is_elements_numba(net, isolated_nodes=None, isolated_nodes_dc=None, # This does not happen because for that we would need to perform another connectivity check # So we do it by hand here: vsc_aux_isolated = net["_pd2ppc_lookups"]["aux"]["vsc"][~is_elements["vsc"]] + # vsc_aux_isolated = net["_pd2ppc_lookups"]["aux"]["vsc"][~is_elements["vsc"] | + # ppc_bus_isolated[net["_pd2ppc_lookups"]["aux"]["vsc"]] | + # ppc_bus_isolated[net._ppc["vsc"][:, VSC_BUS].astype(np.int64)]] net._ppc["bus"][vsc_aux_isolated, BUS_TYPE] = NONE + # if there are no in service VSC that define the DC slack node, we must change the DC slack to type P + bus_dc_slack = net._ppc["bus_dc"][:, DC_BUS_TYPE] == DC_REF + bus_dc_with_vsc = net._ppc["vsc"][is_elements["vsc"], VSC_BUS_DC] + bus_dc_to_change = bus_dc_slack & (~np.isin(net._ppc["bus_dc"][:, DC_BUS_I], bus_dc_with_vsc)) + net._ppc["bus_dc"][bus_dc_to_change, DC_BUS_TYPE] = DC_P + + # if the AC bus is defined as REF only because it is connected to a vsc, and the vsc is out of service, + # it cannot be a REF bus anymore + bus_ac_slack = net._ppc["bus"][:, BUS_TYPE] == REF + bus_ac_with_vsc = net._ppc["vsc"][is_elements["vsc"], VSC_BUS] + bus_ac_to_change = (bus_ac_slack & (~np.isin(net._ppc["bus"][:, BUS_I], bus_ac_with_vsc)) & + (~np.isin(net._ppc["bus"][:, BUS_I], net._ppc["internal"]["ac_slack_buses"]))) + # changing just to PQ is OK because the setting of type PV happens later in build_gen + net._ppc["bus"][bus_ac_to_change, BUS_TYPE] = PQ is_elements["bus_is_idx"] = net["bus"].index.values[bus_in_service[net["bus"].index.values]] is_elements["bus_dc_is_idx"] = net["bus_dc"].index.values[bus_dc_in_service[net["bus_dc"].index.values]] diff --git a/pandapower/build_bus.py b/pandapower/build_bus.py index 85d1fff0e..a6943d76a 100644 --- a/pandapower/build_bus.py +++ b/pandapower/build_bus.py @@ -463,6 +463,7 @@ def set_reference_buses(net, ppc, bus_lookup, mode): return eg_buses = bus_lookup[net.ext_grid.bus.values[net._is_elements["ext_grid"]]] ppc["bus"][eg_buses, BUS_TYPE] = REF + ppc["internal"]["ac_slack_buses"] = set(eg_buses) # needed later in _select_is_elements_numba if mode == "sc": gen_slacks = net._is_elements["gen"] # generators are slacks for short-circuit calculation else: @@ -470,13 +471,15 @@ def set_reference_buses(net, ppc, bus_lookup, mode): if gen_slacks.any(): slack_buses = net.gen["bus"].values[gen_slacks] ppc["bus"][bus_lookup[slack_buses], BUS_TYPE] = REF - + ppc["internal"]["ac_slack_buses"] |= set(bus_lookup[slack_buses]) # needed later in _select_is_elements_numba + ppc["internal"]["ac_slack_buses"] = list(ppc["internal"]["ac_slack_buses"]) def set_reference_buses_dc(net, ppc, bus_lookup, mode): if mode == "nx": return vsc_dc_slack = net.vsc.control_mode_dc.values == "vm_pu" - ref_buses = bus_lookup[net.vsc.bus_dc.values[net._is_elements["vsc"] & vsc_dc_slack]] + vsc_ac_slack = net.vsc.control_mode_ac.values == "slack" # VSC that defines AC slack cannot define DC slack + ref_buses = bus_lookup[net.vsc.bus_dc.values[net._is_elements["vsc"] & vsc_dc_slack & ~vsc_ac_slack]] ppc["bus_dc"][ref_buses, DC_BUS_TYPE] = DC_REF # identify back-to-back converters: diff --git a/pandapower/build_gen.py b/pandapower/build_gen.py index a07a88516..69285a772 100644 --- a/pandapower/build_gen.py +++ b/pandapower/build_gen.py @@ -366,7 +366,8 @@ def _check_for_reference_bus(ppc): raise UserWarning("No reference bus is available. Either add an ext_grid or a gen with slack=True") # todo test this - bus_dc_relevant = np.flatnonzero(ppc["bus_dc"][:, DC_BUS_TYPE] != DC_NONE) + bus_dc_type = ppc["bus_dc"][:, DC_BUS_TYPE] + bus_dc_relevant = np.flatnonzero(bus_dc_type != DC_NONE) ref_dc, b2b_dc, _ = bustypes_dc(ppc["bus_dc"]) # throw an error since no reference bus is defined if len(bus_dc_relevant) > 0 and len(ref_dc) == 0 and len(b2b_dc) == 0: diff --git a/pandapower/pd2ppc.py b/pandapower/pd2ppc.py index 010d8550b..10a4b2c89 100644 --- a/pandapower/pd2ppc.py +++ b/pandapower/pd2ppc.py @@ -182,9 +182,6 @@ def _pd2ppc(net, sequence=None): # Also deactivates lines if they are connected to two out of service buses _branches_with_oos_buses(net, ppc) - if "pf" in mode: - _check_for_reference_bus(ppc) - if check_connectivity: if sequence in [None, 1, 2]: # sets islands (multiple isolated nodes) out of service @@ -202,6 +199,10 @@ def _pd2ppc(net, sequence=None): # sets buses out of service, which aren't connected to branches / REF buses aux._set_isolated_buses_out_of_service(net, ppc) + # we need to check this after checking connectivity (isolated vsc as DC slack cause change of DC_REF to DC_P) + if "pf" in mode: + _check_for_reference_bus(ppc) + _build_gen_ppc(net, ppc) aux._replace_nans_with_default_limits(net, ppc) diff --git a/pandapower/pypower/newtonpf.py b/pandapower/pypower/newtonpf.py index 21baa4456..96b72dbff 100644 --- a/pandapower/pypower/newtonpf.py +++ b/pandapower/pypower/newtonpf.py @@ -169,8 +169,8 @@ def newtonpf(Ybus, Sbus, V0, ref, pv, pq, ppci, options, makeYbus=None): # P_dc[vsc[p_set_point_index, VSC_BUS_DC].astype(np.int64)] = -vsc_value_dc[vsc_mode_dc == 1] # todo sum by group vsc_group_buses_p, P_dc_sum, vsc_group_buses_p_number = _sum_by_group(vsc_dc_bus[p_set_point_index], -vsc_value_dc[p_set_point_index], np.ones(sum(p_set_point_index))) P_dc[vsc_group_buses_p] = P_dc_sum - if len(P_dc_sum) == 0: - P_dc_sum = 0. + vsc_slack_p_dc_bus, _, _ = _sum_by_group(vsc_dc_bus[vsc_mode_ac == VSC_MODE_AC_SL], vsc_dc_bus[vsc_mode_ac == VSC_MODE_AC_SL], vsc_dc_bus[vsc_mode_ac == VSC_MODE_AC_SL]) + P_dc_sum_sl = P_dc[vsc_slack_p_dc_bus].copy() # later used in mismatch for vsc #vsc_group_buses_ref, _, vsc_group_buses_ref_number = _sum_by_group(vsc_dc_bus[p_set_point_index], -vsc_value_dc[vsc_mode_dc == 1], np.ones(sum(p_set_point_index))) # J for HVDC is expanded by the number of DC "P" buses (added below) @@ -302,7 +302,7 @@ def newtonpf(Ybus, Sbus, V0, ref, pv, pq, ppci, options, makeYbus=None): ssc_tb[ssc_controllable], Ybus_ssc, ssc_controllable, ssc_set_vm_pu, F, pq_lookup, vsc_controllable, vsc_fb, vsc_tb, Ybus_vsc, vsc_mode_ac, vsc_mode_dc, vsc_value_ac, vsc_value_dc, vsc_dc_bus, V_dc, Ybus_hvdc, num_branch_dc, P_dc, - dc_p, dc_ref, dc_b2b, dc_p_lookup, dc_ref_lookup, dc_b2b_lookup, P_dc_sum) + dc_p, dc_ref, dc_b2b, dc_p_lookup, dc_ref_lookup, dc_b2b_lookup, P_dc_sum_sl) F = r_[F, mis_facts] T_base = 100 # T in p.u. for better convergence @@ -473,7 +473,7 @@ def newtonpf(Ybus, Sbus, V0, ref, pv, pq, ppci, options, makeYbus=None): vsc_fb[vsc_controllable], vsc_tb[vsc_controllable], Ybus_vsc, vsc_mode_ac, vsc_mode_dc, vsc_value_ac, vsc_value_dc, vsc_dc_bus, V_dc, Ybus_hvdc, num_branch_dc, P_dc, dc_p, dc_ref, dc_b2b, dc_p_lookup, dc_ref_lookup, - dc_b2b_lookup, P_dc_sum) + dc_b2b_lookup, P_dc_sum_sl) F = r_[F, mis_facts] if tdpf: @@ -592,7 +592,7 @@ def _evaluate_Fx_facts(V,pq ,svc_buses=None, svc_set_vm_pu=None, tcsc_controllab vsc_controllable=None, vsc_fb=None, vsc_tb=None, Ybus_vsc=None, vsc_mode_ac=None, vsc_mode_dc=None, vsc_value_ac=None, vsc_value_dc=None, vsc_dc_bus=None, V_dc=None, Ybus_hvdc=None, num_branch_dc=None, P_dc=None, dc_p=None, dc_ref=None, dc_b2b=None, - dc_p_lookup=None, dc_ref_lookup=None, dc_b2b_lookup=None, P_dc_sum=None): + dc_p_lookup=None, dc_ref_lookup=None, dc_b2b_lookup=None, P_dc_sum_sl=None): mis_facts = np.array([], dtype=np.float64) if svc_buses is not None and len(svc_buses) > 0: @@ -649,7 +649,7 @@ def _evaluate_Fx_facts(V,pq ,svc_buses=None, svc_set_vm_pu=None, tcsc_controllab # this connects the AC slack result and the DC bus P set-point: vsc_slack_p = -Sbus_vsc[vsc_tb[ac_mode_sl]].real vsc_slack_p_dc_bus, vsc_slack_p_dc, _ = _sum_by_group(vsc_dc_bus[ac_mode_sl], vsc_slack_p, vsc_slack_p) - P_dc[vsc_slack_p_dc_bus] = P_dc_sum + vsc_slack_p_dc + P_dc[vsc_slack_p_dc_bus] = P_dc_sum_sl + vsc_slack_p_dc # find the connection between the DC buses and VSC buses # find the slack DC buses diff --git a/pandapower/results.py b/pandapower/results.py index eb479d37a..f972fd891 100644 --- a/pandapower/results.py +++ b/pandapower/results.py @@ -10,7 +10,7 @@ from pandapower.results_branch import _get_branch_results, _get_branch_results_3ph from pandapower.results_bus import _get_bus_results, _get_bus_dc_results, _set_buses_out_of_service, \ _get_shunt_results, _get_p_q_results, _get_bus_v_results, _get_bus_v_results_3ph, _get_p_q_results_3ph, \ - _get_bus_results_3ph, _get_bus_dc_v_results, _get_p_dc_results + _get_bus_results_3ph, _get_bus_dc_v_results, _get_p_dc_results, _set_dc_buses_out_of_service from pandapower.results_gen import _get_gen_results, _get_gen_results_3ph, _get_dc_slack_results BRANCH_RESULTS_KEYS = ("branch_ikss_f", "branch_ikss_t", @@ -26,7 +26,8 @@ def _extract_results(net, ppc): - _set_buses_out_of_service(ppc) + _set_buses_out_of_service(ppc) # for NaN results in net.res_bus for inactive buses + _set_dc_buses_out_of_service(ppc) # for NaN results in net.res_bus_dc for inactive buses bus_lookup_aranged = _get_aranged_lookup(net) bus_dc_lookup_aranged = _get_aranged_lookup(net, "bus_dc") _get_bus_v_results(net, ppc) diff --git a/pandapower/results_bus.py b/pandapower/results_bus.py index 0b9851e39..19d1049da 100644 --- a/pandapower/results_bus.py +++ b/pandapower/results_bus.py @@ -9,8 +9,8 @@ from numpy import complex128 from pandapower import VSC_INTERNAL_BUS from pandapower.auxiliary import _sum_by_group, sequence_to_phase, _sum_by_group_nvals -from pandapower.pypower.idx_bus import VM, VA, PD, QD, LAM_P, LAM_Q, BASE_KV, NONE, BS -from pandapower.pypower.idx_bus_dc import DC_VM +from pandapower.pypower.idx_bus import VM, VA, PD, QD, LAM_P, LAM_Q, BASE_KV, NONE, BS, BUS_TYPE, BUS_I +from pandapower.pypower.idx_bus_dc import DC_VM, DC_BUS_TYPE, DC_NONE, DC_PD, DC_BUS_I from pandapower.pypower.idx_gen import PG, QG from pandapower.build_bus import _get_motor_pq, _get_symmetric_pq_of_unsymetric_element @@ -27,14 +27,19 @@ def _set_buses_out_of_service(ppc): - - disco = np.where(ppc["bus"][:, 1] == NONE)[0] + disco = np.where(ppc["bus"][:, BUS_TYPE] == NONE)[BUS_I] ppc["bus"][disco, VM] = np.nan ppc["bus"][disco, VA] = np.nan ppc["bus"][disco, PD] = 0 ppc["bus"][disco, QD] = 0 +def _set_dc_buses_out_of_service(ppc): + disco = np.where(ppc["bus_dc"][:, DC_BUS_TYPE] == DC_NONE)[DC_BUS_I] + ppc["bus_dc"][disco, DC_VM] = np.nan + ppc["bus_dc"][disco, DC_PD] = 0 + + def _get_bus_v_results(net, ppc, suffix=None): bus_idx = _get_bus_idx(net) diff --git a/pandapower/test/loadflow/test_facts.py b/pandapower/test/loadflow/test_facts.py index 6f97b735f..5c7d9784f 100644 --- a/pandapower/test/loadflow/test_facts.py +++ b/pandapower/test/loadflow/test_facts.py @@ -1333,8 +1333,10 @@ def test_vsc_hvdc_mode4(): pp.create_vsc(net, 2, 1, 0.1, 5, control_mode_ac="vm_pu", control_value_ac=1.02, control_mode_dc="p_mw", control_value_dc=5) - with pytest.raises(UserWarning, match="reference bus for the dc grid"): - pp.runpp(net) + # all DC buses are set out of service, so there is no error in this case and bus_dc results are NaN + # with pytest.raises(UserWarning, match="reference bus for the dc grid"): + # pp.runpp(net) + runpp_with_consistency_checks(net) def test_vsc_hvdc_mode5(): @@ -2105,6 +2107,7 @@ def test_2vsc_1ac_2dc(control_mode_ac, control_mode_dc): pp.runpp(net) +@pytest.mark.skip(reason="DC line connected to D2B VSC configuration not implemented") @pytest.mark.parametrize("control_mode_ac", list(product(['vm_pu', 'q_mvar'], repeat=2))) @pytest.mark.parametrize("control_mode_dc", list(product(['vm_pu', 'p_mw'], repeat=2))) def test_2vsc_2ac_1dc(control_mode_ac, control_mode_dc): @@ -2135,6 +2138,7 @@ def test_2vsc_2ac_1dc(control_mode_ac, control_mode_dc): runpp_with_consistency_checks(net) +@pytest.mark.skip(reason="DC line connected to D2B VSC configuration not implemented") @pytest.mark.parametrize("control_mode_ac", list(product(['vm_pu', 'q_mvar'], repeat=2))) @pytest.mark.parametrize("control_mode_dc", list(product(['vm_pu', 'p_mw'], repeat=2))) def test_2vsc_1ac_1dc(control_mode_ac, control_mode_dc): @@ -2168,13 +2172,14 @@ def test_2vsc_1ac_1dc(control_mode_ac, control_mode_dc): pp.runpp(net) -def test_vsc_slack_minimal_wrong(): # todo: VSC as slack cannot have DC bus as vm_pu, therefore must be excluded from DC slacks and then raise error that not enough DC slacks +def test_vsc_slack_minimal_wrong(): # np.set_printoptions(linewidth=1000, suppress=True, precision=2) # from pandapower.test.loadflow.test_facts import * net = pp.create_empty_network() # AC part pp.create_buses(net, 2, 110, geodata=[(200, 0), (400, 0)]) pp.create_load(net, 1, 10, 4) + pp.create_gen(net, 0, 0) # DC part pp.create_bus_dc(net, 110, 'A', geodata=(210, 0)) @@ -2185,14 +2190,15 @@ def test_vsc_slack_minimal_wrong(): # todo: VSC as slack cannot have DC bus as pp.create_vsc(net, 0, 0, 0.1, 5, control_mode_ac="slack", control_value_ac=1, control_mode_dc="vm_pu", control_value_dc=1.02) pp.create_vsc(net, 1, 1, 0.1, 5, control_mode_ac="slack", control_value_ac=1, control_mode_dc="p_mw", control_value_dc=1) - pp.runpp(net) - - runpp_with_consistency_checks(net) - - # pp.plotting.simple_plot(net, plot_loads=True, load_size=5) + # VSC as slack cannot have DC bus as vm_pu, therefore must be excluded from DC slacks + # Then the DC buses are set out of service, and the corresponding VSC are also set out of service + # Then the corresponding AC buses are changed from type REF to type PQ, which is valid because type PV is set later + # Then runpp raises "no slacks" error: + with pytest.raises(UserWarning, match="No reference bus is available"): + pp.runpp(net) -def test_vsc_slack_minimal_wrong2(): # todo runpp must raise error here that DC grid has no slacks because the first VSC is deactivated because its AC bus is not connected to AC slack +def test_vsc_slack_minimal_wrong2(): # np.set_printoptions(linewidth=1000, suppress=True, precision=2) # from pandapower.test.loadflow.test_facts import * net = pp.create_empty_network() @@ -2209,11 +2215,11 @@ def test_vsc_slack_minimal_wrong2(): # todo runpp must raise error here that DC pp.create_vsc(net, 0, 0, 0.1, 5, control_mode_ac="vm_pu", control_value_ac=1, control_mode_dc="vm_pu", control_value_dc=1.02) pp.create_vsc(net, 1, 1, 0.1, 5, control_mode_ac="slack", control_value_ac=1, control_mode_dc="p_mw", control_value_dc=1) - pp.runpp(net) - - runpp_with_consistency_checks(net) - - # pp.plotting.simple_plot(net, plot_loads=True, load_size=5) + # VSC that defines AC slack cannot define DC slack at the same time + # DC slack buses that are only connected to VSC AC slacks are converted to type P buses + # Then runpp raises "no DC slacks" error: + with pytest.raises(UserWarning, match="No reference bus for the dc grid is available"): + pp.runpp(net) @pytest.mark.xfail(reason="AC bus same as ext_grid bus not implemented")