Security-Constrained Optimisation

Note

You can download this example as a Jupyter notebook or start it in interactive mode.

Security-Constrained Optimisation#

In this example, the dispatch of generators is optimised using the security-constrained linear OPF, to guaranteed that no branches are overloaded by certain branch outages.

[1]:
import pypsa, os
import numpy as np
ERROR 1: PROJ: proj_create_from_database: Open of /home/docs/checkouts/readthedocs.org/user_builds/pypsa/conda/latest/share/proj failed
[2]:
network = pypsa.examples.scigrid_de(from_master=True)
WARNING:pypsa.io:Importing network from PyPSA version v0.17.1 while current version is v0.27.1. Read the release notes at https://pypsa.readthedocs.io/en/latest/release_notes.html to prepare your network for import.
INFO:pypsa.io:Imported network scigrid-de.nc has buses, generators, lines, loads, storage_units, transformers

There are some infeasibilities without line extensions.

[3]:
for line_name in ["316", "527", "602"]:
    network.lines.loc[line_name, "s_nom"] = 1200

now = network.snapshots[0]

Performing security-constrained linear OPF

[4]:
branch_outages = network.lines.index[:15]
network.optimize.optimize_security_constrained(
    now, branch_outages=branch_outages, solver_name="cbc"
)
WARNING:pypsa.components:The following transformers have zero r, which could break the linear load flow:
Index(['2', '5', '10', '12', '13', '15', '18', '20', '22', '24', '26', '30',
       '32', '37', '42', '46', '52', '56', '61', '68', '69', '74', '78', '86',
       '87', '94', '95', '96', '99', '100', '104', '105', '106', '107', '117',
       '120', '123', '124', '125', '128', '129', '138', '143', '156', '157',
       '159', '160', '165', '184', '191', '195', '201', '220', '231', '232',
       '233', '236', '247', '248', '250', '251', '252', '261', '263', '264',
       '267', '272', '279', '281', '282', '292', '303', '307', '308', '312',
       '315', '317', '322', '332', '334', '336', '338', '351', '353', '360',
       '362', '382', '384', '385', '391', '403', '404', '413', '421', '450',
       '458'],
      dtype='object', name='Transformer')
INFO:linopy.model: Solve problem using Cbc solver
INFO:linopy.io: Writing time: 0.23s
INFO:linopy.solvers:Welcome to the CBC MILP Solver
Version: 2.10.10
Build Date: Apr 19 2023

command line - cbc -printingOptions all -import /tmp/linopy-problem-73e_crvx.lp -solve -solu /tmp/linopy-solve-dte6rqab.sol (default strategy 1)
Option for printingOptions changed from normal to all
Presolve 14006 (-20391) rows, 1061 (-1424) columns and 30479 (-28022) elements
Perturbing problem by 0.001% of 34203.748 - largest nonzero change 0.00099776615 ( 0.0015440604%) - largest zero change 0.0009961904
0  Obj 24484.924 Primal inf 6732711.7 (3096) Dual inf 1209.4693 (1)
295  Obj 1779.0426 Primal inf 2.3850058e+08 (3084)
590  Obj 1788.1315 Primal inf 96466677 (2559)
774  Obj 1794.2664 Primal inf 1.5665431e+09 (4717)
882  Obj 2117.4174 Primal inf 1.2918623e+10 (4401)
962  Obj 2367.6937 Primal inf 1.302712e+10 (4647)
973  Obj 2467.6619 Primal inf 7.9617623e+10 (5747)
1152  Obj 118632.25 Primal inf 3.4262106e+08 (2095)
1314  Obj 342732.31 Primal inf 28122.885 (72)
1345  Obj 347892.2
1345  Obj 347887.1 Dual inf 8.6477973e-05 (1)
1346  Obj 347887.09
Optimal - objective value 347887.09
After Postsolve, objective 347887.09, infeasibilities - dual 50.795647 (2), primal 4.2010659e-07 (2)
Presolved model was optimal, full model needs cleaning up
0  Obj 347887.09 Dual inf 0.50795627 (2)
End of values pass after 2 iterations
2  Obj 347887.09
Optimal - objective value 347887.09
Optimal objective 347887.0926 - 1348 iterations time 0.492, Presolve 0.04
Total time (CPU seconds):       0.74   (Wallclock seconds):       0.67


INFO:linopy.constants: Optimization successful:
Status: ok
Termination condition: optimal
Solution: 2485 primals, 34397 duals
Objective: 3.48e+05
Solver model: not available
Solver message: Optimal - objective value 347887.09255214


INFO:pypsa.optimization.optimize:The shadow-prices of the constraints Generator-fix-p-lower, Generator-fix-p-upper, Line-fix-s-lower, Line-fix-s-upper, Transformer-fix-s-lower, Transformer-fix-s-upper, StorageUnit-fix-p_dispatch-lower, StorageUnit-fix-p_dispatch-upper, StorageUnit-fix-p_store-lower, StorageUnit-fix-p_store-upper, StorageUnit-fix-state_of_charge-lower, StorageUnit-fix-state_of_charge-upper, Kirchhoff-Voltage-Law, StorageUnit-energy_balance, Transformer-fix-s-lower-security, Transformer-fix-s-upper-security, Line-fix-s-lower-security, Line-fix-s-upper-security were not assigned to the network.

For the PF, set the P to the optimised P.

[5]:
network.generators_t.p_set = network.generators_t.p_set.reindex(
    columns=network.generators.index
)
network.generators_t.p_set.loc[now] = network.generators_t.p.loc[now]

network.storage_units_t.p_set = network.storage_units_t.p_set.reindex(
    columns=network.storage_units.index
)
network.storage_units_t.p_set.loc[now] = network.storage_units_t.p.loc[now]

Check no lines are overloaded with the linear contingency analysis

[6]:
p0_test = network.lpf_contingency(now, branch_outages=branch_outages)
p0_test
INFO:pypsa.pf:Performing linear load-flow on AC sub-network SubNetwork 0 for snapshot(s) DatetimeIndex(['2011-01-01'], dtype='datetime64[ns]', name='snapshot', freq=None)
WARNING:pypsa.contingency:No type given for 1, assuming it is a line
WARNING:pypsa.contingency:No type given for 2, assuming it is a line
WARNING:pypsa.contingency:No type given for 3, assuming it is a line
WARNING:pypsa.contingency:No type given for 4, assuming it is a line
WARNING:pypsa.contingency:No type given for 5, assuming it is a line
WARNING:pypsa.contingency:No type given for 6, assuming it is a line
WARNING:pypsa.contingency:No type given for 7, assuming it is a line
WARNING:pypsa.contingency:No type given for 8, assuming it is a line
WARNING:pypsa.contingency:No type given for 9, assuming it is a line
WARNING:pypsa.contingency:No type given for 10, assuming it is a line
WARNING:pypsa.contingency:No type given for 11, assuming it is a line
WARNING:pypsa.contingency:No type given for 12, assuming it is a line
WARNING:pypsa.contingency:No type given for 13, assuming it is a line
WARNING:pypsa.contingency:No type given for 14, assuming it is a line
WARNING:pypsa.contingency:No type given for 15, assuming it is a line
[6]:
base (Line, 1) (Line, 2) (Line, 3) (Line, 4) (Line, 5) (Line, 6) (Line, 7) (Line, 8) (Line, 9) (Line, 10) (Line, 11) (Line, 12) (Line, 13) (Line, 14) (Line, 15)
Transformer 2 -398.673556 -398.673556 -418.812169 -359.776331 -494.531592 -456.944270 -398.465080 -398.186340 -398.250984 -398.719309 -398.867086 -390.267046 -407.034894 -406.571210 -398.866763 -398.697282
5 883.055800 883.055800 898.249129 811.125660 988.177524 745.490073 883.034699 883.006486 883.013029 883.107970 883.277664 860.524528 886.291016 886.111604 883.277294 883.084018
10 -227.642977 -227.642977 -227.040368 -227.465351 -225.951447 -302.148218 -239.209405 -254.674181 -251.087689 -227.628693 -227.583800 -209.997527 42.837910 27.838187 -227.583898 -227.636786
12 -1211.646080 -1211.646080 -1211.821857 -1211.717451 -1212.306156 -1192.941429 -1191.974372 -1165.672511 -1171.772271 -1211.648632 -1211.656386 -1216.321290 -1479.256769 -1464.416214 -1211.656369 -1211.646922
13 41.861950 41.861950 41.500970 41.608206 39.441350 41.627203 41.832212 41.792450 41.801671 67.075992 5.594734 39.431473 43.269601 43.191538 5.655277 73.316136
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
Line 855 65.129873 65.129873 65.010504 65.078881 64.536480 64.147069 65.127057 65.123292 65.124165 65.140617 65.173879 64.753233 65.277580 65.269389 65.173805 65.134037
856 93.141666 93.141666 92.945010 93.049299 92.092939 91.247887 93.137330 93.131532 93.132876 93.160471 93.218668 92.505187 93.376630 93.363600 93.218540 93.148934
857 359.231588 359.231588 357.976182 359.455060 359.413098 372.664329 359.179306 359.109403 359.125615 359.237745 359.258511 356.942347 361.344964 361.227765 359.258466 359.235642
858 33.680485 33.680485 33.624796 33.654329 33.383509 33.144210 33.679257 33.677615 33.677996 33.685810 33.702290 33.500249 33.747022 33.743332 33.702254 33.682543
859 182.371439 182.371439 181.969317 182.274409 181.001252 181.315362 182.360064 182.344855 182.348382 182.396656 182.474891 181.282508 182.909996 182.880130 182.474718 182.381371

948 rows × 16 columns

Check loading as per unit of s_nom in each contingency

[7]:
max_loading = (
    abs(p0_test.divide(network.passive_branches().s_nom, axis=0)).describe().loc["max"]
)
max_loading
[7]:
base          1.0
(Line, 1)     1.0
(Line, 2)     1.0
(Line, 3)     1.0
(Line, 4)     1.0
(Line, 5)     1.0
(Line, 6)     1.0
(Line, 7)     1.0
(Line, 8)     1.0
(Line, 9)     1.0
(Line, 10)    1.0
(Line, 11)    1.0
(Line, 12)    1.0
(Line, 13)    1.0
(Line, 14)    1.0
(Line, 15)    1.0
Name: max, dtype: float64
[8]:
np.allclose(max_loading, np.ones((len(max_loading))))
[8]:
True