-
Notifications
You must be signed in to change notification settings - Fork 503
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[bug] Doc not up to date + behaviour mismatch on ieee cases between pp3 and pp2 #2557
Comments
@BDonnot thanks for bringing this up. Could you by any chance add the version of lightsim2grid you were using? Since ls2g needed to be patched to be fully compatible. |
Yes of course, lightsim2grid is 0.10.2 as shown in the last paragraph :-) What bothers me here is that the ieee 118 leads to different results (flows and voltages) depending on pandapower version. I am not sure this is desirable. Also, if you have a script that convert the "old" grid to the new grid then i'd be happy to have it. Because we stored lots of grid in pandapower format for the l2rpn competitions / grid2op eco system and I can't convert them unfortunately :-( |
Hi @BDonnot,
... which gives me these results: ![]() Detailed answer: We thought, why is there even a negative As we changed all of this, we got failing tests in the
and apparently we changed only the test files there and not the json files used by pandapower.networks.caseXYZ()
We are currently working on the re-conversion of matpower files via @BDonnot please remember some trafos will be handled as impedances as pointed out by Roman here: Grid2op/lightsim2grid#88 ... I guess the issue here is also related to your answer As a quick fix in grid2op, you can multiply the Summary of To-Do's:
BR Pawel |
P.S. Although the Z and Y mismatches are better than before, the Y mismatch is still off, can't figure out why right now... will need to comparte the
I am also attaching the ppc_branch_pp2.csv Maybe this helps... |
Hello @pawellytaev Thanks a lot for the fix, I will see if fixing it at import will solve (at least for the short term) my issue on old pandapower grid. You also write:
I think this is the right thing to do. And, if I may, I posted an "issue" a few months ago (see #2131). If you are to reconvert them again, from a user perspective, it would be better if the default grid matches the expected results with the default values of pp.runpp.
This bothered me also back in 2020. I was trying to match the results of pandapower in lightsim2grid. I could not explain this piece of code but implemented finally in lightsim2grid without understanding it.
Yes I remember that, but without any input grid and proper working example I cannot improve lightsim2grid with it :-/ It should not be that hard to do honestly. The hardest part is always "what to do exactly" (devil is in the details, here specifically in the "exactly")
Yes I will do that when using the "PandaPowerBackend" and pandapower version 3 or above. When fixed, I will only do this if the "bug" is still present. Thanks a lot :-) |
good idea, we can consider that when we re-work the json's
I was digging deeper into this issue and found out a few things more things that I honestly don't understand. Comparison between pp and ls2g # extract values computed by pandapower
if pp_ver_3:
r_comp = net._ppc["branch"][-net.trafo.shape[0]:, BR_R]
x_comp = net._ppc["branch"][-net.trafo.shape[0]:, BR_X]
g_comp = net._ppc["branch"][-net.trafo.shape[0]:, BR_G]
b_comp = net._ppc["branch"][-net.trafo.shape[0]:, BR_B]
else:
r_comp = net._ppc["branch"][-net.trafo.shape[0]:, BR_R].real
x_comp = net._ppc["branch"][-net.trafo.shape[0]:, BR_X].real
g_comp = -net._ppc["branch"][-net.trafo.shape[0]:, BR_B].imag # in pp<3.0, BR_B was b-1j*g
b_comp = net._ppc["branch"][-net.trafo.shape[0]:, BR_B].real
# extract values computed by lightsim2grid
r_ls = np.array([el.r_pu for el in gridmodel.get_trafos()])
x_ls = np.array([el.x_pu for el in gridmodel.get_trafos()])
h_ls = np.array([el.h_pu for el in gridmodel.get_trafos()])
g_ls = h_ls.real
b_ls = h_ls.imag Comparing them print(f"Mismatch with lightsim corrected (y): {(np.sqrt(np.square(g_ls) + np.square(b_ls)) - np.sqrt(np.square(g_comp) + np.square(b_comp))).max()}")
print(f"Mismatch with lightsim (g): {(np.abs(g_ls - g_comp)).max()}")
print(f"Mismatch with lightsim (b): {(np.abs(b_ls - b_comp)).max()}") leads to: and ... and if compare the power flow results between pandapower and lightsim2grid, I get the following: print("Comparison ls2g ac_pf() and pandapower runpp")
V_ls = gridmodel.ac_pf(np.array([1.0]*len(net.bus)), 15, 1e-8)
Vm_ls = np.abs(V_ls)
Va_ls = np.angle(V_ls)
print(f"Vm max mismatch ls2g {lightsim2grid.__version__} vs. pandapower {pp.__version__}")
print((net.res_bus.vm_pu.values - Vm_ls.T).max())
print(f"Va max mismatch ls2g {lightsim2grid.__version__} vs. pandapower {pp.__version__}")
print((net.res_bus.va_degree.values - Va_ls.T).max()) pandapower3: pandapower2: The slight difference in voltage magnitude I would assume to be a numerical issue, but the difference in voltage angle between pp and ls2g is somewhat concerning. @BDonnot do you know why there might be this huge difference? Or do I compare apples with oranges here? pp.runpp results between pandapower 3.0 and 2.4.10 However, there is still a slight difference between pandapower 3.0 and 2.4.10 results, which can be seen in the voltage results: print(f"pandapower {pp.__version__} vm_pu describe")
print(net.res_bus.vm_pu.describe())
print(f"pandapower {pp.__version__} va_degree describe")
print(net.res_bus.va_degree.describe()) leads to: pandapower2 and this is definitely due to the difference in trafo BR_B values between the versions when comparing the branch_df_3 = pd.read_csv('ppc_branch_pp3.csv', index_col=0)
branch_df_2 = pd.read_csv('ppc_branch_pp2.csv', index_col=0)
BR_B3 = branch_df_3['BR_B'].values
BR_G3 = branch_df_3['BR_G'].values
BR_B2 = np.array(branch_df_2['BR_B'].values, dtype=np.complex128).real
BR_G2 = -np.array(branch_df_2['BR_B'].values.imag, dtype=np.complex128).imag
print(BR_B3[-len(net.trafo):]-BR_B2[-len(net.trafo):])
print(BR_G3[-len(net.trafo):]-BR_G2[-len(net.trafo):]) leads to: I will look deeper into the pandapower code to see where the differences come from. Will keep you posted. What I don't really understand is why I get the same voltage magnitude and branch admittances/impedances after comparing ls2g with pp3.0 and pp2.4... Is there a dependency with pandapower when calling |
P.S. here's again the full reproducible example that I worked on import numpy as np
import pandapower
import pandapower as pp
import pandapower.networks as pn
from importlib.metadata import version
from packaging.version import Version
import warnings
import pandas as pd
import lightsim2grid
from lightsim2grid.gridmodel import init_from_pandapower
pp_ver_3 = Version(pandapower.__version__) >= Version("3")
tol = 1e-5
if pp_ver_3:
from pandapower.pypower.idx_brch import F_BUS, T_BUS, BR_R, BR_X, BR_B, BR_G
else:
from pandapower.pypower.idx_brch import F_BUS, T_BUS, BR_R, BR_X, BR_B
def _wye_delta_new(r, x, g, b, r_ratio=None, x_ratio=None):
"""taken from pandapower 3 source code"""
if r_ratio is None:
r_ratio = 0.5 + np.zeros_like(r)
if x_ratio is None:
x_ratio = 0.5 + np.zeros_like(x)
tidx = (g != 0) | (b != 0)
za_star = r[tidx] * r_ratio[tidx] + x[tidx] * x_ratio[tidx] * 1j
zb_star = r[tidx] * (1 - r_ratio[tidx]) + x[tidx] * (1 - x_ratio[tidx]) * 1j
zc_star = 1 / (g - 1j*b)[tidx]
zSum_triangle = za_star * zb_star + za_star * zc_star + zb_star * zc_star
zab_triangle = zSum_triangle / zc_star
zac_triangle = zSum_triangle / zb_star
zbc_triangle = zSum_triangle / za_star
r[tidx] = zab_triangle.real
x[tidx] = zab_triangle.imag
yf = 1 / zac_triangle
yt = 1 / zbc_triangle
# 2 because in makeYbus Bcf, Bct are divided by 2:
g[tidx] = yf.real * 2
b[tidx] = yf.imag * 2
g_asym = np.zeros_like(g)
b_asym = np.zeros_like(b)
g_asym[tidx] = 2 * yt.real - g[tidx]
b_asym[tidx] = 2 * yt.imag - b[tidx]
return r, x, g, b
print(f"pandapower version: {version('pandapower')}")
print(f"pandas version: {version('pandas')}")
print(f"numpy version: {version('numpy')}")
print(f"scipy version: {version('scipy')}")
print(f"lightsim2grid version: {version('lightsim2grid')}")
### main part of the code
# https://github.com/e2nIEE/pandapower/issues/2532
CASE = "case118"
net = pn.case118()
if pp_ver_3:
net.trafo.i0_percent *= -1
# net.trafo.tap_pos = net.trafo.tap_neutral
pp.runpp(net, lightims2grid=False, numba=False)
with warnings.catch_warnings():
warnings.filterwarnings("ignore")
gridmodel = init_from_pandapower(net)
# fix the "None" in the tap side
net.trafo.loc[np.array([el is None for el in net.trafo["tap_side"]]), "tap_side"] = "hv"
# doc equations
# https://pandapower.readthedocs.io/en/latest/elements/trafo.html
# n
v_ref_lvbus_trafo = net.trafo["vn_lv_kv"]
v_ref_hvbus_trafo = net.trafo["vn_hv_kv"]
theta = net.trafo["shift_degree"] # => always 0.
# impedance
cste = net.sn_mva / net.trafo["sn_mva"]
# cste = 100. / 10000.
r_k = net.trafo["vkr_percent"] * 0.01 * cste
z_k = net.trafo["vk_percent"] * 0.01 * cste
x_k = np.sqrt(z_k**2 - r_k**2)
z_k_cplx = r_k + 1j * x_k
# magnetising admittance
y_m = net.trafo["i0_percent"] * 0.01
g_m = net.trafo["pfe_kw"] / (net.trafo["sn_mva"] * 1000.) * cste
b_m = np.sqrt(y_m**2 - g_m**2)
y_m_cplx = g_m - 1j * b_m
# deal with tap steps (no steps here)
# tap changer
n_tap = 1 + (net.trafo["tap_pos"] - net.trafo["tap_neutral"]) * net.trafo["tap_step_percent"] * 0.01
n_tap[~np.isfinite(n_tap)] = 1.
vn_hv_trafo = np.where(net.trafo["tap_side"] == "hv", net.trafo["vn_hv_kv"] * n_tap, net.trafo["vn_hv_kv"])
vn_lv_trafo = np.where(net.trafo["tap_side"] == "lv", net.trafo["vn_lv_kv"] * n_tap, net.trafo["vn_lv_kv"])
# convert to pu
V_n = vn_lv_trafo
S_n = net.sn_mva
Z_n = V_n**2 / S_n
Z_ref_trafo = V_n**2 * S_n / net.trafo["sn_mva"]
z_cplx = z_k_cplx * Z_ref_trafo / Z_n
y_cplx = y_m_cplx * Z_n / Z_ref_trafo
r_T = z_cplx.values.real # changed these values to ..._T as they are calculated based on the T-equivalent values
x_T = z_cplx.values.imag
g_T = y_cplx.values.real
b_T = y_cplx.values.imag
y_cplx_pp = y_cplx
# output of wye_delta is pi equivalent values: Y->D => T->pi
r_pi, x_pi, g_pi, b_pi = _wye_delta_new(z_cplx.values.real, z_cplx.values.imag, y_cplx_pp.values.real, y_cplx_pp.values.imag)
# extract values computed by pandapower
if pp_ver_3:
r_comp = net._ppc["branch"][-net.trafo.shape[0]:, BR_R]
x_comp = net._ppc["branch"][-net.trafo.shape[0]:, BR_X]
g_comp = net._ppc["branch"][-net.trafo.shape[0]:, BR_G]
b_comp = net._ppc["branch"][-net.trafo.shape[0]:, BR_B]
else:
r_comp = net._ppc["branch"][-net.trafo.shape[0]:, BR_R].real
x_comp = net._ppc["branch"][-net.trafo.shape[0]:, BR_X].real
g_comp = -net._ppc["branch"][-net.trafo.shape[0]:, BR_B].imag # in pp<3.0, BR_B was b-1j*g
b_comp = net._ppc["branch"][-net.trafo.shape[0]:, BR_B].real
# extract values computed by lightsim2grid
r_ls = np.array([el.r_pu for el in gridmodel.get_trafos()])
x_ls = np.array([el.x_pu for el in gridmodel.get_trafos()])
h_ls = np.array([el.h_pu for el in gridmodel.get_trafos()])
g_ls = h_ls.real
b_ls = h_ls.imag
tr_id = 7
print(f"r (computed) = {r_comp[tr_id]}")
print(f"x (computed) = {x_comp[tr_id]}")
print(f"g (computed) = {g_comp[tr_id]}")
print(f"b (computed) = {b_comp[tr_id]}")
print("----------------")
print(f"r (from doc) = {r_T[tr_id]}")
print(f"x (from doc) = {x_T[tr_id]}")
print(f"g (from doc) = {g_T[tr_id]}")
print(f"b (from doc) = {b_T[tr_id]}")
print("----------------")
print(f"r (from lightsim) = {r_ls[tr_id]}")
print(f"x (from lightsim) = {x_ls[tr_id]}")
print(f"g (from lightsim) = {g_ls[tr_id]}")
print(f"b (from lightsim) = {b_ls[tr_id]}")
print("----------------")
print(f"Mismatch with the doc (z): {(np.abs(r_comp - r_T) + np.abs(x_comp - x_T)).max()}")
print(f"Mismatch with the doc (y): {(np.abs(g_T + b_comp) + np.abs(b_T - g_comp)).max()}")
print(f"Mismatch with the doc corrected (y): {(np.sqrt(np.square(g_T) + np.square(b_T)) - np.sqrt(np.square(g_comp) + np.square(b_comp))).max()}")
print("----------------")
print(f"Mismatch lightsim (z): {(np.abs(r_ls - r_comp) + np.abs(x_ls - x_comp)).max()}")
print(f"Mismatch with lightsim (y): {(np.abs(g_ls + b_comp) + np.abs(b_ls - g_comp)).max()}")
print(f"Mismatch with lightsim corrected (y): {(np.sqrt(np.square(g_ls) + np.square(b_ls)) - np.sqrt(np.square(g_comp) + np.square(b_comp))).max()}")
print(f"Mismatch with lightsim (g): {(np.abs(g_ls - g_comp)).max()}")
print(f"Mismatch with lightsim (b): {(np.abs(b_ls - b_comp)).max()}")
branch = net._ppc['branch']
if pp_ver_3:
branch_df = pd.DataFrame(branch, columns=["F_BUS", "T_BUS", "BR_R", "BR_X", "BR_B", "RATE_A", "RATE_B", "RATE_C",
"TAP", "SHIFT", "BR_STATUS", "ANGMIN", "ANGMAX", "PF", "QF", "PT", "QT", "MU_SF", "MU_ST", "MU_ANGMIN",
"MU_ANGMAX", "BR_R_ASYM", "BR_X_ASYM", "BR_G", "BR_G_ASYM", "BR_B_ASYM"])
branch_df.to_csv('ppc_branch_pp3.csv')
else:
branch_df = pd.DataFrame(branch, columns=["F_BUS", "T_BUS", "BR_R", "BR_X", "BR_B", "RATE_A", "RATE_B", "RATE_C",
"TAP", "SHIFT", "BR_STATUS", "ANGMIN", "ANGMAX", "PF", "QF", "PT", "QT",
"MU_SF", "MU_ST", "MU_ANGMIN", "MU_ANGMAX", "BR_R_ASYM", "BR_X_ASYM"])
branch_df.to_csv('ppc_branch_pp2.csv')
# print("--- describe trafos ---")
# print("Trafo vk")
# print(net.trafo.vk_percent.describe())
# print("Trafo vkr")
# print(net.trafo.vkr_percent.describe())
# print("Trafo pfe")
# print(net.trafo.pfe_kw.describe())
# print("Trafo i0")
# print(net.trafo.i0_percent.describe())
# print("--- describe lines ---")
# print("Line R")
# print((net.line.r_ohm_per_km * net.line.length_km).describe())
# print("Line X")
# print((net.line.x_ohm_per_km * net.line.length_km).describe())
# print("Line C")
# print((net.line.c_nf_per_km * net.line.length_km).describe())
print("----------------")
print(f"pandapower {pp.__version__} vm_pu describe")
print(net.res_bus.vm_pu.describe())
print(f"pandapower {pp.__version__} va_degree describe")
print(net.res_bus.va_degree.describe())
print("----------------")
print("Comparison ls2g ac_pf() and pandapower runpp")
V_ls = gridmodel.ac_pf(np.array([1.0]*len(net.bus)), 15, 1e-8)
Vm_ls = np.abs(V_ls)
Va_ls = np.angle(V_ls)
print(f"Vm max mismatch ls2g {lightsim2grid.__version__} vs. pandapower {pp.__version__}")
print((net.res_bus.vm_pu.values - Vm_ls.T).max())
print(f"Va max mismatch ls2g {lightsim2grid.__version__} vs. pandapower {pp.__version__}")
print((net.res_bus.va_degree.values - Va_ls.T).max())
# voltage angles are not matching between ls2g and pp?
branch_df_3 = pd.read_csv('ppc_branch_pp3.csv', index_col=0)
branch_df_2 = pd.read_csv('ppc_branch_pp2.csv', index_col=0)
BR_B3 = branch_df_3['BR_B'].values
BR_G3 = branch_df_3['BR_G'].values
BR_B2 = np.array(branch_df_2['BR_B'].values, dtype=np.complex128).real
BR_G2 = -np.array(branch_df_2['BR_B'].values.imag, dtype=np.complex128).imag
print(BR_B3[-len(net.trafo):]-BR_B2[-len(net.trafo):])
print(BR_G3[-len(net.trafo):]-BR_G2[-len(net.trafo):]) I also changed the inputs and outputs of the |
Thank you :-)
For the voltage magnitude I think you are absolutely right. This looks like numerical issues which we cannot control. Perfect :-) For the voltage angle I would assume it's because either 1) the reference slack is difference or 2) the voltage angle at the slack is different (and it can be both 1 and 2)
Thank you very much :-) The difference might be between the T model and Pi model maybe ?
If input coefficients are the same then lightsim2grid will lead to the same results. But if input coefficients (in the tables) are different then for sure lightsim2grid will lead to something different. And you are correct ls2g has an internal modelling. Everything (for branch) is converted to a Pi equivalent model which is then used to computed the admittance matrix and compute the powerflow. From my understanding here, you change the i0_percent so it's expected that lightsim2grid computes different values. There is a function in lightsim2grid that can be use to compute the KCL mismatch (for each bus) if you want. Let me know if it can help you debug (it works by initializing a ls2g |
Bug report checklis
Searched the issues page for similar reports
Read the relevant sections of the documentation
Browse the tutorials and tests for usefull code snippets and examples of use
Reproduced the issue after updating with
pip install --upgrade pandapower
(orgit pull
)Tried basic troubleshooting (if a bug/error) like restarting the interpreter and checking the pythonpath
Reproducible Example
Issue Description and Traceback
Here What I am doing:
I create two virtual environment with exactly the same packages except for pandapower. In one of the venv pandapower is 2.14.11 and in the other it's 3.0.0, exact details here:
requirements_pp2.txt
requirements_pp3.txt
Then I run the above code which will:
and then I compare 6 things:
a) the "z" parameters of the trafo model in the doc (implementation in the script)
b) the "z" parameters of the trafo model, as computed directly by pandapower (looking at the _ppc["branch"])
c) the "z" parameters of the trafo model as computed by lightsim2grid
d->f) I do the same for the y parameter of the trafo model.
Expected Behavior
I am expecting that:
I. documentation (especially of trafo, see here https://pandapower.readthedocs.io/en/latest/elements/trafo.html) details what is implemented in the code. This is not the case for both pandapower 2 and pandapower 3.
II. the computed parameters for ieee grids does not change between pandapower versions.
For point I
I got, for pandapower 2:
Mismatch with the doc (z): 0.002986873550935059
Mismatch with the doc (y): 0.8196779804309017
This means that the maximum different for (my own) understanding of the doc leads to a maximum difference value of 0.003 for the z component and 0.8 for the y component (everything is per unit here)
And for pandapower 3:
Mismatch with the doc (z): 0.0017865851613352059
Mismatch with the doc (y): 0.7969200083782807
For the y part, I think the doc is missing a "multiplication by 100." somewhere, but I don't really know where. And also I cannot manage (despite some deep understanding of powerflow) to match the exact behaviour of pandapower by reading the doc (without reverse engineering the code)
For point II
I use the implementation of lightsim2grid to compare the trafo parameters obtained by pandapower 2 and pandapower 3. I do this because 1) lightsim2grid fully match pandapower 2 up to numerical precision, se bellow) and 2) lightsim2grid version is the same on both virtual envs (not affected by the change in pandapower).
For pandapower 2 I obtain:
Mismatch lightsim (z): 6.938893903907228e-18
Mismatch with lightsim (y): 2.2215302514227986e-16
So as you can see, errors are neglectible and pandapower and lightsim2grid are perfectly aligned and understand the same way the "ieee grid".
For pandapower 3 I get:
Mismatch lightsim (z): 0.0012002883895998497
Mismatch with lightsim (y): 1.6165979888091822
And here you see quite drastic changes, especially in the "y" part which lead to some impactful changes in the output flow and voltages.
Despite taking few hours (on multiple days) to try to "fix" this, i did not manage to "align" pandapower 2 and pandapower 3. This might be related to issue #2532
Installed Versions
With two changes (because I compare 2 venvs):
Label
The text was updated successfully, but these errors were encountered: