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
[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.22.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.sclopf(now, branch_outages=branch_outages, solver_name="cbc")
INFO:pypsa.opf:Building pyomo model using `kirchhoff` formulation
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
INFO:pypsa.opf:Solving model using cbc
INFO:pypsa.opf:Optimization successful
# ==========================================================
# = Solver Results =
# ==========================================================
# ----------------------------------------------------------
# Problem Information
# ----------------------------------------------------------
Problem:
- Name: unknown
Lower bound: 347887.0926
Upper bound: 347887.0926
Number of objectives: 1
Number of constraints: 31362
Number of variables: 2486
Number of nonzeros: 440
Sense: minimize
# ----------------------------------------------------------
# Solver Information
# ----------------------------------------------------------
Solver:
- Status: ok
User time: -1.0
System time: 0.72
Wallclock time: 0.69
Termination condition: optimal
Termination message: Model was solved to optimality (subject to tolerances), and an optimal solution is available.
Statistics:
Branch and bound:
Number of bounded subproblems: None
Number of created subproblems: None
Black box:
Number of iterations: 1114
Error rc: 0
Time: 0.7016324996948242
# ----------------------------------------------------------
# Solution Information
# ----------------------------------------------------------
Solution:
- number of solutions: 0
number of solutions displayed: 0
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.673557 | -398.673557 | -418.812170 | -359.776332 | -494.531592 | -456.944271 | -398.465081 | -398.186341 | -398.250985 | -398.719309 | -398.867087 | -390.267047 | -407.034895 | -406.571210 | -398.866764 | -398.697283 |
5 | 883.055800 | 883.055800 | 898.249129 | 811.125660 | 988.177524 | 745.490073 | 883.034699 | 883.006486 | 883.013029 | 883.107970 | 883.277664 | 860.524527 | 886.291015 | 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