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.41s
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-98uzs_jn.lp -solve -solu /tmp/linopy-solve-xmh19enf.sol (default strategy 1)
Option for printingOptions changed from normal to all
Presolve 14014 (-20383) rows, 1064 (-1421) columns and 30583 (-28015) elements
Perturbing problem by 0.001% of 43610.233 - largest nonzero change 0.00099680941 ( 0.0017118301%) - largest zero change 0.00085667691
0  Obj 24484.182 Primal inf 6178486.4 (3094) Dual inf 1209.4693 (1)
295  Obj 1778.08 Primal inf 8.4078385e+08 (3222)
590  Obj 1787.6687 Primal inf 8.4951541e+08 (4176)
766  Obj 1790.344 Primal inf 14830599 (1362)
899  Obj 7683.4404 Primal inf 1.0399024e+11 (4186)
1007  Obj 318216.74 Primal inf 8.02202e+08 (491)
1101  Obj 347891.06
1101  Obj 347887.1 Dual inf 0.00010307524 (1)
1102  Obj 347887.09
Optimal - objective value 347887.09
After Postsolve, objective 347887.09, infeasibilities - dual 50.795647 (2), primal 4.2010636e-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 - 1104 iterations time 0.602, Presolve 0.05
Total time (CPU seconds):       0.84   (Wallclock seconds):       0.89


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, Line-fix-s-lower-security, Line-fix-s-upper-security, Transformer-fix-s-lower-security, Transformer-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)
Line 1 -68.803467 0.000000 -68.803467 -68.803467 -68.803467 -68.803467 -68.803467 -68.803467 -68.803467 -68.803467 -68.803467 -68.803467 -68.803467 -68.803467 -68.803467 -68.803467
2 190.707301 190.707301 0.000000 212.790075 13.505590 -122.706578 190.839662 191.016634 190.975592 190.815172 191.158967 154.292844 190.026645 190.064391 191.158213 190.758722
3 325.824501 325.824501 334.145558 0.000000 383.397389 250.482737 325.812945 325.797493 325.801077 325.853073 325.946011 313.484612 327.596359 327.498099 325.945808 325.839956
4 -750.815242 -750.815242 -724.824609 -773.225256 0.000000 -487.570143 -750.782645 -750.739061 -750.749169 -750.921336 -751.264948 -708.345999 -756.662628 -756.338357 -751.264197 -750.871173
5 1069.888703 1069.888703 1045.960039 1054.623198 932.859988 0.000000 1067.815639 1065.043868 1065.686680 1069.894059 1069.913761 1156.214765 1134.263575 1130.693617 1069.913719 1069.893831
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
Transformer 404 3.995926 3.995926 3.998271 3.994990 3.992286 3.984331 3.995920 3.995911 3.995913 3.988666 3.966848 3.993604 3.996373 3.996348 3.966897 3.993757
413 94.603092 94.603092 94.650025 94.624896 94.833998 94.575138 94.606061 94.610030 94.609110 94.624523 94.688311 94.841071 94.467175 94.474713 94.688169 94.608894
421 52.977690 52.977690 53.170624 53.069459 53.942929 52.873378 52.990054 53.006586 53.002752 53.084125 53.401446 53.971018 52.411840 52.443219 53.400739 53.007013
450 82.518670 82.518670 82.460261 82.496273 82.250384 82.130705 82.517162 82.515147 82.515615 82.523638 82.539026 82.337607 82.595019 82.590785 82.538992 82.520601
458 83.475451 83.475451 83.415986 83.452656 83.202370 83.080708 83.473916 83.471864 83.472340 83.480509 83.496173 83.291122 83.553192 83.548881 83.496138 83.477417

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