pandapower最优潮流计算[pandapower Optimal Power Flow]
This is an introduction into the usage of the pandapower optimal power flow. It shows how to set the constraints and the cost factors into the pandapower element tables.
该notebook主要是介绍pandapower最优潮流计算的使用。它展示了如何将约束和成本因子设置进入pandapower的元件列表。
示例网络[Example Network]
We use the following four bus example network for this tutorial:
我们的教程使用下图的4节点示例网络:
We first create this network in pandapower:
我们首先在pandapower中创建网络:
import pandapower as pp
import numpy as np
net = pp.create_empty_network()
#create buses
bus1 = pp.create_bus(net, vn_kv=220.)
bus2 = pp.create_bus(net, vn_kv=110.)
bus3 = pp.create_bus(net, vn_kv=110.)
bus4 = pp.create_bus(net, vn_kv=110.)
#create 220/110 kV transformer
pp.create_transformer(net, bus1, bus2, std_type="100 MVA 220/110 kV")
#create 110 kV lines
pp.create_line(net, bus2, bus3, length_km=70., std_type='149-AL1/24-ST1A 110.0')
pp.create_line(net, bus3, bus4, length_km=50., std_type='149-AL1/24-ST1A 110.0')
pp.create_line(net, bus4, bus2, length_km=40., std_type='149-AL1/24-ST1A 110.0')
#create loads
pp.create_load(net, bus2, p_kw=60e3, controllable = False)
pp.create_load(net, bus3, p_kw=70e3, controllable = False)
pp.create_load(net, bus4, p_kw=10e3, controllable = False)
#create generators
eg = pp.create_ext_grid(net, bus1, min_p_kw = -1e9, max_p_kw = 1e9)
g0 = pp.create_gen(net, bus3, p_kw=-80*1e3, min_p_kw=-80e3, max_p_kw=0,vm_pu=1.01, controllable=True)
g1 = pp.create_gen(net, bus4, p_kw=-100*1e3, min_p_kw=-100e3, max_p_kw=0, vm_pu=1.01, controllable=True)
网损最小化[Loss Minimization]
We specify the same costs for the power at the external grid and all generators to minimize the overall power feed in. This equals an overall loss minimization:
我们特别指出外部电网和所有的发电机具有相同的电能成本,并最小化总的电能供应。这相当于整体网损的最小化。
costeg = pp.create_polynomial_cost(net, 0, 'ext_grid', np.array([-1, 0]))
costgen1 = pp.create_polynomial_cost(net, 0, 'gen', np.array([-1, 0]))
costgen2 = pp.create_polynomial_cost(net, 1, 'gen', np.array([-1, 0]))
print(costeg, costgen1, costgen2)
0 1 2
pp.create_polynomial_cost()为元件的多项式成本创建一个条目,返回已经创建的成本条目的ID
We run an OPF:
我们运行一个最优潮流计算:
pp.runopp(net, verbose=True)
The OPF cost definition has changed! Please check out the tutorial 'opf_changes-may18.ipynb' or the documentation!
PYPOWER Version 5.1.4, 27-June-2018 -- AC Optimal Power Flow
Python Interior Point Solver - PIPS, Version 1.0, 07-Feb-2011
Converged!
let's check the results:
让我们看下结果:
print(net.res_ext_grid)
p_kw q_kvar
0 -56530.125753 -1974.470643
print(net.res_gen)
p_kw q_kvar va_degree vm_pu
0 -71313.391997 1969.654771 -3.712810 1.000009
1 -12299.771985 1451.159898 -3.712777 1.000010
Since all costs were specified the same, the OPF minimizes overall power generation, which is equal to a loss minimization in the network. The loads at buses 3 and 4 are supplied by generators at the same bus, the load at Bus 2 is provided by a combination of the other generators so that the power transmission leads to minimal losses.
因为所有的成本都看成相同的,最优潮流计算最小化总的发电量后的结果和网络的损失最小化结果相同。母线3和母线4的负荷由各自母线上的发电机供应,而母线2上的负荷是由其他发电机联合供应因此电能的传输达到了最小的损失。
个体发电机成本[Individual Generator Costs]
Let's now assign individual costs to each generator.
现在给每台发电机赋予各自的发电成本。
We assign a cost of 10 ct/kW for the external grid, 15 ct/kw for the generator g0 and 12 ct/kw for generator g1:
我们给外部电网赋予10ct/kW的成本,发电机g0赋予15ct/kW的成本以及发电机g1赋予12ct/kW的成本:
net.polynomial_cost.c.at[costeg] = np.array([[-0.1, 0]])
net.polynomial_cost.c.at[costgen1] = np.array([[-0.15, 0]])
net.polynomial_cost.c.at[costgen2] = np.array([[-0.12, 0]])
And now run an OPF:
现在运行最优潮流运算:
pp.runopp(net, verbose=True)
The OPF cost definition has changed! Please check out the tutorial 'opf_changes-may18.ipynb' or the documentation!
PYPOWER Version 5.1.4, 27-June-2018 -- AC Optimal Power Flow
Python Interior Point Solver - PIPS, Version 1.0, 07-Feb-2011
Converged!
print(net.res_ext_grid)
p_kw q_kvar
0 -144559.444871 -9193.066456
print(net.res_gen)
p_kw q_kvar va_degree vm_pu
0 -0.011910 -8601.802715 -16.426869 0.967619
1 -0.033788 -10594.678350 -13.481042 0.989756
We can see that all active power is provided by the external grid. This makes sense, because the external grid has the lowest cost of all generators and we did not define any constraints.
我们可以看出所有的有功都是由外部电网提供。这是合理的,因为外部电网拥有最低的发电成本,而且我们没有定义任何的约束。
The dispatch costs are given in net.res_cost:
dispatch成本可以由net.res_cost给出:
print(net.res_cost)
14455.950328285478
变压器约束[Transformer Constraint]
Since all active power comes from the external grid and subsequently flows through the transformer, the transformer is overloaded with a loading of about 145%:
因为所有的有功功率都来自与外部电网,接着潮流流过了变压器,变压器过载运行,负载约为145%:
print(net.res_trafo)
p_hv_kw q_hv_kvar p_lv_kw q_lv_kvar pl_kw \
0 144559.444871 9193.066457 -143959.516332 15993.742384 599.928539
ql_kvar i_hv_ka i_lv_ka loading_percent
0 25186.808841 0.380136 0.759988 144.85146
We now limit the transformer loading to 50%:
现在设置变压器的负载约束为50%:
net.trafo["max_loading_percent"] = 50
(the max_loading_percent parameter can also be specified directly when creating the transformer)
and run the OPF:
(max_loading_percent参数也可以在创建变压器时直接指定)
然后运行最优潮流计算:
pp.runopp(net)
The OPF cost definition has changed! Please check out the tutorial 'opf_changes-may18.ipynb' or the documentation!
We can see that the transformer complies with the maximum loading:
我们可以看到变压器以最大负荷运行:
print(net.res_trafo)
p_hv_kw q_hv_kvar p_lv_kw q_lv_kvar pl_kw \
0 49953.864488 -2147.313884 -49833.813812 5167.453489 120.050676
ql_kvar i_hv_ka i_lv_ka loading_percent
0 3020.139605 0.131216 0.262153 49.999995
And power generation is now split between the external grid and generator 1 (which is the second cheapest generation unit):
现在有功功率的产生主要来自于外部电网和发电机g1(g1是发电成本第二低的发电机组)
print(net.res_ext_grid)
p_kw q_kvar
0 -49953.864488 2147.313884
print(net.res_gen)
p_kw q_kvar va_degree vm_pu
0 -0.005754 -2993.145845 -6.232829 0.985230
1 -93304.077572 -3453.343461 -1.237884 1.025709
This comes of course with an increase in dispatch costs:
这样的结果就导致了dispatch成本的上升:
print(net.res_cost)
16191.876620556373
线路负载约束[Line Loading Constraints]
We now look at the line loadings:
我们现在关注于线路负载:
print(net.res_line)
p_from_kw q_from_kvar p_to_kw q_to_kvar pl_kw \
0 19780.009515 -2479.435419 -19341.695290 1104.393239 438.314225
1 -50658.298957 1888.752606 52783.697109 921.064101 2125.398152
2 30520.380464 2532.279360 -29946.195703 -2688.018070 574.184761
ql_kvar i_from_ka i_to_ka i_ka loading_percent
0 -1375.042180 0.104309 0.103207 0.104309 22.193320
1 2809.816707 0.270061 0.270140 0.270140 57.476548
2 -155.738710 0.156712 0.157323 0.157323 33.472994
and run the OPF with a 50% loading constraint:
然后在线路50%负载水平条件下运行最优潮流计算:
net.line["max_loading_percent"] = 50
pp.runopp(net, verbose=True)
The OPF cost definition has changed! Please check out the tutorial 'opf_changes-may18.ipynb' or the documentation!
PYPOWER Version 5.1.4, 27-June-2018 -- AC Optimal Power Flow
Python Interior Point Solver - PIPS, Version 1.0, 07-Feb-2011
Converged!
Now the line loading constraint is complied with:
现在线路的负载约束编译后:
print(net.res_line)
p_from_kw q_from_kvar p_to_kw q_to_kvar pl_kw \
0 16727.519781 -3194.974086 -16412.725022 1534.133994 314.794759
1 -44451.195382 868.876229 46059.937188 830.815836 1608.741806
2 27533.080431 4051.080417 -27060.140775 -4429.078165 472.939657
ql_kvar i_from_ka i_to_ka i_ka loading_percent
0 -1660.840092 0.088849 0.087130 0.088849 18.903945
1 1699.692065 0.234999 0.235000 0.235000 49.999998
2 -377.997748 0.141964 0.143057 0.143057 30.437636
And all generators are involved in supplying the loads:
所有的发电机都开始供应负荷:
print(net.res_ext_grid)
p_kw q_kvar
0 -49787.59354 4603.760721
print(net.res_gen)
p_kw q_kvar va_degree vm_pu
0 -9136.079301 -2403.009062 -5.814954 0.992994
1 -83593.017290 -4881.895570 -1.511656 1.028899
This of course comes with a once again rising dispatch cost:
这导致了dispatch成本又一次的提升:
print(net.res_cost)
16380.333324014753
电压约束[Voltage Constraints]
Finally, we have a look at the bus voltage:
最后,我们在关注一下电压:
print(net.res_bus)
vm_pu va_degree p_kw q_kvar lam_p lam_q
0 1.000000 0.000000 -49787.593540 4603.760721 100.000000 -2.642153e-21
1 1.006025 -3.408832 60000.000000 0.000000 130.952263 -5.410268e-01
2 0.992994 -5.814954 60863.920699 -2403.009062 149.999973 1.192009e-21
3 1.028899 -1.511656 -73593.017290 -4881.895570 120.000014 2.988530e-21
and constrain it:
加以约束,并运行最优潮流计算:
net.bus["min_vm_pu"] = 1.0
net.bus["max_vm_pu"] = 1.02
pp.runopp(net)
The OPF cost definition has changed! Please check out the tutorial 'opf_changes-may18.ipynb' or the documentation!
We can see that all voltages are within the voltage band:
print(net.res_bus)
vm_pu va_degree p_kw q_kvar lam_p lam_q
0 1.000000 0.000000 -49906.847219 3050.617054 100.000000 -6.408104e-22
1 1.004168 -3.421015 60000.000000 0.000000 131.268596 -2.133663e-01
2 1.000000 -5.976094 59278.204655 -14858.933373 149.999992 3.120534e-21
3 1.020000 -1.366892 -71863.491321 9172.691847 120.000005 -1.926105e-21
And all generators are once again involved in supplying the loads:
所有的发电机又一次都参与了负荷的供应:
print(net.res_ext_grid)
p_kw q_kvar
0 -49906.847219 3050.617054
print(net.res_gen)
p_kw q_kvar va_degree vm_pu
0 -10721.795345 -14858.933373 -5.976094 1.00
1 -81863.491321 9172.691847 -1.366892 1.02
This of course comes once again with rising dispatch costs:
又一次导致了dipatch成本的提升:
print(net.res_cost)
16422.572982172846
直流最优潮流计算[DC OPF]
pandapower also provides the possibility of running a DC Optimal Power Flow:
pandapower也可以提供了运行直流最优潮流计算的可能:
pp.rundcopp(net)
Since voltage magnitudes are not included in the DC power flow formulation, voltage constraints canot be considered in the DC OPF:
因为直流潮流公式中不考虑电压幅值,所以在直流潮流计算中并不考虑电压约束:
print(net.res_bus)
vm_pu va_degree p_kw q_kvar lam_p lam_q
0 NaN 0.000000 -49999.999965 NaN 100.000000 0.0
1 NaN -3.436967 60000.000000 NaN 130.909091 0.0
2 NaN -5.708566 61488.746680 NaN 150.000000 0.0
3 NaN -1.362340 -71488.746715 NaN 120.000000 0.0
Line and transformer loading limits are however complied with:
线路和变压器负载限制编译后为:
print(net.res_line)
p_from_kw q_from_kvar p_to_kw q_to_kvar pl_kw ql_kvar \
0 16715.233329 0.0 -16715.233329 0.0 0.0 0.0
1 -44773.513351 0.0 44773.513351 0.0 0.0 0.0
2 26715.233363 0.0 -26715.233363 0.0 0.0 0.0
i_from_ka i_to_ka i_ka loading_percent
0 0.087732 0.087732 0.087732 18.666430
1 0.235000 0.235000 0.235000 50.000000
2 0.140219 0.140219 0.140219 29.833747
print(net.res_trafo)
p_hv_kw q_hv_kvar p_lv_kw q_lv_kvar pl_kw ql_kvar i_hv_ka \
0 49999.999965 0.0 -49999.999965 0.0 0.0 0.0 0.131216
i_lv_ka loading_percent
0 0.262432 50.0
As are generator limits:
发电机限制为:
print(net.gen)
name bus p_kw vm_pu sn_kva min_q_kvar max_q_kvar scaling \
0 None 2 -80000.0 1.01 NaN NaN NaN 1.0
1 None 3 -100000.0 1.01 NaN NaN NaN 1.0
in_service type min_p_kw max_p_kw controllable
0 True None -80000.0 0.0 True
1 True None -100000.0 0.0 True
print(net.res_gen)
p_kw q_kvar va_degree vm_pu
0 -8511.253320 NaN -5.708566 1.0
1 -81488.746715 NaN -1.362340 1.0
The cost function is the same for the linearized OPF as for the non-linear one:
线性最优潮流计算和非线性潮流计算的成本函数一致:
print(net.res_cost)
16055.337600298368
Piecewise linear cost functions
The OPF also offers us piecewise linear cost functions. Let us first check the actual cost function setup:
分段线性成本函数
最优潮流九三也提供了分段线性成本函数。我们首先看一下实际的成本函数设置:
print(net.polynomial_cost)
type element element_type c
0 p 0 ext_grid [[-0.1, 0.0]]
1 p 0 gen [[-0.15, 0.0]]
2 p 1 gen [[-0.12, 0.0]]
An element can either have polynomial costs or piecewise linear costs at the same time. So let us first delete the polynomial costs in order to avoid confusion and errors:
一个元件的成本函数只能是多项式或者分段线性中的一种。因此,让我们首先删除多项式成本函数,以免产生困惑和错误:
net.polynomial_cost= net.polynomial_cost.drop(net.polynomial_cost.index.values)
The results above have been produced with polynomial cost functions, that were linear. Let's try to reproduce the results using piecewise linear cost functions. Note: The cost functions need to have the same gradient!
上面的计算结果是采用多项式成本函数计算的,是线性的。现在我们使用分段线性函数重新计算结果。需要强调的是:成本函数需要拥有相同的梯度!
pp.create_piecewise_linear_cost(net, 0, "gen", np.array([[-1 , 0.15] ,[0, 0]]))
pp.create_piecewise_linear_cost(net, 1, "gen", np.array([[-1, 0.12], [0, 0]]))
pp.create_piecewise_linear_cost(net, 0, "ext_grid", np.array([[-1, 0.1], [0, 0]]))
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
in
----> 1 pp.create_piecewise_linear_cost(net, 0, "gen", np.array([[-1 , 0.15] ,[0, 0]]))
2 pp.create_piecewise_linear_cost(net, 1, "gen", np.array([[-1, 0.12], [0, 0]]))
3 pp.create_piecewise_linear_cost(net, 0, "ext_grid", np.array([[-1, 0.1], [0, 0]]))
~/.local/lib/python3.5/site-packages/pandapower/create.py in create_piecewise_linear_cost(net, element, element_type, data_points, type, index)
2469 if not (net[element_type].max_p_kw.at[element] <= max(p) and
2470 net[element_type].min_p_kw.at[element] >= min(p)):
-> 2471 raise ValueError("Cost function must be defined for whole power range of the "
2472 "generator")
2473 if type == "q":
ValueError: Cost function must be defined for whole power range of the generator
What we forgot is that the piecewise linear function should be defined for the whole range of the generator. The range is determined by p_max and p_min. Let's check:
额...错误信息是ValueError: Cost function must be defined for whole power range of the generator,让我们检查一下:
print(net.gen.max_p_kw)
0 0.0
1 0.0
Name: max_p_kw, dtype: float64
print(net.gen.min_p_kw)
0 -80000.0
1 -100000.0
Name: min_p_kw, dtype: float64
We try again:
我们再试一下:
pp.create_piecewise_linear_cost(net, 0, "gen", np.array([[-80000* 1 , 80000*0.15], [0, 0]]))
pp.create_piecewise_linear_cost(net, 1, "gen", np.array([[-100000*1, 100000*0.12], [0, 0]]))
1
An external grid usually has no operational limits, but this is a problem for the OPF:
外部电网通常没有运行限制,但这是OPF的一个问题:
pp.create_piecewise_linear_cost(net, 0, "ext_grid", np.array([[0, 0], [1, 0.1]]))
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
in
----> 1 pp.create_piecewise_linear_cost(net, 0, "ext_grid", np.array([[0, 0], [1, 0.1]]))
~/.local/lib/python3.5/site-packages/pandapower/create.py in create_piecewise_linear_cost(net, element, element_type, data_points, type, index)
2469 if not (net[element_type].max_p_kw.at[element] <= max(p) and
2470 net[element_type].min_p_kw.at[element] >= min(p)):
-> 2471 raise ValueError("Cost function must be defined for whole power range of the "
2472 "generator")
2473 if type == "q":
ValueError: Cost function must be defined for whole power range of the generator
So we set imaginary constraints, that we can choose very broad:
因此,我们虚构一个约束,将这个约束的范围设置的很广:
net.ext_grid["max_p_kw"] = 1e9
net.ext_grid["min_p_kw"] = -1e9
print(net.ext_grid)
name bus vm_pu va_degree in_service min_p_kw max_p_kw
0 None 0 1.0 0.0 True -1.000000e+09 1.000000e+09
pp.create_piecewise_linear_cost(net, 0, "ext_grid", np.array([[-1e9, 1e9*.1], [1e9, -1e9*0.1]]))
2
Let us check the results from the previous OPF again!
让我们再看一下之前的OPF运行结果:
print(net.res_bus)
vm_pu va_degree p_kw q_kvar lam_p lam_q
0 NaN 0.000000 -49999.999965 NaN 100.000000 0.0
1 NaN -3.436967 60000.000000 NaN 130.909091 0.0
2 NaN -5.708566 61488.746680 NaN 150.000000 0.0
3 NaN -1.362340 -71488.746715 NaN 120.000000 0.0
print(net.res_cost)
16055.337600298368
We run the same OPF now with different cost function setup. We should get the exact same results:
我们运行了相同的OPF但是采用不同的成本函数,我们应该获得完全相同的结果:
pp.rundcopp(net)
print(net.res_cost)
16055.337600298368
print(net.res_bus)
vm_pu va_degree p_kw q_kvar lam_p lam_q
0 NaN 0.000000 -49999.999965 NaN 100.000000 0.0
1 NaN -3.436967 60000.000000 NaN 130.909091 0.0
2 NaN -5.708566 61488.746680 NaN 150.000000 0.0
3 NaN -1.362340 -71488.746715 NaN 120.000000 0.0