pandapower Interface#
This example (1) shows how to convert an ANDES system (ssa
) to a pandapower network (ssp
), (2) benchmarks the powerflow results, (3) shows how to alter ssa
active power setpoints according to ssp
results.
This interface is mainly developed by Jinning Wang.
import andes
from andes.interop.pandapower import to_pandapower, \
make_link_table, runopp_map, add_gencost
andes.config_logger(20)
import pandapower as pp
import numpy as np
import pandas as pd
from math import pi
Load case#
Here we use the same ANDES system ss0
to do the pandapower conversion, which leaves the ssa
untouched.
This will be useful if you need to modify the ssa
parametes or setpoints.
ssa = andes.load(andes.get_case('ieee14/ieee14_ieeet1.xlsx'),
setup=False,
no_output=True,
default_config=True)
ssa.Toggler.u.v = [0, 0]
ssa.setup()
ss0 = andes.load(andes.get_case('ieee14/ieee14_ieeet1.xlsx'),
setup=False,
no_output=True,
default_config=True)
ss0.Toggler.u.v = [0, 0]
ss0.setup()
Working directory: "/Users/jinningwang/Documents/work/andes/docs/source/examples"
> Loaded generated Python code in "/Users/jinningwang/.andes/pycode".
Parsing input file "/Users/jinningwang/Documents/work/andes/andes/cases/ieee14/ieee14_ieeet1.xlsx"...
Input file parsed in 0.2310 seconds.
System internal structure set up in 0.0481 seconds.
Working directory: "/Users/jinningwang/Documents/work/andes/docs/source/examples"
> Reloaded generated Python code of module "pycode".
Parsing input file "/Users/jinningwang/Documents/work/andes/andes/cases/ieee14/ieee14_ieeet1.xlsx"...
Input file parsed in 0.0895 seconds.
System internal structure set up in 0.0383 seconds.
True
Convert to pandapower net#
Convert ADNES system ssa
to pandapower net ssp
.
ssp = to_pandapower(ss0)
-> System connectivity check results:
No islanded bus detected.
System is interconnected.
Each island has a slack bus correctly defined and enabled.
-> Power flow calculation
Sparse solver: KLU
Solution method: NR method
Power flow initialized in 0.0031 seconds.
0: |F(x)| = 0.5605182134
1: |F(x)| = 0.006202200332
2: |F(x)| = 5.819382825e-06
3: |F(x)| = 6.967745825e-12
Converged in 4 iterations in 0.0071 seconds.
Power flow results are consistent. Conversion is successful.
Add generator cost data.
gen_cost = np.array([
[2, 0, 0, 3, 0.0430293, 20, 0],
[2, 0, 0, 3, 0.25, 20, 0],
[2, 0, 0, 3, 0.01, 40, 0],
[2, 0, 0, 3, 0.01, 40, 0],
[2, 0, 0, 3, 0.01, 40, 0]
])
add_gencost(ssp, gen_cost)
True
Inspect the pandapower net ssp
.
ssp
This pandapower network includes the following parameter tables:
- bus (14 elements)
- load (11 elements)
- gen (5 elements)
- shunt (2 elements)
- line (16 elements)
- trafo (4 elements)
- poly_cost (5 elements)
and the following results tables:
- res_bus (14 elements)
- res_line (16 elements)
- res_trafo (4 elements)
- res_load (11 elements)
- res_shunt (2 elements)
- res_gen (5 elements)
Comapre Power Flow Results#
Run power flow of ssa
.
ssa.PFlow.run()
-> System connectivity check results:
No islanded bus detected.
System is interconnected.
Each island has a slack bus correctly defined and enabled.
-> Power flow calculation
Sparse solver: KLU
Solution method: NR method
Power flow initialized in 0.0037 seconds.
0: |F(x)| = 0.5605182134
1: |F(x)| = 0.006202200332
2: |F(x)| = 5.819382825e-06
3: |F(x)| = 6.967745825e-12
Converged in 4 iterations in 0.0071 seconds.
True
# ssa
ssa_res_gen = pd.DataFrame(columns=['name', 'p_mw', 'q_mvar', 'va_degree', 'vm_pu'])
ssa_res_gen['name'] = ssa.PV.as_df()['name']
ssa_res_gen['p_mw'] = ssa.PV.p.v * ssa.config.mva
ssa_res_gen['q_mvar'] = ssa.PV.q.v * ssa.config.mva
ssa_res_gen['va_degree'] = ssa.PV.a.v * 180 / pi
ssa_res_gen['vm_pu'] = ssa.PV.v.v
ssa_res_slack = pd.DataFrame([[ssa.Slack.name.v[0], ssa.Slack.p.v[0] * ssa.config.mva,
ssa.Slack.q.v[0] * ssa.config.mva, ssa.Slack.a.v[0] * 180 / pi,
ssa.Slack.v.v[0]]],
columns=ssa_res_gen.columns,
)
ssa_res_gen = pd.concat([ssa_res_gen, ssa_res_slack]).reset_index(drop=True)
# ssp
pp.runpp(ssp)
ssp_res_gen = pd.concat([ssp.gen['name'], ssp.res_gen], axis=1)
res_gen_concat = pd.concat([ssa_res_gen, ssp_res_gen], axis=1)
# ssa
ssa_pf_bus = ssa.Bus.as_df()[["name"]].copy()
ssa_pf_bus['v_andes'] = ssa.Bus.v.v
ssa_pf_bus['a_andes'] = ssa.Bus.a.v * 180 / pi
# ssp
ssp_pf_bus = ssa.Bus.as_df()[["name"]].copy()
ssp_pf_bus['v_pp'] = ssp.res_bus['vm_pu']
ssp_pf_bus['a_pp'] = ssp.res_bus['va_degree']
pf_bus_concat = pd.concat([ssa_pf_bus, ssp_pf_bus], axis=1)
Generation#
In the table below, the left half are ANDES results, and the right half are from pandapower
res_gen_concat.round(4)
name | p_mw | q_mvar | va_degree | vm_pu | name | p_mw | q_mvar | va_degree | vm_pu | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 2 | 40.0000 | 30.4361 | -1.7641 | 1.03 | 2 | 40.0000 | 30.4361 | -1.7641 | 1.03 |
1 | 3 | 40.0000 | 12.5971 | -3.5371 | 1.01 | 3 | 40.0000 | 12.5971 | -3.5371 | 1.01 |
2 | 4 | 30.0000 | 20.9866 | -6.4527 | 1.03 | 4 | 30.0000 | 20.9866 | -6.4527 | 1.03 |
3 | 5 | 35.0000 | 7.3964 | -1.5400 | 1.03 | 5 | 35.0000 | 7.3964 | -1.5400 | 1.03 |
4 | 1 | 81.4272 | -21.6171 | 0.0000 | 1.03 | 1 | 81.4272 | -21.6171 | 0.0000 | 1.03 |
Bus voltage and angle#
Likewise, the left half are ANDES results, and the right half are from pandapower
pf_bus_concat.round(4)
name | v_andes | a_andes | name | v_pp | a_pp | |
---|---|---|---|---|---|---|
uid | ||||||
0 | BUS1 | 1.0300 | 0.0000 | BUS1 | 1.0300 | 0.0000 |
1 | BUS2 | 1.0300 | -1.7641 | BUS2 | 1.0300 | -1.7641 |
2 | BUS3 | 1.0100 | -3.5371 | BUS3 | 1.0100 | -3.5371 |
3 | BUS4 | 1.0114 | -4.4098 | BUS4 | 1.0114 | -4.4098 |
4 | BUS5 | 1.0173 | -3.8430 | BUS5 | 1.0173 | -3.8430 |
5 | BUS6 | 1.0300 | -6.4527 | BUS6 | 1.0300 | -6.4527 |
6 | BUS7 | 1.0225 | -4.8852 | BUS7 | 1.0225 | -4.8852 |
7 | BUS8 | 1.0300 | -1.5400 | BUS8 | 1.0300 | -1.5400 |
8 | BUS9 | 1.0218 | -7.2459 | BUS9 | 1.0218 | -7.2459 |
9 | BUS10 | 1.0155 | -7.4155 | BUS10 | 1.0155 | -7.4155 |
10 | BUS11 | 1.0191 | -7.0797 | BUS11 | 1.0191 | -7.0797 |
11 | BUS12 | 1.0174 | -7.4730 | BUS12 | 1.0174 | -7.4730 |
12 | BUS13 | 1.0145 | -7.7208 | BUS13 | 1.0145 | -7.7208 |
13 | BUS14 | 1.0163 | -9.4811 | BUS14 | 1.0163 | -9.4811 |
Generator dispatch based on OPF from pandapower#
Prepare the link table.
# Asign the StaticGen with OPF, in this case, all the SynGen are GENROU
link_table = make_link_table(ssa)
link_table
stg_name | stg_idx | bus_idx | syn_idx | exc_idx | gov_idx | bus_name | |
---|---|---|---|---|---|---|---|
0 | 2 | 2 | 2 | GENROU_2 | EXST1_1 | TGOV1_2 | BUS2 |
1 | 3 | 3 | 3 | GENROU_3 | ESST3A_3 | TGOV1_3 | BUS3 |
2 | 4 | 4 | 6 | GENROU_4 | ESST3A_4 | TGOV1_4 | BUS6 |
3 | 5 | 5 | 8 | GENROU_5 | 1 | TGOV1_5 | BUS8 |
4 | 1 | 1 | 1 | GENROU_1 | ESST3A_2 | TGOV1_1 | BUS1 |
Run the TDS in ADNES to 2s.
ssa.TDS.config.tf = 2
ssa.TDS.run()
ssa.TDS.plt.plot(ssa.GENROU.Pe)
-> Time Domain Simulation Summary:
Sparse Solver: KLU
Simulation time: 0.0-2 s.
Fixed step size: h=33.33 ms. Shrink if not converged.
Initialization for dynamics completed in 0.0435 seconds.
Initialization was successful.
Simulation completed in 0.0734 seconds.
(<Figure size 600x400 with 1 Axes>, <AxesSubplot:xlabel='Time [s]'>)
Get the OPF results from pandapower. The ssp_res
has been converted to p.u..
ssp_res = runopp_map(ssp, link_table)
ssp_res
name | p | q | vm_pu | bus_name | bus_idx | controllable | syn_idx | gov_idx | exc_idx | stg_idx | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2 | 0.500000 | 0.129214 | 1.096908 | BUS2 | 2 | True | GENROU_2 | TGOV1_2 | EXST1_1 | 2 |
1 | 3 | 0.412728 | 0.149999 | 1.082554 | BUS3 | 3 | True | GENROU_3 | TGOV1_3 | ESST3A_3 | 3 |
2 | 4 | 0.343962 | 0.099999 | 1.086078 | BUS6 | 6 | True | GENROU_4 | TGOV1_4 | ESST3A_4 | 4 |
3 | 5 | 0.496196 | 0.085231 | 1.099999 | BUS8 | 8 | True | GENROU_5 | TGOV1_5 | 1 | 5 |
4 | 1 | 0.500000 | -0.079092 | 1.100000 | BUS1 | 1 | True | GENROU_1 | TGOV1_1 | ESST3A_2 | 1 |
Now dispatch the resutls into ssa
, where the active power setpoitns are updated to TurbinGov.pref0
.
ssa_gov_idx = list(ssp_res['gov_idx'][~ssp_res['gov_idx'].isna()])
ssa.TurbineGov.set(src='pref0', idx=ssa_gov_idx, attr='v', value=ssp_res['p'][~ssp_res['gov_idx'].isna()])
ssa.TurbineGov.get(src='pref0', idx=ssa_gov_idx, attr='v')
array([0.49999999, 0.41272757, 0.34396221, 0.49619557, 0.50000009])
Now run the TDS to 50s.
ssa.TDS.config.tf = 50
ssa.TDS.run()
Simulation completed in 2.6138 seconds.
True
We can see the outputs of GENROU
are rearranged by the OPF results.
ssa.TDS.plt.plot(ssa.GENROU.Pe)
(<Figure size 600x400 with 1 Axes>, <AxesSubplot:xlabel='Time [s]'>)