import pandapower as pp
from numpy import array
net = pp.create_empty_network()
b1 = pp.create_bus(net, 380)
b2 = pp.create_bus(net, 380)
b3 = pp.create_bus(net, 380)
b4 = pp.create_bus(net, 380)
b5 = pp.create_bus(net, 380)
l1 = pp.create_line(net, b1, b2, 30, "490-AL1/64-ST1A 380.0")
l2 = pp.create_line(net, b3, b4, 20, "490-AL1/64-ST1A 380.0")
l3 = pp.create_line(net, b4, b5, 20, "490-AL1/64-ST1A 380.0")
dcl1 = pp.create_dcline(net, name="dc line", from_bus=b2, to_bus=b3, p_kw=0.2e6, loss_percent=1.0,
loss_kw=500, vm_from_pu=1.01, vm_to_pu=1.012, max_p_kw=1e6,
in_service=True)
eg1 = pp.create_ext_grid(net, b1, 1.02, max_p_kw=0.)
eg2 = pp.create_ext_grid(net, b5, 1.02, max_p_kw=0.)
l1 = pp.create_load(net, bus=b4, p_kw=800e3, controllable = False)
We now run a regular load flow to check out the DC line model:
pp.runpp(net)
The transmission power of the DC line is defined in the loadflow as given by the p_kw parameter, which was set to 0.2 GW:
net.res_dcline
p_from_kw | q_from_kvar | p_to_kw | q_to_kvar | pl_kw | vm_from_pu | va_from_degree | vm_to_pu | va_to_degree | |
---|---|---|---|---|---|---|---|---|---|
0 | 200000.0 | 152443.185449 | -197500.0 | 74491.758943 | 2500.0 | 1.01 | -0.48595 | 1.012 | -0.725627 |
The losses amount to 2500 kW, which are made up of 500 kW conversion loss and 200 MW * 0.01 = 2 MW transmission losses. The voltage setpoints defined at from and to bus are complied with.
Now lets define costs for the external grids to run an OPF:
costeg0 = pp.create_polynomial_cost(net, 0, 'ext_grid', array([.1, 0]))
costeg1 = pp.create_polynomial_cost(net, 1, 'ext_grid', array([.08, 0]))
net.bus['max_vm_pu'] = 1.5
net.line['max_loading_percent'] = 1000
pp.runopp(net)
Since we defined lower costs for Ext Grid 2, it fully services the load:
net.res_ext_grid
p_kw | q_kvar | |
---|---|---|
0 | -500.078353 | 7787.557732 |
1 | -805091.476316 | -628.307279 |
While the DC line does not transmit any power:
net.res_dcline
p_from_kw | q_from_kvar | p_to_kw | q_to_kvar | pl_kw | vm_from_pu | va_from_degree | vm_to_pu | va_to_degree | |
---|---|---|---|---|---|---|---|---|---|
0 | 500.075407 | 7787.456005 | -0.07466 | -627.126367 | 500.000747 | 1.019994 | -0.001448 | 1.013925 | -1.563437 |
If we set the costs of the left grid to a lower value than the right grid and run the loadflow again:
net.polynomial_cost.c.at[costeg0]= array([[0.08, 0]])
net.polynomial_cost.c.at[costeg1]= array([[0.1, 0]])
pp.runopp(net)
We can see that the power now comes from the left ext_grid:
net.res_ext_grid
p_kw | q_kvar | |
---|---|---|
0 | -821525.358281 | 7787.558529 |
1 | -0.054350 | 21048.592139 |
And is transmitted over the DC line:
net.res_dcline
p_from_kw | q_from_kvar | p_to_kw | q_to_kvar | pl_kw | vm_from_pu | va_from_degree | vm_to_pu | va_to_degree | |
---|---|---|---|---|---|---|---|---|---|
0 | 813573.88367 | -26446.047364 | -805023.647198 | -21736.311963 | 8550.236472 | 1.011014 | -2.39987 | 1.027504 | 1.522333 |
We can however see that the lines on the left hand side are now overloaded:
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 | 821525.358281 | -7787.558529 | -813573.883670 | 26446.047364 | 7951.474611 | 18658.488835 | 1.223760 | 1.223277 | 1.223760 | 127.474952 |
1 | 805023.647198 | 21736.311963 | -800001.920574 | -10668.101730 | 5021.726624 | 11068.210233 | 1.190800 | 1.191114 | 1.191114 | 124.074383 |
2 | 1.920574 | 10668.101730 | 0.054350 | -21048.592139 | 1.974924 | -10380.490409 | 0.015882 | 0.031353 | 0.031353 | 3.265934 |
If we set the maximum line loading to 100% and run the OPF again:
net.line["max_loading_percent"] = 100
pp.runopp(net)
We can see that the lines are no longer overloaded:
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 | 644488.858958 | -642.506854 | -639594.566319 | 6169.959158 | 4894.292639 | 5527.452303 | 0.960000 | 0.959878 | 0.960000 | 100.000000 |
1 | 632766.897378 | 10098.568594 | -629647.068718 | -7138.702673 | 3119.828660 | 2959.865921 | 0.938624 | 0.938836 | 0.938836 | 97.795376 |
2 | -170352.931282 | 7138.702673 | 170582.491792 | -16527.962212 | 229.560511 | -9389.259539 | 0.254211 | 0.255281 | 0.255281 | 26.591809 |
Because the load is serviced from both grids:
net.res_ext_grid
p_kw | q_kvar | |
---|---|---|
0 | -644488.858782 | 642.506837 |
1 | -170582.491792 | 16527.962212 |
And the DC line transmits only part of the power needed to service the load:
net.res_dcline
p_from_kw | q_from_kvar | p_to_kw | q_to_kvar | pl_kw | vm_from_pu | va_from_degree | vm_to_pu | va_to_degree | |
---|---|---|---|---|---|---|---|---|---|
0 | 639594.566352 | -6169.958242 | -632766.897378 | -10098.568594 | 6827.668974 | 1.012429 | -1.875 | 1.024385 | 0.875621 |
Finally, we can also define transmission costs for the DC line:
costeg1 = pp.create_polynomial_cost(net, 0, 'dcline', array([.03, 0]))
pp.runopp(net)
Because the sum of the costs for generating power on the left hand side (0.08) and transmitting it to the right side (0.03) is now larger than for generating on the right side (0.1), the OPF draws as much power from the right side as is possible without violating line loading constraints:
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 | 161370.351241 | -7787.557137 | -161063.552750 | -6442.901692 | 306.798490 | -14230.458829 | 0.240649 | 0.240554 | 0.240649 | 25.067628 |
1 | 158973.810254 | -4896.498618 | -158773.820749 | -4539.248232 | 199.989505 | -9435.746850 | 0.237798 | 0.237781 | 0.237798 | 24.770574 |
2 | -641226.170349 | 4539.247144 | 644488.776263 | -880.672692 | 3262.605914 | 3658.574451 | 0.959936 | 0.960000 | 0.960000 | 100.000031 |
net.res_dcline
p_from_kw | q_from_kvar | p_to_kw | q_to_kvar | pl_kw | vm_from_pu | va_from_degree | vm_to_pu | va_to_degree | |
---|---|---|---|---|---|---|---|---|---|
0 | 161063.55275 | 6442.901692 | -158973.814604 | 4896.534963 | 2089.738146 | 1.018095 | -0.467991 | 1.016202 | -0.938725 |
If we broaden the line loading constraint and run the OPF again:
net.line["max_loading_percent"] = 1000
pp.runopp(net)
The load is once again fully serviced by the grid on the right hand side:
net.res_ext_grid
p_kw | q_kvar | |
---|---|---|
0 | -500.300942 | 7787.555581 |
1 | -805091.253109 | -628.302293 |
And the DC line is in open loop operation:
net.res_dcline
p_from_kw | q_from_kvar | p_to_kw | q_to_kvar | pl_kw | vm_from_pu | va_from_degree | vm_to_pu | va_to_degree | |
---|---|---|---|---|---|---|---|---|---|
0 | 500.297993 | 7787.458105 | -0.295043 | -627.119172 | 500.00295 | 1.019994 | -0.001448 | 1.013925 | -1.563436 |