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 i.e., 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]