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 PyPSA from older version of PyPSA than current version.
Please read the release notes at https://pypsa.readthedocs.io/en/latest/release_notes.html
carefully to prepare your network for import.
Currently used PyPSA version [0, 19, 3], imported network file PyPSA version [0, 17, 1].

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
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/conda/v0.19.3/lib/python3.10/site-packages/scipy/sparse/linalg/_dsolve/linsolve.py:322: SparseEfficiencyWarning: splu requires CSC matrix format
  warn('splu requires CSC matrix format', SparseEfficiencyWarning)
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/conda/v0.19.3/lib/python3.10/site-packages/pypsa/contingency.py:78: RuntimeWarning: divide by zero encountered in true_divide
  (1 / (1 - np.diag(branch_PTDF)), (r_[:num_branches], r_[:num_branches]))
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.0925
  Upper bound: 347887.0925
  Number of objectives: 1
  Number of constraints: 31362
  Number of variables: 2486
  Number of nonzeros: 442
  Sense: minimize
# ----------------------------------------------------------
#   Solver Information
# ----------------------------------------------------------
Solver:
- Status: ok
  User time: -1.0
  System time: 0.72
  Wallclock time: 0.68
  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: 1064
  Error rc: 0
  Time: 0.6944501399993896
# ----------------------------------------------------------
#   Solution Information
# ----------------------------------------------------------
Solution:
- number of solutions: 0
  number of solutions displayed: 0
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/conda/v0.19.3/lib/python3.10/site-packages/pypsa/opf.py:1971: FutureWarning: Using the level keyword in DataFrame and Series aggregations is deprecated and will be removed in a future version. Use groupby instead. df.sum(level=1) should use df.groupby(level=1).sum().
  pd.concat(

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)
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/conda/v0.19.3/lib/python3.10/site-packages/scipy/sparse/linalg/_dsolve/linsolve.py:322: SparseEfficiencyWarning: splu requires CSC matrix format
  warn('splu requires CSC matrix format', SparseEfficiencyWarning)
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/conda/v0.19.3/lib/python3.10/site-packages/pypsa/contingency.py:78: RuntimeWarning: divide by zero encountered in true_divide
  (1 / (1 - np.diag(branch_PTDF)), (r_[:num_branches], r_[:num_branches]))
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.803446 0.000000 -68.803446 -68.803446 -68.803446 -68.803446 -68.803446 -68.803446 -68.803446 -68.803446 -68.803446 -68.803446 -68.803446 -68.803446 -68.803446 -68.803446
2 190.707291 190.707291 0.000000 212.790065 13.505582 -122.706586 190.839652 191.016624 190.975581 190.815162 191.158957 154.292834 190.026634 190.064381 191.158203 190.758712
3 325.824498 325.824498 334.145554 0.000000 383.397385 250.482735 325.812942 325.797490 325.801074 325.853070 325.946008 313.484609 327.596356 327.498096 325.945806 325.839953
4 -750.815235 -750.815235 -724.824604 -773.225249 0.000000 -487.570139 -750.782638 -750.739054 -750.749162 -750.921330 -751.264941 -708.345993 -756.662622 -756.338350 -751.264190 -750.871166
5 1069.888694 1069.888694 1045.960031 1054.623189 932.859981 0.000000 1067.815630 1065.043859 1065.686670 1069.894050 1069.913752 1156.214755 1134.263566 1130.693608 1069.913710 1069.893822
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
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.942928 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.517162 82.515147 82.515615 82.523638 82.539026 82.337607 82.595019 82.590785 82.538992 82.520601
458 83.475451 83.475451 83.415986 83.452656 83.202370 83.080708 83.473916 83.471864 83.472340 83.480509 83.496173 83.291122 83.553192 83.548881 83.496138 83.477417

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