Skip to content

Commit

Permalink
WIP: bugfixes for B2B configuration, NaN results for inactive bus_dc,…
Browse files Browse the repository at this point in the history
… better handling of vsc status when connected to inactive bus/bus_dc
  • Loading branch information
rbolgaryn committed Feb 14, 2024
1 parent 2c96a7d commit c1513dc
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 36 deletions.
26 changes: 22 additions & 4 deletions pandapower/auxiliary.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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]]
Expand Down
7 changes: 5 additions & 2 deletions pandapower/build_bus.py
Original file line number Diff line number Diff line change
Expand Up @@ -463,20 +463,23 @@ 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:
gen_slacks = net._is_elements["gen"] & net.gen["slack"].values
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:
Expand Down
3 changes: 2 additions & 1 deletion pandapower/build_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
7 changes: 4 additions & 3 deletions pandapower/pd2ppc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
12 changes: 6 additions & 6 deletions pandapower/pypower/newtonpf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions pandapower/results.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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)
Expand Down
13 changes: 9 additions & 4 deletions pandapower/results_bus.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)

Expand Down
34 changes: 20 additions & 14 deletions pandapower/test/loadflow/test_facts.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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))
Expand All @@ -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()
Expand All @@ -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")
Expand Down

0 comments on commit c1513dc

Please sign in to comment.