MatProcessor for Sensitivity Matrices Calculation#
This notebook demonstrates how to build sensitivity matrices:
Power Transfer Distribution Factors (PTDF)
Line Outage Distribution Factors (LODF)
Outage Distribution Transfer Factors (ODTF)
[1]:
import numpy as np
import ams
[2]:
sp = ams.load(ams.get_case('matpower/case300.m'),
no_output=True)
PTDF#
The \(\mathbf{PTDF}[i, j]\) represents the additional flow on \(\text{Line}_i\) resulting from a 1 p.u. power injection at \(\text{Bus}_j\).
PTDF can be calculated using the build_ptdf method of the MatProcessor class. The calculated matrix will be stored in the MParam attribute PTDF. The method also returns the PTDF matrix.
Note: When the memory is limited to calculate PTDF at once, set
incremental=Trueto incrementally calculate the PTDF matrix.
[3]:
PTDF = sp.mats.build_ptdf()
Building system matrices
The PTDF matrix is in the shape of (n_lines, n_buses).
[4]:
PTDF.shape
[4]:
(411, 300)
LODF#
The \(\mathbf{LODF}[i, j]\) represents the additional flow on \(\text{Line}_i\) resulting from a 1 p.u. power reduction on \(\text{Line}_j\) caused by the outage of \(\text{Line}_j\).
Similarly, LODF can also be calculated using the build_lodf method of the MatProcessor class. The calculated matrix will be stored in the MParam attribute LODF.
[5]:
LODF = sp.mats.build_lodf()
The LODF matrix is in the shape of (n_lines, n_lines).
[6]:
LODF.shape
[6]:
(411, 411)
OTDF#
The \(\mathbf{OTDF}_k[i, j]\) represents the additional flow on \(\text{Line}_i\) resulting from a 1 p.u. power injection at \(\text{Bus}_j\) during the outage of \(\text{Line}_k\).
Keep in mind that OTDF is linked to specific line outages, which means there can be multiple OTDF matrices corresponding to different line outages.
[7]:
OTDF7 = sp.mats.build_otdf('Line_7')
The OTDF matrix is in the shape of (n_lines, n_buses).
[8]:
OTDF7.shape
[8]:
(411, 300)
Quick Contingency Assessment#
These matrices are useful for quick contingency assessment.
[9]:
sp.DCOPF.run(solver='CLARABEL')
Parsing OModel for <DCOPF>
Evaluating OModel for <DCOPF>
Finalizing OModel for <DCOPF>
<DCOPF> solved as optimal in 0.0372 seconds, converged in 13 iterations with CLARABEL.
[9]:
True
[10]:
Pbus = sp.DCOPF.Cg.v @ sp.DCOPF.pg.v
Pbus -= sp.DCOPF.Cl.v @ sp.DCOPF.pd.v
Pbus -= sp.DCOPF.Csh.v @ sp.DCOPF.gsh.v
plf = PTDF @ Pbus
[11]:
np.allclose(sp.DCOPF.plf.v, plf, atol=0.001)
[11]:
True
The line flow plf here is calculed using the PTDF matrix, and it is close to the line flow from the DCOPF.
Next, let’s check it again when Line 7 is outaged.
[12]:
sp.Line.alter(src='u', idx='Line_7', value=0)
[13]:
sp.DCOPF.update()
Building system matrices
<DCOPF> reinit OModel due to non-parametric change.
Evaluating OModel for <DCOPF>
Finalizing OModel for <DCOPF>
[13]:
True
[14]:
sp.DCOPF.run(solver='CLARABEL')
<DCOPF> solved as optimal in 0.0444 seconds, converged in 13 iterations with CLARABEL.
[14]:
True
[15]:
Pbus2 = sp.DCOPF.Cg.v @ sp.DCOPF.pg.v
Pbus2 -= sp.DCOPF.Cl.v @ sp.DCOPF.pd.v
Pbus2 -= sp.DCOPF.Csh.v @ sp.DCOPF.gsh.v
plf2 = OTDF7 @ Pbus2
[16]:
np.allclose(sp.DCOPF.plf.v, plf2, atol=0.001)
[16]:
True
We observe that the line flow calculated using OTDF closely matches the line flow obtained from DCOPF.
Since matrix calculations are significantly faster than DCOPF, they are frequently used for quick contingency assessments.