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.34.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 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 Highs solver
INFO:linopy.io: Writing time: 0.23s
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
Running HiGHS 1.10.0 (git hash: fd86653): Copyright (c) 2025 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
LP linopy-problem-3vu6_xjp has 34397 rows; 2485 cols; 58467 nonzeros
Coefficient ranges:
Matrix [1e-09, 2e+02]
Cost [3e+00, 1e+02]
Bound [0e+00, 0e+00]
RHS [1e-07, 7e+03]
Presolving model
15330 rows, 1653 cols, 33584 nonzeros 0s
12730 rows, 1423 cols, 28489 nonzeros 0s
6717 rows, 1269 cols, 16323 nonzeros 0s
Dependent equations search running on 522 equations with time limit of 1000.00s
Dependent equations search removed 0 rows and 0 nonzeros in 0.00s (limit = 1000.00s)
6716 rows, 1265 cols, 16318 nonzeros 0s
Presolve : Reductions: rows 6716(-27681); columns 1265(-1220); elements 16318(-42149)
Solving the presolved LP
Using EKK dual simplex solver - serial
Iteration Objective Infeasibilities num(sum)
0 0.0000000000e+00 Ph1: 0(0) 0s
707 3.4788709255e+05 Pr: 0(0) 0s
Solving the original LP from the solution after postsolve
Model name : linopy-problem-3vu6_xjp
Model status : Optimal
Simplex iterations: 707
Objective value : 3.4788709255e+05
Relative P-D gap : 1.9743486133e-14
HiGHS run time : 0.13
Writing the solution to /tmp/linopy-solve-jx82e44s.sol
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-<pypsa.networks.SubNetwork object at 0x711636f82510>, Transformer-fix-s-upper-security-for-Line-outage-in-<pypsa.networks.SubNetwork object at 0x711636f82510>, Line-fix-s-lower-security-for-Line-outage-in-<pypsa.networks.SubNetwork object at 0x711636f82510>, Line-fix-s-upper-security-for-Line-outage-in-<pypsa.networks.SubNetwork object at 0x711636f82510> 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 <pypsa.networks.SubNetwork object at 0x711636dac690> 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