DC Optimal Power Flow
B-theta Formulation
The DC OPF minimizes generation cost subject to linearized power flow constraints using the susceptance-weighted Laplacian:
\[\min_{g, \theta, f, \text{psh}} \quad g^\top C_q g + c_l^\top g + c_{\text{shed}}^\top \text{psh} + \frac{\tau^2}{2} \|f\|^2\]
subject to:
\[\begin{aligned} G_{\text{inc}} g + \text{psh} - d &= B \theta & (\nu_{\text{bal}}) \\ f &= W A \theta & (\nu_{\text{flow}}) \\ -f_{\max} \leq f &\leq f_{\max} & (\lambda_{\text{lb}}, \lambda_{\text{ub}}) \\ g_{\min} \leq g &\leq g_{\max} & (\rho_{\text{lb}}, \rho_{\text{ub}}) \\ 0 \leq \text{psh} &\leq d_+ & (\mu_{\text{lb}}, \mu_{\text{ub}}) \\ \mathrm{sw} \circ \alpha_{\min} \leq \mathrm{sw} \circ A\theta &\leq \mathrm{sw} \circ \alpha_{\max} & (\gamma_{\text{lb}}, \gamma_{\text{ub}}) \\ \theta_{\text{refs}} &= 0 & (\eta_{\text{ref}}) \end{aligned}\]
where:
- $B = A^\top \operatorname{diag}(-b \circ \mathrm{sw}) \, A$ is the susceptance-weighted Laplacian
- $W = \operatorname{diag}(-b \circ \mathrm{sw})$ is the branch weight matrix
- $A$ is the $m \times n$ incidence matrix (branches × buses)
- $G_{\text{inc}}$ is the $n \times k$ generator-bus incidence matrix
- $C_q = \operatorname{diag}(c_q)$ contains quadratic cost coefficients
- $c_l$ contains linear cost coefficients
- $c_{\text{shed}}$ is the load shedding cost vector
- $d_+ = \max(d, 0)$ is the curtailable portion of signed net demand; negative net demand remains in power balance as an injection
- $\tau$ is a small regularization parameter for numerical conditioning
- the angle difference limits are gated by $\mathrm{sw}$ so an open branch ($\mathrm{sw}_e = 0$) imposes no limit; the factor cancels for a branch in service ($\mathrm{sw}_e = 1$)
- $\text{refs}$ contains one reference bus per energized island, including isolated buses. The choice is deterministic: the configured $\text{ref_bus}$ is the reference for its island, and every other island uses its lowest sequential bus index
The built OPF model stores one reference constraint for each entry of $\text{refs}$. Consequently, topology changes that can alter the energized island partition must rebuild the model: use update_switching! for switch changes, and rebuild the DCOPFProblem after direct susceptance edits that move a branch across zero.
KKT System for Implicit Differentiation
OPF sensitivities are computed via the implicit function theorem applied to the KKT conditions. At an optimal solution $z^*$, the KKT residual satisfies $K(z^*, p) = 0$ where $z$ collects all primal and dual variables and $p$ is a parameter.
By the implicit function theorem:
\[\frac{dz}{dp} = -\left(\frac{\partial K}{\partial z}\right)^{-1} \frac{\partial K}{\partial p}\]
KKT Variable Ordering
The KKT variable vector $z$ is structured as:
\[z = [\theta, g, f, \text{psh}, \lambda_{\text{lb}}, \lambda_{\text{ub}}, \gamma_{\text{lb}}, \gamma_{\text{ub}}, \rho_{\text{lb}}, \rho_{\text{ub}}, \mu_{\text{lb}}, \mu_{\text{ub}}, \nu_{\text{bal}}, \nu_{\text{flow}}, \eta_{\text{ref}}]\]
with total dimension $5n + 6m + 3k + n_{\text{ref}}$.
KKT Conditions
The KKT residual $K(z, p)$ consists of:
- Stationarity w.r.t. $\theta$: $B^\top \nu_{\text{bal}} + (WA)^\top \nu_{\text{flow}} + E_{\text{ref}} \eta_{\text{ref}} + A^\top \operatorname{diag}(\mathrm{sw}) (\gamma_{\text{ub}} - \gamma_{\text{lb}}) = 0$, where $E_{\text{ref}} \in \mathbb{R}^{n \times n_{\text{ref}}}$ selects the per-island reference buses
- Stationarity w.r.t. $g$: $2 C_q g + c_l - G_{\text{inc}}^\top \nu_{\text{bal}} - \rho_{\text{lb}} + \rho_{\text{ub}} = 0$
- Stationarity w.r.t. $f$: $\tau^2 f - \nu_{\text{flow}} - \lambda_{\text{lb}} + \lambda_{\text{ub}} = 0$
- Stationarity w.r.t. psh: $c_{\text{shed}} - \nu_{\text{bal}} - \mu_{\text{lb}} + \mu_{\text{ub}} = 0$
- Complementary slackness (flow bounds): $\lambda_{\text{lb}} \circ (f + f_{\max}) = 0$, $\lambda_{\text{ub}} \circ (f_{\max} - f) = 0$
5b. Complementary slackness (angle differences): $\gamma_{\text{lb}} \circ \mathrm{sw} \circ (A\theta - \alpha_{\min}) = 0$, $\gamma_{\text{ub}} \circ \mathrm{sw} \circ (\alpha_{\max} - A\theta) = 0$ 5c. Complementary slackness (generation/shedding bounds): $\rho \circ (\cdot) = 0$, $\mu \circ (\cdot) = 0$
- Primal feasibility: $G_{\text{inc}} g + \text{psh} - d - B\theta = 0$
- Flow definition: $f - WA\theta = 0$
- Reference buses: $\theta_{\text{refs}} = 0$
Analytical Sparse KKT Jacobian
The KKT Jacobian $\partial K / \partial z$ is computed analytically as a sparse matrix, which is more efficient than automatic differentiation for large problems.
Parameter Derivatives
For each parameter type, we compute $\partial K / \partial p$ and then the full derivative via the IFT formula.
Demand ($d$)
Demand enters the power balance and the load shedding upper bound through $d_+ = \max(d, 0)$:
\[\frac{\partial K_{\nu_{\text{bal}}}}{\partial d} = -I, \qquad \frac{\partial K_{\mu_{\text{ub}}}}{\partial d} = \operatorname{diag}\left(\mu_{\text{ub}} \circ \frac{\partial d_+}{\partial d}\right)\]
For strictly positive demand, $\partial d_+ / \partial d = 1$. For negative demand, it is $0$. At zero demand, the positive part function is nonsmooth; the implementation uses the fixed zero shedding convention already required by the collapsed bound $0 \leq \text{psh} \leq 0$.
Switching ($\mathrm{sw}$)
Switching enters the Laplacian $B$, the weight matrix $W$, and the angle difference limits gated by $\mathrm{sw}$, with elementary perturbations:
\[\frac{\partial B}{\partial \mathrm{sw}_e} = -b_e \, a_e a_e^\top, \qquad \frac{\partial W}{\partial \mathrm{sw}_e} = -b_e \, e_e e_e^\top\]
where $a_e^\top$ is row $e$ of $A$ (so $a_e^\top \theta = (A\theta)_e$) and $e_e$ is the unit vector for branch $e$. These yield the nonzero parameter derivative blocks of $\partial K / \partial \mathrm{sw}_e$:
\[\begin{aligned} \frac{\partial K_{\nu_{\text{bal}}}}{\partial \mathrm{sw}_e} &= -\frac{\partial B}{\partial \mathrm{sw}_e}\,\theta = b_e\,(a_e^\top \theta)\, a_e && \text{(power balance)} \\ \frac{\partial K_{\nu_{\text{flow}}}}{\partial \mathrm{sw}_e} &= -\frac{\partial W}{\partial \mathrm{sw}_e}\,A\theta = b_e\,(a_e^\top \theta)\, e_e && \text{(flow definition)} \\ \frac{\partial K_{\theta}}{\partial \mathrm{sw}_e} &= -b_e\,\bigl(a_e^\top \nu_{\text{bal}} + (\nu_{\text{flow}})_e\bigr)\, a_e + (\gamma_{\text{ub},e} - \gamma_{\text{lb},e})\, a_e && (\theta\text{ stationarity}) \\ \frac{\partial K_{\gamma_{\text{lb}}}}{\partial \mathrm{sw}_e} &= \gamma_{\text{lb},e}\,\bigl((A\theta)_e - \alpha_{\min,e}\bigr)\, e_e, \quad \frac{\partial K_{\gamma_{\text{ub}}}}{\partial \mathrm{sw}_e} = \gamma_{\text{ub},e}\,\bigl(\alpha_{\max,e} - (A\theta)_e\bigr)\, e_e && \text{(angle limits)} \end{aligned}\]
Each block is rank-1 in the incidence row $a_e$ or supported on branch $e$ alone, so every column of $\partial K / \partial \mathrm{sw}$ has only a handful of nonzeros. The stationarity block combines the Laplacian term ($\partial B / \partial \mathrm{sw}_e$), the flow coupling term ($\partial W / \partial \mathrm{sw}_e$), and the gated angle limit term ($\partial \operatorname{diag}(\mathrm{sw}) / \partial \mathrm{sw}_e = e_e e_e^\top$).
Sensitivities are defined while the energized island partition is fixed. Opening or closing a bridge splits or merges islands and changes the reference set, so the derivative is nonsmooth at that topology boundary.
Cost Coefficients ($c_q$, $c_l$)
Quadratic cost $c_q$ enters stationarity w.r.t. $g$:
\[\frac{\partial K_g}{\partial c_{q,i}} = 2 g_i e_i\]
Linear cost $c_l$ enters stationarity w.r.t. $g$:
\[\frac{\partial K_g}{\partial c_l} = I_k\]
Flow Limits ($f_{\max}$)
Flow limits enter the complementary slackness conditions:
\[\frac{\partial K_{\lambda_{\text{lb}}}}{\partial f_{\max}} = -\operatorname{diag}(\lambda_{\text{lb}}), \qquad \frac{\partial K_{\lambda_{\text{ub}}}}{\partial f_{\max}} = \operatorname{diag}(\lambda_{\text{ub}})\]
Susceptances ($b$)
Susceptances affect the same blocks as switching (through $B$ and $W$), but with different partial derivatives since $B = A^\top \operatorname{diag}(-b \circ \mathrm{sw}) A$.
LMP Decomposition
Locational marginal prices are the power balance duals $\nu_{\text{bal}}$, decomposed as:
\[\text{LMP} = \underbrace{\text{energy}}_{\text{per island uniform component}} + \underbrace{\text{congestion}}_{\text{flow limit component}}\]
The congestion component is extracted by solving:
\[\text{congestion}[\text{non-ref}] = B_r^{-1} \left(A_r^\top W (\lambda_{\text{ub}}^{\text{std}} - \lambda_{\text{lb}}^{\text{std}}) + A_r^\top \operatorname{diag}(\mathrm{sw}) (\gamma_{\text{ub}}^{\text{std}} - \gamma_{\text{lb}}^{\text{std}})\right)\]
where $\lambda^{\text{std}}$, $\gamma^{\text{std}}$ use the standard sign convention (non-negative for binding constraints). The $\gamma$ terms capture congestion from binding phase angle difference limits. The energy component is uniform within each energized island and reflects that island's marginal cost of generation.