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.29.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 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')
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 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)
INFO:linopy.model: Solve problem using Highs solver
INFO:linopy.io: Writing time: 0.29s
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:
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, Transformer-fix-s-lower-security-for-Line-outage-in-SubNetwork 0, Transformer-fix-s-upper-security-for-Line-outage-in-SubNetwork 0, Line-fix-s-lower-security-for-Line-outage-in-SubNetwork 0, Line-fix-s-upper-security-for-Line-outage-in-SubNetwork 0 were not assigned to the network.
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, 32948 nonzeros 0s
12730 rows, 1423 cols, 28495 nonzeros 0s
6718 rows, 1270 cols, 16332 nonzeros 0s
6717 rows, 1266 cols, 16327 nonzeros 0s
Presolve : Reductions: rows 6717(-27680); columns 1266(-1219); elements 16327(-42140)
Solving the presolved LP
Using EKK dual simplex solver - serial
Iteration Objective Infeasibilities num(sum)
0 0.0000000000e+00 Ph1: 0(0) 0s
703 3.4788709255e+05 Pr: 0(0) 0s
Solving the original LP from the solution after postsolve
Model status : Optimal
Simplex iterations: 703
Objective value : 3.4788709255e+05
HiGHS run time : 0.18
Writing the solution to /tmp/linopy-solve-2f10n4hk.sol
[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) | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Transformer | 2 | -398.673565 | -398.673565 | -418.812178 | -359.776340 | -494.531601 | -456.944280 | -398.465090 | -398.186350 | -398.250993 | -398.719318 | -398.867096 | -390.267056 | -407.034903 | -406.571219 | -398.866773 | -398.697291 |
| 5 | 883.055798 | 883.055798 | 898.249126 | 811.125658 | 988.177522 | 745.490070 | 883.034697 | 883.006484 | 883.013027 | 883.107968 | 883.277662 | 860.524526 | 886.291013 | 886.111602 | 883.277291 | 883.084016 | |
| 10 | -227.642971 | -227.642971 | -227.040362 | -227.465346 | -225.951441 | -302.148212 | -239.209400 | -254.674176 | -251.087684 | -227.628687 | -227.583794 | -209.997522 | 42.837917 | 27.838193 | -227.583893 | -227.636780 | |
| 12 | -1211.646078 | -1211.646078 | -1211.821855 | -1211.717450 | -1212.306155 | -1192.941427 | -1191.974369 | -1165.672509 | -1171.772269 | -1211.648631 | -1211.656385 | -1216.321289 | -1479.256768 | -1464.416213 | -1211.656367 | -1211.646921 | |
| 13 | 41.861950 | 41.861950 | 41.500969 | 41.608205 | 39.441350 | 41.627203 | 41.832211 | 41.792449 | 41.801671 | 67.075991 | 5.594733 | 39.431473 | 43.269600 | 43.191538 | 5.655277 | 73.316135 | |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| Line | 855 | 65.129874 | 65.129874 | 65.010504 | 65.078881 | 64.536480 | 64.147069 | 65.127058 | 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.049300 | 92.092940 | 91.247887 | 93.137330 | 93.131532 | 93.132877 | 93.160471 | 93.218669 | 92.505188 | 93.376631 | 93.363601 | 93.218540 | 93.148934 | |
| 857 | 359.231589 | 359.231589 | 357.976182 | 359.455061 | 359.413098 | 372.664330 | 359.179307 | 359.109404 | 359.125615 | 359.237746 | 359.258512 | 356.942348 | 361.344965 | 361.227766 | 359.258467 | 359.235643 | |
| 858 | 33.680485 | 33.680485 | 33.624797 | 33.654329 | 33.383510 | 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.969318 | 182.274410 | 181.001252 | 181.315363 | 182.360064 | 182.344856 | 182.348383 | 182.396657 | 182.474892 | 181.282509 | 182.909996 | 182.880130 | 182.474719 | 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