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的元件列表。
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)
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:
让我们看下结果:
net.res_ext_grid
p_kw | q_kvar | |
---|---|---|
0 | -56530.125753 | -1974.470643 |
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上的负荷是由其他发电机联合供应因此电能的传输达到了最小的损失。
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!
net.res_ext_grid
p_kw | q_kvar | |
---|---|---|
0 | -144559.444871 | -9193.066456 |
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给出:
net.res_cost
14455.950328285478
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%:
net.res_trafo
p_hv_kw | q_hv_kvar | p_lv_kw | q_lv_kvar | pl_kw | ql_kvar | i_hv_ka | i_lv_ka | loading_percent | |
---|---|---|---|---|---|---|---|---|---|
0 | 144559.444871 | 9193.066457 | -143959.516332 | 15993.742384 | 599.928539 | 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:
我们可以看到变压器以最大负荷运行:
net.res_trafo
p_hv_kw | q_hv_kvar | p_lv_kw | q_lv_kvar | pl_kw | ql_kvar | i_hv_ka | i_lv_ka | loading_percent | |
---|---|---|---|---|---|---|---|---|---|
0 | 49953.864488 | -2147.313884 | -49833.813812 | 5167.453489 | 120.050676 | 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是发电成本第二低的发电机组)
net.res_ext_grid
p_kw | q_kvar | |
---|---|---|
0 | -49953.864488 | 2147.313884 |
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成本的上升:
net.res_cost
16191.876620556373
We now look at the line loadings:
我们现在关注于线路负载:
net.res_line
p_from_kw | q_from_kvar | p_to_kw | q_to_kvar | pl_kw | ql_kvar | i_from_ka | i_to_ka | i_ka | loading_percent | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 19780.009515 | -2479.435419 | -19341.695290 | 1104.393239 | 438.314225 | -1375.042180 | 0.104309 | 0.103207 | 0.104309 | 22.193320 |
1 | -50658.298957 | 1888.752606 | 52783.697109 | 921.064101 | 2125.398152 | 2809.816707 | 0.270061 | 0.270140 | 0.270140 | 57.476548 |
2 | 30520.380464 | 2532.279360 | -29946.195703 | -2688.018070 | 574.184761 | -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:
现在线路的负载约束编译后:
net.res_line
p_from_kw | q_from_kvar | p_to_kw | q_to_kvar | pl_kw | ql_kvar | i_from_ka | i_to_ka | i_ka | loading_percent | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 16727.519781 | -3194.974086 | -16412.725022 | 1534.133994 | 314.794759 | -1660.840092 | 0.088849 | 0.087130 | 0.088849 | 18.903945 |
1 | -44451.195382 | 868.876229 | 46059.937188 | 830.815836 | 1608.741806 | 1699.692065 | 0.234999 | 0.235000 | 0.235000 | 49.999998 |
2 | 27533.080431 | 4051.080417 | -27060.140775 | -4429.078165 | 472.939657 | -377.997748 | 0.141964 | 0.143057 | 0.143057 | 30.437636 |
And all generators are involved in supplying the loads:
所有的发电机都开始供应负荷:
net.res_ext_grid
p_kw | q_kvar | |
---|---|---|
0 | -49787.59354 | 4603.760721 |
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成本又一次的提升:
net.res_cost
16380.333324014753
Finally, we have a look at the bus voltage:
最后,我们在关注一下电压:
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:
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:
所有的发电机又一次都参与了负荷的供应:
net.res_ext_grid
p_kw | q_kvar | |
---|---|---|
0 | -49906.847219 | 3050.617054 |
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成本的提升:
net.res_cost
16422.572982172846
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:
因为直流潮流公式中不考虑电压幅值,所以在直流潮流计算中并不考虑电压约束:
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:
线路和变压器负载限制编译后为:
net.res_line
p_from_kw | q_from_kvar | p_to_kw | q_to_kvar | pl_kw | ql_kvar | i_from_ka | i_to_ka | i_ka | loading_percent | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 16715.233329 | 0.0 | -16715.233329 | 0.0 | 0.0 | 0.0 | 0.087732 | 0.087732 | 0.087732 | 18.666430 |
1 | -44773.513351 | 0.0 | 44773.513351 | 0.0 | 0.0 | 0.0 | 0.235000 | 0.235000 | 0.235000 | 50.000000 |
2 | 26715.233363 | 0.0 | -26715.233363 | 0.0 | 0.0 | 0.0 | 0.140219 | 0.140219 | 0.140219 | 29.833747 |
net.res_trafo
p_hv_kw | q_hv_kvar | p_lv_kw | q_lv_kvar | pl_kw | ql_kvar | i_hv_ka | i_lv_ka | loading_percent | |
---|---|---|---|---|---|---|---|---|---|
0 | 49999.999965 | 0.0 | -49999.999965 | 0.0 | 0.0 | 0.0 | 0.131216 | 0.262432 | 50.0 |
As are generator limits:
发电机限制为:
net.gen
name | bus | p_kw | vm_pu | sn_kva | min_q_kvar | max_q_kvar | scaling | in_service | type | min_p_kw | max_p_kw | controllable | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | None | 2 | -80000.0 | 1.01 | NaN | NaN | NaN | 1.0 | True | None | -80000.0 | 0.0 | True |
1 | None | 3 | -100000.0 | 1.01 | NaN | NaN | NaN | 1.0 | True | None | -100000.0 | 0.0 | True |
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:
线性最优潮流计算和非线性潮流计算的成本函数一致:
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:
分段线性成本函数
最优潮流九三也提供了分段线性成本函数。我们首先看一下实际的成本函数设置:
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,让我们检查一下:
net.gen.max_p_kw
0 0.0
1 0.0
Name: max_p_kw, dtype: float64
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
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运行结果:
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 |
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)
net.res_cost
16055.337600298368
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 |