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 numpy as np

import pypsa
[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.30.0. 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)
WARNING:pypsa.consistency:The following generators have carriers which are not defined:
Index(['1 Gas', '1 Hard Coal', '102 Gas', '108 Run of River', '108 Waste',
       '111 Gas', '112 Gas', '112 Run of River', '114 Hard Coal',
       '115 Brown Coal',
       ...
       '382_220kV Solar', '384_220kV Solar', '385_220kV Solar',
       '391_220kV Solar', '403_220kV Solar', '404_220kV Solar',
       '413_220kV Solar', '421_220kV Solar', '450_220kV Solar',
       '458_220kV Solar'],
      dtype='object', name='Generator', length=1423)
WARNING:pypsa.consistency:The following lines have carriers which are not defined:
Index(['1', '2', '3', '4', '5', '6', '7', '8', '9', '10',
       ...
       '850', '851', '852', '853', '854', '855', '856', '857', '858', '859'],
      dtype='object', name='Line', length=852)
WARNING:pypsa.consistency: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')
WARNING:pypsa.consistency:The following buses have carriers which are not defined:
Index(['1', '2', '3', '4', '5', '6', '7', '8', '9', '10',
       ...
       '382_220kV', '384_220kV', '385_220kV', '391_220kV', '403_220kV',
       '404_220kV', '413_220kV', '421_220kV', '450_220kV', '458_220kV'],
      dtype='object', name='Bus', length=585)
WARNING:pypsa.consistency:The following storage_units have carriers which are not defined:
Index(['100_220kV Pumped Hydro', '114 Pumped Hydro', '121 Pumped Hydro',
       '140 Pumped Hydro', '141 Pumped Hydro', '158 Pumped Hydro',
       '166 Pumped Hydro', '205 Pumped Hydro', '228 Pumped Hydro',
       '235 Pumped Hydro', '25 Pumped Hydro', '266 Pumped Hydro',
       '268 Pumped Hydro', '279_220kV Pumped Hydro', '293 Pumped Hydro',
       '320 Pumped Hydro', '32_220kV Pumped Hydro', '333 Pumped Hydro',
       '342 Pumped Hydro', '343 Pumped Hydro', '362_220kV Pumped Hydro',
       '368 Pumped Hydro', '374 Pumped Hydro', '379 Pumped Hydro',
       '389 Pumped Hydro', '397 Pumped Hydro', '423 Pumped Hydro',
       '424 Pumped Hydro', '479 Pumped Hydro', '481 Pumped Hydro',
       '493 Pumped Hydro', '51 Pumped Hydro', '63 Pumped Hydro',
       '66 Pumped Hydro', '72 Pumped Hydro', '78_220kV Pumped Hydro',
       '8 Pumped Hydro', '98 Pumped Hydro'],
      dtype='object', name='StorageUnit')
INFO:linopy.model: Solve problem using Highs solver
INFO:linopy.io: Writing time: 0.28s
INFO:linopy.solvers:Log file at /tmp/highs.log
Running HiGHS 1.7.2 (git hash: 184e327): Copyright (c) 2024 HiGHS under MIT licence terms
WARNING: LP matrix packed vector contains 4 |values| in [9.11236e-10, 9.11239e-10] less than or equal to 1e-09: ignored
Coefficient ranges:
  Matrix [1e-09, 2e+02]
  Cost   [3e+00, 1e+02]
  Bound  [0e+00, 0e+00]
  RHS    [1e-07, 7e+03]
Presolving model
15012 rows, 1653 cols, 33045 nonzeros  0s
12704 rows, 1423 cols, 28556 nonzeros  0s
6654 rows, 1268 cols, 16314 nonzeros  0s
6638 rows, 1263 cols, 16280 nonzeros  0s
Presolve : Reductions: rows 6638(-27759); columns 1263(-1222); elements 16280(-42284)
Solving the presolved LP
Using EKK dual simplex solver - serial
  Iteration        Objective     Infeasibilities num(sum)
          0     0.0000000000e+00 Ph1: 0(0) 0s
        725     3.4788709255e+05 Pr: 0(0) 0s
        725     3.4788709255e+05 Pr: 0(0) 0s
Solving the original LP from the solution after postsolve
Model   status      : Optimal
Simplex   iterations: 725
Objective value     :  3.4788709255e+05
HiGHS run time      :          0.17
Writing the solution to /tmp/linopy-solve-xfx7gxvb.sol
INFO:linopy.constants: Optimization successful:
Status: ok
Termination condition: optimal
Solution: 2485 primals, 34397 duals
Objective: 3.48e+05
Solver model: available
Solver message: optimal

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-for-Line-outage-in-SubNetwork 0, Line-fix-s-upper-security-for-Line-outage-in-SubNetwork 0, Transformer-fix-s-lower-security-for-Line-outage-in-SubNetwork 0, Transformer-fix-s-upper-security-for-Line-outage-in-SubNetwork 0 were not assigned to the network.
[4]:
('ok', 'optimal')

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.803462 0.000000 -68.803462 -68.803462 -68.803462 -68.803462 -68.803462 -68.803462 -68.803462 -68.803462 -68.803462 -68.803462 -68.803462 -68.803462 -68.803462 -68.803462
2 190.707297 190.707297 0.000000 212.790071 13.505587 -122.706583 190.839658 191.016630 190.975588 190.815168 191.158964 154.292840 190.026641 190.064387 191.158210 190.758718
3 325.824500 325.824500 334.145556 0.000000 383.397388 250.482736 325.812944 325.797492 325.801075 325.853072 325.946010 313.484611 327.596357 327.498098 325.945807 325.839954
4 -750.815240 -750.815240 -724.824608 -773.225254 0.000000 -487.570141 -750.782643 -750.739059 -750.749167 -750.921335 -751.264946 -708.345998 -756.662627 -756.338355 -751.264195 -750.871171
5 1069.888707 1069.888707 1045.960042 1054.623201 932.859992 0.000000 1067.815642 1065.043871 1065.686683 1069.894062 1069.913764 1156.214767 1134.263578 1130.693620 1069.913723 1069.893834
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
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.517163 82.515147 82.515615 82.523638 82.539026 82.337607 82.595020 82.590786 82.538992 82.520601
458 83.475451 83.475451 83.415986 83.452656 83.202370 83.080708 83.473917 83.471864 83.472340 83.480509 83.496173 83.291123 83.553192 83.548881 83.496138 83.477418

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