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, 1], 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.1/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.1/lib/python3.10/site-packages/pypsa/contingency.py:70: RuntimeWarning: divide by zero encountered in true_divide
  denominator = csr_matrix((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.0945
  Upper bound: 347887.0945
  Number of objectives: 1
  Number of constraints: 31362
  Number of variables: 2486
  Number of nonzeros: 441
  Sense: minimize
# ----------------------------------------------------------
#   Solver Information
# ----------------------------------------------------------
Solver:
- Status: ok
  User time: -1.0
  System time: 0.66
  Wallclock time: 0.62
  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: 943
  Error rc: 0
  Time: 0.6299810409545898
# ----------------------------------------------------------
#   Solution Information
# ----------------------------------------------------------
Solution:
- number of solutions: 0
  number of solutions displayed: 0
/home/docs/checkouts/readthedocs.org/user_builds/pypsa/conda/v0.19.1/lib/python3.10/site-packages/pypsa/opf.py:1293: 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({c.name:

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.1/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.1/lib/python3.10/site-packages/pypsa/contingency.py:70: RuntimeWarning: divide by zero encountered in true_divide
  denominator = csr_matrix((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)
Transformer 2 -398.673560 -398.673560 -418.812173 -359.776335 -494.531596 -456.944274 -398.465085 -398.186344 -398.250988 -398.719313 -398.867090 -390.267050 -407.034898 -406.571214 -398.866767 -398.697286
5 883.055800 883.055800 898.249129 811.125660 988.177524 745.490072 883.034699 883.006486 883.013029 883.107970 883.277664 860.524528 886.291016 886.111604 883.277294 883.084018
10 -227.642978 -227.642978 -227.040369 -227.465353 -225.951448 -302.148219 -239.209406 -254.674183 -251.087690 -227.628694 -227.583801 -209.997529 42.837910 27.838186 -227.583900 -227.636787
12 -1211.646079 -1211.646079 -1211.821856 -1211.717451 -1212.306156 -1192.941428 -1191.974372 -1165.672511 -1171.772271 -1211.648632 -1211.656386 -1216.321290 -1479.256769 -1464.416215 -1211.656368 -1211.646922
13 41.862034 41.862034 41.501053 41.608289 39.441434 41.627286 41.832295 41.792533 41.801754 67.076083 5.594816 39.431557 43.269684 43.191622 5.655360 73.316233
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
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.132876 93.160471 93.218669 92.505187 93.376631 93.363601 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.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.315362 182.360064 182.344855 182.348383 182.396657 182.474891 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