This notebook demonstrates how to build sensitivity matrices:
Power Transfer Distribution Factors (PTDF)
Line Outage Distribution Factors (LODF)
Outage Distribution Transfer Factors (ODTF)
import numpy as np
import ams
sp = ams.load(ams.get_case('matpower/case300.m'),
no_output=True)
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=True
to incrementally calculate the PTDF matrix.
PTDF = sp.mats.build_ptdf()
Building system matrices
The PTDF matrix is in the shape of (n_lines, n_buses).
PTDF.shape
(411, 300)
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
.
LODF = sp.mats.build_lodf()
The LODF matrix is in the shape of (n_lines, n_lines).
LODF.shape
(411, 411)
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.
OTDF7 = sp.mats.build_otdf('Line_7')
The OTDF matrix is in the shape of (n_lines, n_buses).
OTDF7.shape
(411, 300)
These matrices are useful for quick contingency assessment.
sp.DCOPF.run(solver='CLARABEL')
Parsing OModel for <DCOPF> Evaluating OModel for <DCOPF> Finalizing OModel for <DCOPF> <DCOPF> solved as optimal in 0.0247 seconds, converged in 13 iterations with CLARABEL.
True
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
np.allclose(sp.DCOPF.plf.v, plf, atol=0.001)
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.
sp.Line.alter(src='u', idx='Line_7', value=0)
sp.DCOPF.update()
Building system matrices <DCOPF> reinit OModel due to non-parametric change. Evaluating OModel for <DCOPF> Finalizing OModel for <DCOPF>
True
sp.DCOPF.run(solver='CLARABEL')
<DCOPF> solved as optimal in 0.0223 seconds, converged in 13 iterations with CLARABEL.
True
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
np.allclose(sp.DCOPF.plf.v, plf2, atol=0.001)
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.