Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The sensitivity interface

Every sensitivity comes from a converged solution by the implicit function theorem on a residual $K(z, p) = 0$:

$$ \frac{dz}{dp} = -\left(\frac{\partial K}{\partial z}\right)^{-1} \frac{\partial K}{\partial p}. $$

Operand / Parameter vocabulary

This vocabulary and the shared driver below serve the balanced DC/AC/conic formulations. The experimental native MC OPF API is described separately below.

A sensitivity is named for the physical quantities it relates, not a formulation’s internal variables. The operand (what the derivative is of) and parameter (what it is taken with respect to) are the orthogonal sub-axes (active/reactive, from/to, voltage representation):

  • Operand: Price, Dispatch, Flow { power, end }, Voltage(kind).
  • Parameter: Demand, Cost, LineLimit, SeriesAdmittance, ShuntAdmittance, VoltageBound, GenBound, Transformer, Switching, ConstraintLimit.

ConstraintLimit (DC OPF) shifts both limits of a request’s linear constraint by the same amount, or the fixed value of an equality, in MW. Its columns are keyed ElementId::Constraint(i) by the constraint’s position in the request. The DC KKT carries a multiplier for each stated limit of each linear constraint, so every other DC cell differentiates the constrained program as well.

Each formulation maps a request to its own KKT rows or reports the combination as unsupported.

The object-safe Differentiable trait

One trait exposes the pieces the shared driver needs: the system Jacobian $K$, the parameter column $\partial K/\partial p$, the operand selector $S$, and the per-formulation regularization. It is object safe by construction (&dyn Differentiable), so the DC KKT, the AC Newton system (AcNewton), and the conic KKT (ConicKkt) all plug into one driver.

Forward and adjoint

The single driver runs whichever direction is cheaper:

  • forward solves $K X = \partial K/\partial p$ once per parameter, reads the operand rows;
  • adjoint solves $K^\top Y = S^\top$ once per operand, contracts with $\partial K/\partial p$.

The two are algebraically identical; Mode::Auto picks the smaller dimension.

Per-cell parity classes

Finite differences validate the analytic columns per cell:

  • clean: cells routed through active power, relative error $< 10^{-3}$.
  • Jabr-coupled / soft: squared-voltage or reactive cells, looser (the cone’s degenerate directions).
  • norm-floor skip: columns below the regularization floor carry no resolvable derivative and are not compared.

See Validation for how these classes are checked, and Methodology for how the figures are produced.

Native multiconductor OPF

The opt-in native mc-opf feature exposes McOpfResult::sensitivities() and McOpfSession::sensitivities(). Solve with McOpfOptions.collect_duals = true first. Construction checks the complete saved-report invariants, recompiles the declared problem, and builds one sparse factorization from its verified primal and original-scale multipliers. It does not rerun optimization. The returned McOpfSensitivities borrows the accepted result and reuses its factorization for subsequent queries. The feature enables the existing optional faer dependency; no additional dependency versions or shipping adapters are enabled.

For a complete runnable example covering edits, warm starts, report persistence, unavailable derivatives and both query directions, see the public native workflow. It uses McOpfParameter::new(owner, component, axis, kind) with canonical equipment identity and a zero-based coil/port axis. parameter.unit() describes the parameter kind’s unit; query qualification still checks existence, active constraints and differentiability. For its objective-only query, a forward direction of 1 W and an adjoint objective weight of 1 yield the same numeric local marginal cost in (currency/hour)/W. Forward values generally retain output units per unit path change, while adjoints divide the weighted output unit by each parameter’s physical unit.

use tellegen::{McOpfParameterDirection, McOpfSensitivityOutput};

// `result` is an accepted MC OPF result with dual collection enabled.
let local = result.sensitivities()?;
// Select an explicit identity from local.parameters(), for example load coil P.
let direction = McOpfParameterDirection {
    parameter: selected_load_p,
    delta: 1.0, // one watt per unit change in the path parameter alpha
};
let derivatives = local.forward(
    &[direction],
    &[McOpfSensitivityOutput::Objective],
)?;

forward accepts a combined direction in physical parameter units and returns d(output)/d(alpha) for each selected output. Repeated parameter entries add. adjoint accepts McOpfOutputWeight entries defining one weighted scalar output and returns its gradient with respect to selected McOpfParameter identities. Each query uses one backsolve, apart from any numerical refinement; many parameter or output contractions reuse that solve. Callers own the units of a weighted scalar that combines different physical quantities. Returned entries carry output or parameter units explicitly and retain request order.

Parameters use the same mappings as local marginal values: nominal load coil P/Q for constant-power/current/impedance, ZIP and exponential laws, selected generator P/Q bounds, inverter availability and capabilities, effective branch current/apparent-power ratings, transformer port ratings and physical continuous tap bounds, prepared voltage limits, and inverter control-profile settings. Inactive supported limits have zero derivatives on the fixed active set. Unknown, unsupported or coincident-bound parameters produce an error even if their requested weight is zero; they are never silently treated as inactive limits. Shared linecodes and scalar bus bounds can affect several effective parameters.

For voltage-dependent loads, P/Q directions vary the canonical nominal coil powers while holding the voltage-law coefficients, exponents and nominal voltage fixed. The parameter column includes the exact mixed state–parameter derivative in stationarity. Canonical metadata preserves terms removed at zero nominal powers and follows scalar/per-coil defaults and coil incidence. Current-law rows differentiate conj(S)*U/abs(U)^2; power-law rows differentiate the voltage factor directly, using the log-voltage variable when the compiler introduced it. This supports zero-P/Q transitions without changing the physical formulation. At zero voltage, unsupported neighboring positive-voltage profiles are refused; pure impedance current laws retain their finite derivative. Derivatives with respect to ZIP fractions, exponents or nominal voltage are not exposed.

Inverter directions use canonical per-coil P/Q bounds and apparent-power ratings, and terminal current ratings (coil current for three-leg delta). Availability follows the retained-session edit: one allocated p_max entry changes and the sum becomes p_avail. It requires a positive selected allocation and a coherent observed total. The mappings include all affected volt-var/volt-watt references, including cross-coil PAvailable targets and averaged voltage monitors. Exact control-reference partials enter both feasibility and stationarity through the compiler’s existing magnitude lifts. This does not perturb curve coefficients or introduce numerical finite differences. Two-wire current-rating ties, absent or nonfinite ratings and zero nameplates do not provide independent partials. See the linked marginal-value documentation for complete field and axis semantics.

ControlProfile directions vary a shared profile’s volt-var/volt-watt voltage breakpoints, output fractions, or signed power factor. They accumulate across all assigned inverters and coils, including averaged voltage monitors. Exact mixed derivatives include both the interval slopes and the breakpoint-dependent softplus smoothing width. No runtime finite differences are used. Breakpoints use volts; fractions and power factor use ratio units. Unity power factor has no finite two-sided derivative, and unused profiles are unsupported. For the bounded PF row a*P + pf*Q, the parameter partial is (-pf/a)*P + Q, including both mixed state derivatives; no division by pf is needed. Control families, references, assignments and aggregation stay fixed.

Transformer rating parameters address effective selected result ports in A/VA, holding electrical coefficients fixed. A shared winding nameplate may require several port directions and can also change impedance bases; arbitrary canonical nameplate edits are not represented by a single port parameter. Tap-bound inputs use physical ratio units, including the bound-side reversal and negative chain rule of type-B regulators. The physical formulation and nominal tap stay fixed.

Outputs include operating cost, earth-referenced terminal voltage real/imaginary parts, magnitude and local angle, device coil or terminal P/Q and rectangular current, and branch-end P/Q and rectangular current. Units are currency/hour, V, radians, W, var and A. Device selectors include kind and identity, preventing load/generator name collisions. Coil indices and terminal labels are distinct; neutral returns and delta terminal powers use the complete physical incidence. Branch currents leave the bus toward the branch and include the selected end’s shunt. Device signs follow the physical result ledgers. Gradients differentiate physical measurements rather than assuming auxiliary power columns are exact. Voltage magnitude and angle at zero are unavailable; angle derivatives describe a local continuous angle, not the discontinuity of a wrapped display value.

Transformer outputs use McOpfTransformerAxis: a coil index, a bus/terminal identity, or a named rating port. Coil and terminal currents enter the transformer; terminal outputs sum bare winding incidence, including delta and neutral returns. Separate core/grounding shunt ledgers are excluded from these winding-terminal outputs. Named ports reproduce the result’s rating measurements, including exciting-current and through-power conventions; they are not extra KCL injections. P/Q and rectangular-current outputs use W/var/A and differentiate the physical voltage-current products. TransformerTap returns the optimized physical ratio, with the appropriate direct or inverse derivative. Fixed taps have no such output. Unknown components, terminals, coils or port names are rejected.

Raw multiplier outputs, network-admittance parameters, fixed/discrete tap setpoints and control-mode switching are not part of this interface.

For active constraints c(x,theta)=0, the stationary system is

$$F(x,\lambda,\theta)= \begin{bmatrix}\nabla_x f + J_c^T\lambda\c\end{bmatrix}=0, \qquad K=\begin{bmatrix}\nabla_{xx}^2 L & J_c^T\J_c&0\end{bmatrix}.$$

Forward differentiation solves K z' = -F_theta theta'. Adjoint differentiation solves K^T a = h_z and contracts -a^T F_theta. POUNCE supplies exact sparse Jacobians and the Lagrangian Hessian, using only active-row multipliers and the original objective scale. A normalized rating row g=G(x)/theta^2 contributes both -2g/theta in the constraint derivative and -2 lambda grad(g)/theta in the stationarity derivative. Both branch ends are included. No supported output has an additional explicit parameter term.

The symmetric KKT matrix is equilibrated and factored with sparse LU, without diagonal regularization or a pseudoinverse. Before exposing a view, construction solves every coordinate right-hand side, streaming the computed inverse X through linear workspace. For the equilibrated matrix B, it requires ||I-BX||_1 < 0.5 and the bound ||B||_1 ||X||_1 / (1-||I-BX||_1) <= 1e12, with a floating-point accumulation allowance. This checks all directions, including null directions orthogonal to dense probe vectors. Construction costs one sparse backsolve per KKT column; no dense inverse is retained, and queries reuse the qualified factorization. Each query also checks its componentwise residual against the original unregularized matrix and can perform two refinement steps.

view.diagnostics() returns the active rank qualification, variable/active-row counts, KKT dimension and assembled nonzeros. forward_with_diagnostics and adjoint_with_diagnostics return the same derivatives as the ordinary methods, plus the query’s residual infinity norm, componentwise backward error, refinement count and maximum residual/tolerance ratio. That ratio must be at most one, with tolerance 1e-10 + 1e-8*(|rhs| + |K|*|x|) per row. These statistics describe the assembled matrix and backsolve, not factor memory or forward error.

The inverse check is a numerical safeguard, not an interval-arithmetic or second-order sufficiency certificate. Every returned derivative is conditional on the same regular stationary branch and active set; it does not certify global optimality or predict behavior across active-set transitions.

McOpfSensitivityError distinguishes missing duals, stale results, inconsistent reports, unreliable duals, unsupported parameters/outputs, malformed requests, derivative-evaluation failure, singular/unstable KKT systems and failed linear solves. The existing conservative dual eligibility checks still apply, including zero-limit reductions, weak activity and incomplete or deficient sparse rank qualification. The rank audit budgets sparse fill and work instead of imposing the legacy 256-row cutoff; its outcome and usage are retained in the dual report. It normalizes rows and applies twice-reorthogonalized sparse QR, preferring the sparsest residual row whose norm is at least half the largest remaining norm and exceeds the 1e-9 pivot tolerance. The global largest norm controls termination. This reduces fill on feeders without dropping small coefficients, forming normal equations or relaxing the tolerance. The limits remain two million live coefficients and 100 million work units; candidate visits also count as work. A completed audit is a numerical rank test, not a singular-value or conditioning certificate. Near the tolerance boundary, pivot order can affect the reported numerical rank. Larger or denser feeders can still exhaust a budget and return no rank; physical feasibility does not override that refusal. Eligibility and factorization errors carry the available active_rank; a failed backsolve carries its last finite linear_solve diagnostics when available. Sensitivities additionally refuse ambiguous narrow inequalities and loss of stationarity when inactive multipliers are removed. Physical acceptance alone does not establish this eligibility.

The session entry point requires current_result(): edits, changed options and failed or cancelled attempts invalidate access. Rust borrowing prevents edits while a sensitivity view is in use. An explicitly selected or cloned historical result remains an independent snapshot of its original instance. Portable version 9 results can rebuild a view after import. Version 2–8 dual reports are verified with their original mappings and rank audit (dense for versions 2–6, largest-residual sparse pivots for 7–8) before upgrading to current mappings and sparsity-aware qualification. Versions 1–7 with assigned PF controls are rejected because their constraint scaling differs; they require a new solve. Factorizations and views are native in-memory objects and are not serialized. Neither queries nor failures change the session’s accepted result, warm start or revision.