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
from power_grid_model import AttributeType, ComponentType

pd.options.future.no_silent_downcasting = True  # enable behaviour of pandas 3.x

# The node data is stored as a numpy structured array in input_data[ComponentType.line]
display(input_data[ComponentType.line])

# We can use pandas to display the data in a convenient tabular format
# display(pd.DataFrame(input_data[ComponentType.line]))

# The original indices are stored in the extra_data dictionary
display({i: extra_info[i] for i in input_data[ComponentType.line][AttributeType.id]})
array([(6, 2, 1, 1, 1, 0.12792, 0.0492, 6.4206e-07, 0., nan, nan, 0., 0., 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})
{np.int32(6): {'id_reference': {'table': 'line', 'index': 101},
  'pgm_input': {from_node: np.int32(2),
   to_node: np.int32(1),
   i_n: np.float64(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[ComponentType.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 5.817753e-07 -1.649725e-08
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 2.765216e-08 -4.767335e-08
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': np.int64(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.877113 -3.640600
102 0.993171 -30.016181 0.000000 0.000000
103 0.992419 -30.005563 2.475814 0.237678
104 0.988794 -59.887701 0.293314 1.613227
105 0.991372 -59.864589 -0.940607 -0.577278
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 -2.926681e-13 -5.341135e-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:
    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.0,
        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.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)
2026-03-27 16:55:28 [warning  ] Zero sequence parameters given in trafo shall be ignored: vkr0_percent, si0_hv_partial

Add the keys to pandapower net if required

for table in converted_asym_output_data:
    pp_net_3ph[table] = converted_asym_output_data[table]

Summary

%%capture cap --no-stderr

from power_grid_model import CalculationType, PowerGridModel
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:
    pp_net[table] = converted_output_data[table]