This tutorial gives a quick overview of the grid equivalents function. This script gives an example to reduce a grid area using the IEEE case9 grid.
The user can determine the grid area of interest (internal area), the grid area for the reduction (external area), and the boundary buses between them. The figure below schows an example of the grid with identified grid areas:
In order to identify the user-defined area, the boundary buses and one of the internal buses must be provided as inputs.
The most important inputs for grid equivalents are:
In the following, the reduction of the external grid (buses [1, 2, 5, 6, 7] of the grid case 9) in the figure above is shown. First, the necessary libraries need to be imported.
import matplotlib.pyplot as plt
import pandapower as pp
import pandapower.plotting as plotting
import pandapower.networks
import pandapower.grid_equivalents
Subsequently, the IEEE case 9 grid model is created, and its power flow is calculated.
net = pp.networks.case9()
net.sn_mva = 1.0
pp.runpp(net)
net
This pandapower network includes the following parameter tables: - bus (9 elements) - load (3 elements) - gen (2 elements) - ext_grid (1 element) - line (9 elements) - poly_cost (3 elements) and the following results tables: - res_bus (9 elements) - res_line (9 elements) - res_ext_grid (1 element) - res_load (3 elements) - res_gen (2 elements)
# equivalent type
eq_type = "rei"
# boundary buses
boundary_buses = [4, 8]
# internal buses
internal_buses = [0]
Next, the equivalent function is called, and the grid reduction is executed.
net_eq = pp.grid_equivalents.get_equivalent(net, eq_type, boundary_buses, internal_buses)
Now, the grid equivalent is obtained: "net_eq" is the REI-equivalent grid model. We can compare the power flow results between the original grid "net" and the reduced grid "net_eq".
print("--- power flow (original grid) ---")
display(net.res_bus)
print("--- power flow (reduced grid) ---")
display(net_eq.res_bus)
--- power flow (original grid) ---
vm_pu | va_degree | p_mw | q_mvar | |
---|---|---|---|---|
0 | 1.000000 | 0.000000 | -71.954702 | -24.068958 |
1 | 1.000000 | 9.668741 | -163.000000 | -14.460120 |
2 | 1.000000 | 4.771073 | -85.000000 | 3.649026 |
3 | 0.987007 | -2.406644 | 0.000000 | 0.000000 |
4 | 0.975472 | -4.017264 | 90.000000 | 30.000000 |
5 | 1.003375 | 1.925602 | 0.000000 | 0.000000 |
6 | 0.985645 | 0.621545 | 100.000000 | 35.000000 |
7 | 0.996185 | 3.799120 | 0.000000 | 0.000000 |
8 | 0.957621 | -4.349934 | 125.000000 | 50.000000 |
--- power flow (reduced grid) ---
vm_pu | va_degree | p_mw | q_mvar | |
---|---|---|---|---|
0 | 1.000000 | 0.000000 | -71.954702 | -24.068958 |
3 | 0.987007 | -2.406644 | 0.000000 | 0.000000 |
4 | 0.975472 | -4.017264 | 89.283896 | 8.224645 |
8 | 0.957621 | -4.349934 | 124.581896 | 32.419383 |
9 | 0.985645 | 0.621545 | 99.317421 | 0.635364 |
10 | 1.000000 | 9.668741 | -162.192474 | -24.665476 |
11 | 1.000000 | 4.771073 | -83.866102 | -11.311074 |
It can be seen that the power flow results (vm_pu, va_degree) of the internal buses [0, 3, 4, 8] in both grids are the same , i.e., the equivalent calculation is successful. The p_mw and q_mvar values at the boundary buses [4, 8] are different due to the equivalent devices. During the grid reduction, additional equivalent devices (e.g., additional buses [9, 10, 11], impedance, shunts, etc.) are created, representing the interaction between the internal area and the external area, and maintaining the grid states in the internal area. We can see the additional shunts in the grid equivalent:
print("--- shunts (original grid) ---")
display(net.shunt)
print("--- shunts (reduced grid) ---")
display(net_eq.shunt)
--- shunts (original grid) ---
bus | name | q_mvar | p_mw | vn_kv | step | max_step | in_service |
---|
--- shunts (reduced grid) ---
bus | name | q_mvar | p_mw | vn_kv | step | max_step | in_service | |
---|---|---|---|---|---|---|---|---|
1 | 4 | eq_shunt | -22.884186 | -0.752570 | 345.0 | 1 | 1 | True |
2 | 8 | eq_shunt | -19.171087 | -0.455928 | 345.0 | 1 | 1 | True |
3 | 9 | eq_shunt | -35.372912 | -0.702606 | 345.0 | 1 | 1 | True |
4 | 10 | eq_shunt | -10.205356 | 0.807526 | 345.0 | 1 | 1 | True |
5 | 11 | eq_shunt | -14.960100 | 1.133898 | 345.0 | 1 | 1 | True |
In the following, we demonstrate an example for the (x)ward-equivalent. We change the equivalent type and repeat the equivalent calculation.
# equivalent type
eq_type = "ward" # for xward-equivalent: eq_type = "xward"
# run equivalent calculation
net_eq_ward = pp.grid_equivalents.get_equivalent(net, eq_type, boundary_buses,
internal_buses)
Now, the ward equivalent is obtained. We can verify the power flow results.
print("--- power flow (original grid) ---")
display(net.res_bus)
print("--- power flow (reduced grid) ---")
display(net_eq_ward.res_bus)
--- power flow (original grid) ---
vm_pu | va_degree | p_mw | q_mvar | |
---|---|---|---|---|
0 | 1.000000 | 0.000000 | -71.954702 | -24.068958 |
1 | 1.000000 | 9.668741 | -163.000000 | -14.460120 |
2 | 1.000000 | 4.771073 | -85.000000 | 3.649026 |
3 | 0.987007 | -2.406644 | 0.000000 | 0.000000 |
4 | 0.975472 | -4.017264 | 90.000000 | 30.000000 |
5 | 1.003375 | 1.925602 | 0.000000 | 0.000000 |
6 | 0.985645 | 0.621545 | 100.000000 | 35.000000 |
7 | 0.996185 | 3.799120 | 0.000000 | 0.000000 |
8 | 0.957621 | -4.349934 | 125.000000 | 50.000000 |
--- power flow (reduced grid) ---
vm_pu | va_degree | p_mw | q_mvar | |
---|---|---|---|---|
0 | 1.000000 | 0.000000 | -71.954702 | -24.068958 |
3 | 0.987007 | -2.406644 | 0.000000 | 0.000000 |
4 | 0.975472 | -4.017264 | 28.820879 | 10.232062 |
8 | 0.957621 | -4.349934 | 42.681861 | 39.100723 |
The power flow results (vm_pu, va_degree) for the internal buses [0, 3, 4, 8] in both grids are the same. The external area is represented by the addtional ward elements in the grid model "net_eq_ward", attached at the boundary buses [4, 8]:
print("--- ward (original grid) ---")
display(net.ward)
print("--- ward (reduced grid) ---")
display(net_eq_ward.ward)
--- ward (original grid) ---
name | bus | ps_mw | qs_mvar | qz_mvar | pz_mw | in_service |
---|
--- ward (reduced grid) ---
name | bus | ps_mw | qs_mvar | qz_mvar | pz_mw | in_service | |
---|---|---|---|---|---|---|---|
0 | network_equivalent | 4 | -61.258111 | 34.553823 | -57.087899 | 0.083012 | True |
1 | network_equivalent | 8 | -83.211209 | 41.300941 | -56.922631 | 0.973863 | True |
For this example we will use the lines between the buses 4 ... 5 and 7 ... 8
With the function set_bus_zone_by_boundary_branches we first define the zones of the grid based on this separation. Thereafter, with the function get_boundaries_by_bus_zone_with_boundary_branches, we obtain the boundary buses that we can use with the functions for obtaining the grid equivalents, as showed in previous sections.
boundary_branches = {"line": [2, 7]}
pp.grid_equivalents.set_bus_zone_by_boundary_branches(net, boundary_branches)
buses, branches = pp.grid_equivalents.get_boundaries_by_bus_zone_with_boundary_branches(net)
The dictionary buses contains the biundary buses, internal buses and external buses from the point of view of each of the zones.
buses
{0: {'all': {4, 5, 7, 8}, 'internal': {4, 8}, 'external': {5, 7}, 1: {5, 7}}, 1: {'all': {4, 5, 7, 8}, 'internal': {5, 7}, 'external': {4, 8}, 0: {4, 8}}, 'all': {4, 5, 7, 8}}
The resulting separation can be seen in the figure below. The green buses are the nodes of the boundary lines that are part of the internal zone. The red buses are the nodes of the boundary lines that are in the external zone. The red dashed lines are the boundary lines.
zone = 1
#plotting.generic_geodata.create_generic_coordinates(net, overwrite=True)
plotting.plotting_toolbox.set_line_geodata_from_bus_geodata(net, overwrite=True)
import geopandas as gpd
import geojson
fig, ax=plt.subplots(figsize=(7,11))
net.bus.geo = net.bus.geo.apply(lambda x: geojson.loads(x))
net.bus = gpd.GeoDataFrame(net.bus, geometry='geo')
net.line.geo = net.line.geo.apply(lambda x: geojson.loads(x))
net.line = gpd.GeoDataFrame(net.line, geometry='geo')
net.line.plot(ax=ax)
net.line.loc[list(boundary_branches["line"])].plot(ax=ax, color='red', linestyle="--")
net.bus.plot(column=net.bus.zone.values, ax=ax,zorder=10)
net.bus.loc[net.ext_grid.bus.values].plot(ax=ax, color='k', marker="s", markersize=200)
net.bus.loc[list(buses[zone]["external"])].plot(color='r', ax=ax, zorder=100, markersize=100)
net.bus.loc[list(buses[zone]["internal"])].plot(color='g', ax=ax, zorder=100, markersize=100)
<Axes: >
Now, we can obtain the grid equivalent to reduce the zone 0
b_internal = [net.bus.loc[net.bus.zone==1].index[0]]
net_eq = pp.grid_equivalents.get_equivalent(net, "rei", buses[1]["internal"], b_internal)
net_eq.bus
name | vn_kv | type | zone | in_service | max_vm_pu | min_vm_pu | geo | |
---|---|---|---|---|---|---|---|---|
0 | 1 | 345.0 | b | 0 | True | 1.1 | 0.9 | POINT (-2.91341 -0.64387) |
1 | 2 | 345.0 | b | 1 | True | 1.1 | 0.9 | POINT (0.93699 -0.11737) |
5 | 6 | 345.0 | b | 1 | True | 1.1 | 0.9 | POINT (-1.29440 1.87189) |
7 | 8 | 345.0 | b | 1 | True | 1.1 | 0.9 | POINT (-0.06390 0.28582) |
8 | load_integrated-total 4/6/8 | 345.0 | b | None | True | 1.1 | 0.9 | None |
9 | gen_separate-total 2 | 345.0 | b | NaN | True | NaN | NaN | None |
pp.drop_lines(net, [2])
pp.create_dcline(net, 4, 5, 50, 0.5, 0.7, 1.0, 1.0)
pp.runpp(net)
net_eq = pp.grid_equivalents.get_equivalent(net, "rei", [4,8], [0])
net_eq.bus
name | vn_kv | type | zone | in_service | max_vm_pu | min_vm_pu | geo | |
---|---|---|---|---|---|---|---|---|
0 | 1 | 345.0 | b | 0 | True | 1.1 | 0.9 | POINT (-2.91341 -0.64387) |
3 | 4 | 345.0 | b | 0 | True | 1.1 | 0.9 | POINT (-2.05494 0.00979) |
4 | 5 | 345.0 | b | 0 | True | 1.1 | 0.9 | POINT (-2.17891 1.14892) |
8 | 9 | 345.0 | b | 0 | True | 1.1 | 0.9 | POINT (-0.98302 -0.39409) |
9 | load_integrated-total 6 | 345.0 | b | None | True | 1.1 | 0.9 | None |
10 | gen_separate-total 1 | 345.0 | b | NaN | True | NaN | NaN | None |
11 | gen_separate-total 2 | 345.0 | b | NaN | True | NaN | NaN | None |
12 | gen_separate-total 5 | 345.0 | b | NaN | True | NaN | NaN | None |