PandaPower conversion
This example illustrates conversion from PandaPower to power-grid-model input data. We can then calculate power-flow with it or convert to a different formats like PGM JSON.
NOTE: To run this example, the optional examples
dependencies are required:
pip install .[examples]
1. Load the PandaPower Data
For this example we will construct a minimal pandapower network.
(ext_grid #1) shunt - [104] - trafo_3w - [105] - (sym_gen + asym_gen + asym_load + ward + motor)
| |
[101] ---trafo- [102] ------------- [103]
| |
-/- (load #31)
|
[106]
import warnings
import pandapower as pp
warnings.filterwarnings("ignore", module="pandapower", category=FutureWarning) # Hide warnings related to pandas
def pandapower_simple_grid():
net = pp.create_empty_network(f_hz=50)
pp.create_bus(net, index=101, vn_kv=110)
pp.create_bus(net, index=102, vn_kv=20)
pp.create_bus(net, index=103, vn_kv=20)
pp.create_bus(net, index=104, vn_kv=30.1)
pp.create_bus(net, index=105, vn_kv=60)
pp.create_bus(net, index=106, vn_kv=110)
pp.create_ext_grid(net, index=1, in_service=True, bus=101, vm_pu=1, s_sc_max_mva=1e10, rx_max=0, va_degree=0)
pp.create_transformer_from_parameters(net,index=101,hv_bus=101,lv_bus=102,i0_percent=3.0,pfe_kw=11.6,vkr_percent=10.22,sn_mva=40,vn_lv_kv=20.0,vn_hv_kv=110.0,vk_percent=17.8,vector_group="Dyn",shift_degree=30,tap_side="hv",tap_pos=2,tap_min=-1,tap_max=3,tap_step_percent=2,tap_neutral=1,parallel=2,)
pp.create_line(net, index=101, from_bus=103, to_bus=102, length_km=1.23, parallel=2, df=0.2, std_type="NAYY 4x150 SE")
pp.create_load(net, index=101, bus=103, p_mw=2.5, q_mvar=0.24, const_i_percent=26.0, const_z_percent=51.0, cos_phi=2)
pp.create_switch(net, index=101, et="l", bus=103, element=101, closed=True)
pp.create_switch(net, index=3021, et="b", bus=101, element=106, closed=True)
pp.create_switch(net, index=321, et="t", bus=101, element=101, closed=True)
pp.create_shunt(net, index=1201, in_service=True, bus=104, p_mw=0.1, q_mvar=0.55, step=3)
pp.create_sgen(net, index=31, bus=105, p_mw=1.21, q_mvar=0.81)
pp.create_asymmetric_sgen(net, index=32, bus=105, p_a_mw=0.1, p_b_mw=0.2, p_c_mw=3, q_a_mvar=0.01, q_b_mvar=0.01, q_c_mvar=0.01)
pp.create_asymmetric_load(net, index=33, bus=105, p_a_mw=0.1, p_b_mw=0.2, p_c_mw=3, q_a_mvar=0.01, q_b_mvar=0.01, q_c_mvar=0.01)
pp.create_ward(net, index=34, bus=105, ps_mw=0.1, qs_mvar=0.1, pz_mw=0.1, qz_mvar=0.1)
pp.create_motor(net, bus=105, index=12, pn_mech_mw=0.1, cos_phi=0.9, loading_percent=80, efficiency_percent=90, scaling=0.8)
pp.create_transformer3w_from_parameters(net,index=102,hv_bus=103,mv_bus=105,lv_bus=104,in_service=True,vn_hv_kv=20.0,vn_mv_kv=60.0,vn_lv_kv=30.1,sn_hv_mva=40,sn_mv_mva=100,sn_lv_mva=50,vk_hv_percent=10,vk_mv_percent=11,vk_lv_percent=12,vkr_hv_percent=1,vkr_mv_percent=2,vkr_lv_percent=4,i0_percent=0.1,pfe_kw=10,vector_group="Dyny",shift_mv_degree=30,shift_lv_degree=30,tap_side="lv",tap_pos=2,tap_min=1,tap_max=3,tap_step_percent=3,tap_neutral=2)
return net
Instantiate the converter. The converter assumes that all the parameters (eg. r_ohm_per_km
) are already present in the respective component dataframes. If they are not present but a std_type
is mentioned, then it is recommended that the user refers pandapower.add_zero_impedance_parameters()
or pandapower.load_std_type()
to include those parameters to the pandapower net.
Then use load_input_data()
to load the data and convert it to power-grid-model data.
The additional information that is not used in the powerflow calculation but may be useful to link the results to the source data is stored in extra_info
.
%%capture cap --no-stderr
from power_grid_model_io.converters import PandaPowerConverter
pp_net = pandapower_simple_grid()
converter = PandaPowerConverter()
input_data, extra_info = converter.load_input_data(pp_net)
Let’s investigate the data we have converted, for one of the components: lines
import pandas as pd
pd.set_option('future.no_silent_downcasting', True) # enable behaviour of pandas 3.x
# The node data is stored as a numpy structured array in input_data["line"]
display(input_data["line"])
# We can use pandas to display the data in a convenient tabular format
# display(pd.DataFrame(input_data["line"]))
# The original indices are stored in the extra_data dictionary
display({i: extra_info[i] for i in input_data["line"]["id"]})
array([(6, 2, 1, 1, 1, 0.12792, 0.0492, 6.4206e-07, 0., nan, nan, nan, nan, 108.)],
dtype={'names': ['id', 'from_node', 'to_node', 'from_status', 'to_status', 'r1', 'x1', 'c1', 'tan1', 'r0', 'x0', 'c0', 'tan0', 'i_n'], 'formats': ['<i4', '<i4', '<i4', 'i1', 'i1', '<f8', '<f8', '<f8', '<f8', '<f8', '<f8', '<f8', '<f8', '<f8'], 'offsets': [0, 4, 8, 12, 13, 16, 24, 32, 40, 48, 56, 64, 72, 80], 'itemsize': 88, 'aligned': True})
{6: {'id_reference': {'table': 'line', 'index': 101},
'pgm_input': {'from_node': 2, 'to_node': 1, 'i_n': 108.0}}}
2. Validate the data
Before we run a power flow calculation, it is wise validate the data. The most basic method is to use assert_valid_input_data()
, which will raise a ValueError
when the data is invalid. For more details on data validation, please consult the validation Example.
from power_grid_model import CalculationType
from power_grid_model.validation import assert_valid_input_data
assert_valid_input_data(input_data, calculation_type=CalculationType.power_flow, symmetric=True)
3. Run the calculation
Run powerflow calculation with the input_data
and show the results for nodes
.
from power_grid_model import PowerGridModel
pgm = PowerGridModel(input_data=input_data)
output_data = pgm.calculate_power_flow()
display(pd.DataFrame(output_data["node"]))
id | energized | u_pu | u | u_angle | p | q | |
---|---|---|---|---|---|---|---|
0 | 0 | 1 | 1.000000 | 109999.999962 | -1.798666e-10 | 1.798666e+06 | 3.476629e+06 |
1 | 1 | 1 | 0.973746 | 19474.919873 | -5.239008e-01 | 1.408570e-07 | -3.370807e-07 |
2 | 2 | 1 | 0.973014 | 19460.275746 | -5.237224e-01 | -2.414573e+06 | -2.317990e+05 |
3 | 3 | 1 | 0.969550 | 29183.446690 | -1.045187e+00 | -8.295649e-08 | 1.430200e-07 |
4 | 4 | 1 | 0.971998 | 58319.874965 | -1.044829e+00 | 9.444109e+05 | 5.810813e+05 |
5 | 5 | 1 | 1.000000 | 109999.999962 | -1.798666e-10 | 0.000000e+00 | -0.000000e+00 |
Cross referencing objects
The converter has generated unique numerical IDs for all the components in the pandapower net, in fact for some special components like loads
, multiple PGM components have been created, each with their own numerical ID. To find out which component belongs to which id, some helper functions have been defined:
print("PGM object #4:", converter.lookup_id(4))
print("Trafo with index=101:", converter.get_id("trafo", 101))
PGM object #4: {'table': 'bus', 'index': 105}
Trafo with index=101: 12
Saving the data as a JSON file
The data can be stored in a json file using the PgmJsonConverter. The file will be saved in the destination_file
path supplied in the constructor.
from power_grid_model_io.converters import PgmJsonConverter
input_file = "data/pandapower/example_simple_input.json"
output_file = "data/pandapower/example_simple_output.json"
PgmJsonConverter(destination_file=input_file).save(data=input_data, extra_info=extra_info)
PgmJsonConverter(destination_file=output_file).save(data=output_data, extra_info=extra_info)
4. Converting output data
Before we convert the output data, lets run the powerflow in pandapower so we can compare results for demostration purpose
pp.runpp(pp_net, trafo_model='pi', trafo_loading='power', calculate_voltage_angles=True, numba=False)
display(pp_net.res_bus)
vm_pu | va_degree | p_mw | q_mvar | |
---|---|---|---|---|
101 | 1.000000 | 0.000000 | -1.798661 | -3.476701 |
102 | 0.973746 | -30.017299 | 0.000000 | 0.000000 |
103 | 0.973014 | -30.007075 | 2.414572 | 0.231799 |
104 | 0.969519 | -59.886045 | 0.281990 | 1.550945 |
105 | 0.972063 | -59.860441 | -0.944398 | -0.581069 |
106 | 1.000000 | 0.000000 | 0.000000 | 0.000000 |
To get the results of powerflow in the pandapower net, convert the result from power-grid-model powerflow ie. output_data
from previous section to the pandapower res_*
dataframes.
converted_output_data = converter.convert(output_data)
display(converted_output_data["res_bus"])
vm_pu | va_degree | p_mw | q_mvar | |
---|---|---|---|---|
101 | 1.000000 | -1.030560e-08 | -1.798666e+00 | -3.476629e+00 |
102 | 0.973746 | -3.001731e+01 | 3.026798e-14 | 3.019813e-13 |
103 | 0.973014 | -3.000708e+01 | 2.414573e+00 | 2.317990e-01 |
104 | 0.969550 | -5.988481e+01 | 2.820080e-01 | 1.551044e+00 |
105 | 0.971998 | -5.986427e+01 | -9.444109e-01 | -5.810813e-01 |
106 | 1.000000 | -1.030560e-08 | 0.000000e+00 | 0.000000e+00 |
Thus we can see that the results of powerflow match. We can then replace the dataframes of results in the pandapower net.
for table in converted_output_data.keys():
pp_net[table] = converted_output_data[table]
5. Asymmetrical Calculations
For simulating the asymmetric calculation, we shall use the same grid as used in unbalanced_minimal.ipynb
tutorial of pandapower.
def pandapower_simple_asym_grid():
net = pp.create_empty_network()
b1 = pp.create_bus(net, 20.0)
b2 = pp.create_bus(net, 0.4)
b3 = pp.create_bus(net, 0.4)
pp.create_ext_grid(net, b1, s_sc_max_mva=1000, rx_max=0.1, x0x_max=1.0,
r0x0_max=0.1)
pp.create_transformer_from_parameters(net, b1, b2, sn_mva=0.63,
vn_hv_kv=20., vn_lv_kv=0.4,
vkr_percent=0.1, vk_percent=6,
vk0_percent=6, vkr0_percent=0.78125,
mag0_percent=100, mag0_rx=0.,
pfe_kw=0.1, i0_percent=0.1,
vector_group="Dyn", shift_degree=150,
si0_hv_partial=0.9)
pp.create_line_from_parameters(net, b2, b3, length_km=0.1, r0_ohm_per_km=0.0848,
x0_ohm_per_km=0.4649556, c0_nf_per_km=230.6,
max_i_ka=0.963, r_ohm_per_km=0.0212,
x_ohm_per_km=0.1162389, c_nf_per_km= 230)
pp.create_asymmetric_load(net, b3, p_a_mw=0.25, p_b_mw=0.18, p_c_mw=0.20, type="wye")
return net
Convert to get input data. Run asymmetric powerflow calculation similarly. Then convert the asymmetric PGM output data:
pp_net_3ph = pandapower_simple_asym_grid()
asym_input_data, asym_extra_info = converter.load_input_data(pp_net_3ph)
asym_pgm = PowerGridModel(input_data=asym_input_data)
asym_output_data = asym_pgm.calculate_power_flow(symmetric=False)
converted_asym_output_data = converter.convert(asym_output_data)
2024-02-07 12:15:14 [warning ] Zero sequence parameters given in trafo shall be ignored:vkr0_percent, mag0_percent, mag0_rx, si0_hv_partial
Add the keys to pandapower net if required
for table in converted_asym_output_data.keys():
pp_net_3ph[table] = converted_asym_output_data[table]
Summary
%%capture cap --no-stderr
from power_grid_model import PowerGridModel, CalculationType
from power_grid_model.validation import assert_valid_input_data
from power_grid_model_io.converters import PandaPowerConverter
output_file = "data/pandapower/example_simple_output.json"
pp_net = pandapower_simple_grid()
converter = PandaPowerConverter()
input_data, extra_info = converter.load_input_data(pp_net)
assert_valid_input_data(input_data, calculation_type=CalculationType.power_flow, symmetric=True)
pgm = PowerGridModel(input_data=input_data)
output_data = pgm.calculate_power_flow()
json_converter = PgmJsonConverter(destination_file=output_file)
json_converter.save(data=output_data, extra_info=extra_info)
converted_output_data = converter.convert(output_data)
for table in converted_output_data.keys():
pp_net[table] = converted_output_data[table]