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.21.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.0875
  Upper bound: 347887.0875
  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.7
  Wallclock time: 0.71
  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: 1124
  Error rc: 0
  Time: 0.7294807434082031
# ----------------------------------------------------------
#   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)
Line 1 -68.803422 0.000000 -68.803422 -68.803422 -68.803422 -68.803422 -68.803422 -68.803422 -68.803422 -68.803422 -68.803422 -68.803422 -68.803422 -68.803422 -68.803422 -68.803422
2 190.707277 190.707277 0.000000 212.790051 13.505569 -122.706595 190.839638 191.016610 190.975568 190.815148 191.158943 154.292820 190.026621 190.064367 191.158189 190.758698
3 325.824496 325.824496 334.145551 0.000000 383.397383 250.482733 325.812939 325.797488 325.801071 325.853068 325.946006 313.484607 327.596353 327.498094 325.945803 325.839950
4 -750.815231 -750.815231 -724.824601 -773.225244 0.000000 -487.570138 -750.782634 -750.739050 -750.749158 -750.921325 -751.264937 -708.345988 -756.662617 -756.338346 -751.264186 -750.871162
5 1069.888680 1069.888680 1045.960019 1054.623175 932.859967 0.000000 1067.815615 1065.043845 1065.686656 1069.894036 1069.913738 1156.214741 1134.263550 1130.693592 1069.913696 1069.893808
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
Transformer 404 3.995927 3.995927 3.998271 3.994990 3.992287 3.984332 3.995921 3.995912 3.995914 3.988667 3.966849 3.993605 3.996374 3.996349 3.966898 3.993758
413 94.603093 94.603093 94.650025 94.624896 94.833999 94.575138 94.606061 94.610031 94.609110 94.624524 94.688312 94.841071 94.467176 94.474713 94.688169 94.608894
421 52.977690 52.977690 53.170624 53.069459 53.942928 52.873378 52.990054 53.006586 53.002752 53.084125 53.401446 53.971017 52.411840 52.443219 53.400739 53.007013
450 82.518656 82.518656 82.460247 82.496259 82.250370 82.130691 82.517149 82.515133 82.515601 82.523624 82.539012 82.337593 82.595006 82.590772 82.538978 82.520587
458 83.475437 83.475437 83.415972 83.452641 83.202356 83.080694 83.473902 83.471850 83.472326 83.480495 83.496159 83.291108 83.553178 83.548867 83.496124 83.477403

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