e2nIEE / pandapower

Convenient Power System Modelling and Analysis based on PYPOWER and pandas
https://www.pandapower.org
Other
864 stars 482 forks source link

Understanding the bus indices in the json format #2143

Open SanPen opened 1 year ago

SanPen commented 1 year ago

Hi folks,

I'm writing a GridCal backend for Grid2Op and I need to read your json format (this file particularly)

In the file, the line and transformer indices don't seem to match the bus indices or the bus names properly.

The function I'm using is this:


def read_pandapower_file(filename: str) -> MultiCircuit:
    """
    Parse pandapower json file into a GridCal MultiCircuit object
    :param filename: file path
    :return: MultiCircuit object
    """
    grid = MultiCircuit()

    with open(filename) as f:
        data = json.load(f)
        data2 = data['_object']

        if type(data2) == str:  # because madness
            data2 = json.loads(data2)

    # buses
    bus_dict_by_name = dict()
    bus_dict_by_index = dict()
    for i, row in decode_panda_structre(obj=data2['bus']).iterrows():

        name = 'sub_{}'.format(i)
        vmin = row['min_vm_pu'] if 'min_vm_pu' in row else 0.8 * row['vn_kv']
        vmax = row['max_vm_pu'] if 'max_vm_pu' in row else 1.2 * row['vn_kv']
        bus = dev.Bus(idtag='',
                      code='',
                      name=name,
                      active=bool(row['in_service']),
                      vnom=row['vn_kv'],
                      vmin=vmin,
                      vmax=vmax
                      )
        bus_dict_by_name[name] = bus
        bus_dict_by_index[i] = bus
        grid.add_bus(bus)

    # loads
    for i, row in decode_panda_structre(obj=data2['load']).iterrows():
        bus = bus_dict_by_index[row['bus']]

        name = "load_{0}_{1}".format(row['bus'], i)

        grid.add_load(bus, dev.Load(idtag='',
                                    code='',
                                    name=name,
                                    active=bool(row['in_service']),
                                    P=row['p_mw'] * row['scaling'],
                                    Q=row['q_mvar'] * row['scaling']))

    # shunt
    for i, row in decode_panda_structre(obj=data2['shunt']).iterrows():
        bus = bus_dict_by_index[row['bus']]
        grid.add_shunt(bus, dev.Shunt(idtag='',
                                      code='',
                                      name=str(row['name']),
                                      active=bool(row['in_service']),
                                      G=row['p_mw'],
                                      B=row['q_mvar']))

    # generators
    for i, row in decode_panda_structre(obj=data2['gen']).iterrows():

        bus = bus_dict_by_index[row['bus']]
        name = "gen_{0}_{1}".format(row['bus'], i)

        if row['slack']:
            bus.is_slack = True

        Pmin = row['min_p_mw'] if 'min_p_mw' in row else None
        Pmax = row['max_p_mw'] if 'max_p_mw' in row else None
        Qmin = row['min_q_mvar'] if 'min_q_mvar' in row else None
        Qmax = row['max_q_mvar'] if 'max_q_mvar' in row else None
        if Pmin is None:
            Pmin = -999999.
        if Pmax is None:
            Pmax = 999999.
        if Qmin is None:
            Qmin = -999999.
        if Qmax is None:
            Qmax = 999999.
        grid.add_generator(bus, dev.Generator(idtag='',
                                              code='',
                                              name=name,
                                              active=bool(row['in_service']),
                                              P=row['p_mw'] * row['scaling'],
                                              vset=row['vm_pu'],
                                              Pmin=Pmin,
                                              Pmax=Pmax,
                                              Qmin=Qmin,
                                              Qmax=Qmax, ))

    # lines
    for i, row in decode_panda_structre(obj=data2['line']).iterrows():
        bus_f = bus_dict_by_index[row['from_bus']]
        bus_t = bus_dict_by_index[row['to_bus']]

        # name = 'CryLine {}'.format(i)
        name = f"{row['from_bus']}_{row['to_bus']}_{i}"

        line = dev.Line(idtag='',
                        code='',
                        name=name,
                        active=bool(row['in_service']),
                        bus_from=bus_f,
                        bus_to=bus_t)

        line.fill_design_properties(r_ohm=row['r_ohm_per_km'],
                                    x_ohm=row['x_ohm_per_km'],
                                    c_nf=row['c_nf_per_km'],
                                    Imax=row['max_i_ka'],
                                    freq=data2['f_hz'] if 'f_hz' in data2 else 50.0,
                                    length=row['length_km'],
                                    Sbase=grid.Sbase)
        grid.add_line(line)

    # transformer
    for i, row in decode_panda_structre(obj=data2['trafo']).iterrows():
        bus_f = bus_dict_by_index[row['lv_bus']]
        bus_t = bus_dict_by_index[row['hv_bus']]

        # name = 'Line {}'.format(i)  # transformers are also calles Line apparently
        name = f"{row['hv_bus']}_{row['lv_bus']}_{i}"

        transformer = dev.Transformer2W(idtag='',
                                        code='',
                                        name=name,
                                        active=bool(row['in_service']),
                                        bus_from=bus_f,
                                        bus_to=bus_t,
                                        HV=row['vn_hv_kv'],
                                        LV=row['vn_lv_kv'],
                                        rate=row['sn_mva'])

        transformer.fill_design_properties(Pcu=0.0,  # pandapower has no pcu apparently
                                           Pfe=row['pfe_kw'],
                                           I0=row['i0_percent'],
                                           Vsc=row['vk_percent'],
                                           Sbase=grid.Sbase)

        grid.add_transformer2w(transformer)

    return grid

When applied, the computed conductance matrix is:

6.025029055768224 -4.999131600798035 0.0 0.0 0.0 0.0 0.0 0.0 -1.025897454970189 0.0 0.0 0.0
-4.999131600798035 9.521323610814779 0.0 0.0 0.0 0.0 -1.1350191923073958 -1.686033150614943 -1.7011396670944048 0.0 0.0 0.0
0.0 0.0 3.83591331687766 0.0 0.0 0.0 0.0 0.0 0.0 -1.9550285631772604 0.0 0.0
0.0 0.0 0.0 4.014992027272893 -2.4890245868219187 0.0 0.0 0.0 0.0 -1.525967440450974 0.0 0.0
0.0 0.0 0.0 -2.4890245868219187 6.724946148466233 -1.1369941578063267 0.0 0.0 0.0 -3.0989274038379877 0.0 0.0
0.0 0.0 0.0 0.0 -1.1369941578063267 2.560999644826258 0.0 0.0 0.0 0.0 0.0 0.0
0.0 -1.1350191923073958 0.0 0.0 0.0 0.0 3.1209949022329564 -1.9859757099255606 0.0 0.0 0.0 0.0
0.0 -1.686033150614943 0.0 0.0 0.0 0.0 -1.9859757099255606 10.512989522036175 -6.840980661495672 0.0 -2.2866982506648618e-19 0.0
-1.025897454970189 -1.7011396670944048 0.0 0.0 0.0 0.0 0.0 -6.840980661495672 9.568017783560265 -1.5744540324006639e-19 0.0 0.0
0.0 0.0 -1.9550285631772604 -1.525967440450974 -3.0989274038379877 0.0 0.0 0.0 -1.5744540324006639e-19 6.579923407466222 0.0 0.0
0.0 0.0 0.0 0.0 0.0 0.0 0.0 -2.2866982506648618e-19 0.0 0.0 1.3772468653867874e-18 -3.222810018008256e-19
0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -3.222810018008256e-19 3.222810018008256e-19
0.0 0.0 0.0 0.0 0.0 -1.4240054870199312 0.0 -3.232728741636893e-20 0.0 0.0 -8.262960385194756e-19 0.0
0.0 0.0 -1.8808847537003996 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0

However the hugely verified one is this:

BUS 1 BUS 2 BUS 3 BUS 4 BUS 5 BUS 6 BUS 7 BUS 8 BUS 9 BUS 10 BUS 11 BUS 12 BUS 13 BUS 14
BUS 1 6.02502905576822 -4.99913160079803 0 0 -1.02589745497019 0 0 0 0 0 0 0 0 0
BUS 2 -4.99913160079803 9.52132361081478 -1.1350191923074 -1.68603315061494 -1.7011396670944 0 0 0 0 0 0 0 0 0
BUS 3 0 -1.1350191923074 3.12099490223296 -1.98597570992556 0 0 0 0 0 0 0 0 0 0
BUS 4 0 -1.68603315061494 -1.98597570992556 10.5129895220362 -6.84098066149567 0 -2.33813727061847E-19 0 -3.3361493721743E-20 0 0 0 0 0
BUS 5 -1.02589745497019 -1.7011396670944 0 -6.84098066149567 9.56801778356027 -1.68932836094492E-19 0 0 0 0 0 0 0 0
BUS 6 0 0 0 0 -1.68932836094492E-19 6.57992340746622 0 0 0 0 -1.95502856317726 -1.52596744045097 -3.09892740383799 0
BUS 7 0 0 0 -2.33813727061847E-19 0 0 1.37724686538679E-18 -3.22281001800826E-19 -8.26296038519476E-19 0 0 0 0 0
BUS 8 0 0 0 0 0 0 -3.22281001800826E-19 3.22281001800826E-19 0 0 0 0 0 0
BUS 9 0 0 0 -3.3361493721743E-20 0 0 -8.26296038519476E-19 0 5.32605503946736 -3.90204955244743 0 0 0 -1.42400548701993
BUS 10 0 0 0 0 0 0 0 0 -3.90204955244743 5.78293430614783 -1.8808847537004 0 0 0
BUS 11 0 0 0 0 0 -1.95502856317726 0 0 0 -1.8808847537004 3.83591331687766 0 0 0
BUS 12 0 0 0 0 0 -1.52596744045097 0 0 0 0 0 4.01499202727289 -2.48902458682192 0
BUS 13 0 0 0 0 0 -3.09892740383799 0 0 0 0 0 -2.48902458682192 6.72494614846623 -1.13699415780633
BUS 14 0 0 0 0 0 0 0 0 -1.42400548701993 0 0 0 -1.13699415780633 2.56099964482626

Observe the index shuffling.

So my question is, how do you assign the bus indices and what is the relation to those in the loads, generators, etc..

Thanks!

KS-HTK commented 1 year ago

I assume you are attempting to avoid having a dependency on pandapower? If that is not the case it would be far simpler to work with data from the pandas dataframes when they are loaded in pandapower.

The structure in the json matches the structure of the corresponding element in the pandapower documentation. From that you can see how the indeces are used.

From the Documentation of net.line: Parameter Datatype Value Type Explanation
from_bus* integer   Index of bus where the line starts
to_bus* integer   Index of bus where the line ends

the integers stored there are an index of the net.bus dataframe.

For the net.trafo table accordingly there exists: Parameter Datatype Value Range Explanation
hv_bus* integer   high voltage bus index of the transformer
lv_bus* integer   low voltage bus index of the transformer

There is no direct correlation of the index of a line or trafo to a index of a bus.

Indices have nothing to do with the name of a bus. If the bus is named "Bus 0" that does not mean it has index 0. Sorting by name vs sorting by index thus may result in different sequences. Names are for better Identification by the user. They should not be used to match up the data from the different dataframes.