API reference

All exported names, grouped by capability.

Module

PowerOptLabModule
PowerOptLab

A research laboratory for four-wire distribution-network decisions when the network state or model is uncertain. It connects model evidence and forensics, informative interventions, and verification of operating decisions. The complete workflow is a research direction; current APIs provide separate foundations and state their local, scenario, or prototype limits explicitly.

PowerOptLab builds on the BMOPFTools reference current–voltage OPF engine, using its public extension seams (model_hook! / solution_hook! and the staged build_opf_model / enforce_kcl! / generation_cost / extract_result API) rather than forking the engine. Experimental devices and formulations reuse the engine's neutral-explicit physics, per-unit handling, and result extraction. Stable foundational work can later be proposed for the BMOPF spec.

Contributions are organised by what layer of the engine they extend:

Component models — new network elements (src/components/)

Stamped into a solve through model_hook! / solution_hook!.

  • Storage / EV (StorageDevice, EVDevice) — battery/EV inverter ports stamped as current injections with an inter-temporal state-of-charge state (an energy/power "PE" model with fixed efficiency).
  • IVQ battery (IVQBattery) — the current–voltage counterpart: cells of a BatteryChemistry modelled in the voltage–current–charge space (v = OCV(soc) − i·R), so voltage and current limits bind individually and a current-dependent cell efficiency emerges from the physics (a Rint proxy, not a full round-trip energy efficiency). Reuses the AdvancedInverter for the AC↔DC converter.
  • Advanced inverter (AdvancedInverter) — an experimental internal-AC-node IBR with an output filter, internal-EMF/DC-modulation bounds, grid-forming operation, converter losses, and double-frequency ripple limits.

Problem specifications — new formulations over the staged API (src/problems/)

A different objective/variable/constraint structure on the same physics.

  • Multi-period OPF (solve_multiperiod_opf) — several network snapshots co-optimised in one model with storage/EV state linking each step to the next.
  • State estimation (solve_state_estimation) — weighted least-squares estimation of the network state from noisy measurements (an inverse problem on the same physics).
  • Constrained NLLS state estimation (solve_sparse_state_estimator) — a compiled neutral-explicit residual/constraint model with tangent-space observability, selected local covariance, and sparse or dense reference solves.
  • Parameter estimation (solve_parameter_estimation) — calibration of uncertain line lengths and transformer tap ratios from smart-meter data across multiple time steps (the shared-parameter dual of state estimation).
  • Inverse Carson reconstruction (solve_inverse_carson) — screens discrete overhead construction candidates against diagonal sequence data, retaining ambiguity and local identifiability diagnostics while reconstructing a full primitive, neutral-explicit line model.
  • Dynamic operating envelopes (solve_operating_envelope) — per-connection-point active-power import/export capacity with parameterized fairness, forecast/model scenarios, explicit corner-security semantics, and prescribed IBR Q-V controls retained from the network model.
  • Bilevel PV/tap POC (solve_bilevel_pv_tap) — differentiated aggregate-export and local-controller lower levels with native Volt-var/ Volt-watt controls, compared with a centralized single-level tap/export solve.
  • Network-scale inverter-control studies (solve_controlled_inverter_fleet) — selected dataset IBRs are replaced by phase-aware advanced inverters in one simultaneous snapshot, with explicit ownership provenance and table-ready hardware-stress results.

Bespoke algorithms — new solution methods (src/algorithms/)

Question-driven custom solve loops and alternative solution methods.

  • HELM (solve_pf_helm) — the Holomorphic Embedding Load-flow Method: a non-iterative power flow that expands each voltage as a power series in a load-scaling parameter and evaluates it by Padé analytic continuation. Physical mismatch, Padé spread, and coefficient-tail diagnostics distinguish convergence from finite-order numerical divergence without treating the latter as a non-existence certificate.

Everything is SI at the interface; per-unit conditioning inside the solve is handled via the engine's opf_bases(ctx) accessor.

source

Shared contracts

PowerOptLab.SolveOutcomeType
SolveOutcome

Normalized MathOptInterface solve status used by PowerOptLab entry points. has_primal records whether the optimizer returned a candidate point, feasible requires a fully feasible primal status, and optimal additionally requires OPTIMAL or LOCALLY_SOLVED termination. Only optimal outcomes are published as final numerical results by the research optimization wrappers.

acceptable identifies MOI's relaxed ALMOST_OPTIMAL / ALMOST_LOCALLY_SOLVED outcomes. It is exposed for diagnostics but is not silently promoted to an optimal result.

source
PowerOptLab.TimeGridType
TimeGrid(durations_h)
TimeGrid(periods, duration_h=1.0)

Validated period durations in hours. durations_h[t] weights both the period's rate objective and its state transition, so nonuniform horizons do not silently assume one-hour snapshots.

source
PowerOptLab.build_multi_contextFunction
build_multi_context(nets; hook_factory, kwargs...) -> MultiContext

Build every network snapshot into one JuMP model through BMOPFTools' staged API. hook_factory(t) returns the model_hook! for snapshot t. The builder centralizes the shared optimizer, solver options, time index, and common unit settings; callers add linking constraints/objectives and then enforce KCL. Pass an existing model when shared variables/operators must be created first.

source

Extension interfaces

PowerOptLab.measurement_predictionFunction
measurement_prediction(kind, dvr, dvi;
    ir=nothing, ii=nothing, magnitude=nothing, magnitude_epsilon=0)

Evaluate the shared scalar measurement model from a complex voltage drop dvr + im*dvi and, for power/current quantities, a complex current ir + im*ii.

Supported node kinds are :vr, :vi, :vmag, :pinj, and :qinj; supported branch kinds are :ire, :iim, :imag, :pflow, and :qflow. Injection and flow powers use the same V * conj(I) convention. Pass an auxiliary nonnegative magnitude when a JuMP formulation represents |V| explicitly; otherwise the magnitude is evaluated directly. A positive magnitude_epsilon supplies the smooth magnitude used by compiled state estimation.

The function is generic over numbers, automatic-differentiation values, and JuMP expressions so every estimation formulation uses the same physical measurement equations.

source

Devices

PowerOptLab.StorageDeviceType
StorageDevice(; id, bus, kwargs...)

A grid-connected battery (or generic energy-storage) inverter with an inter-temporal state of charge. Powers are SI watts, energies SI watt-hours.

Required

  • id::String — unique device id.
  • bus::String — connection bus.
  • p_charge_max, p_discharge_max — charge / discharge power limits (W ≥ 0).
  • energy_max — usable energy capacity (Wh).
  • energy_init — energy at the start of the horizon (Wh).

Optional

  • phase_terminals=["1"], neutral="n" — the inverter's phase conductor(s) and the return terminal (nothing if referenced directly to ground).
  • energy_min=0.0 — lower energy bound (Wh).
  • q_min=0.0, q_max=0.0 — reactive-power box (var); default is unity power factor.
  • eff_charge=1.0, eff_discharge=1.0 — one-way efficiencies (0,1].
  • cyclic=true — require the terminal state of charge to equal energy_init.
  • energy_final=nothing — if set, pin the terminal energy to this value (Wh), overriding cyclic.
source
PowerOptLab.EVDeviceType
EVDevice(; id, bus, available, departure_energy, kwargs...)

An electric-vehicle charger: a storage device that is only controllable while plugged in and must reach a target energy by departure. Set p_discharge_max > 0 for bidirectional (V2G) charging; the default 0.0 gives unidirectional (V1G).

Required

  • id, bus — as StorageDevice.
  • p_charge_max — charge power limit (W).
  • energy_max, energy_init — battery capacity and plug-in energy (Wh).
  • available::Vector{Bool} — per period, whether the vehicle is plugged in. While unavailable the charger is idle and the state of charge is held.
  • departure_energy — energy (Wh) required by departure_period.

Optional

  • p_discharge_max=0.0 — V2G discharge limit (W); 0.0 ⇒ charge-only.
  • departure_period=nothing — period index by whose end departure_energy must be met; nothing ⇒ the end of the horizon.
  • phase_terminals, neutral, energy_min, q_min, q_max, eff_charge, eff_discharge — as StorageDevice.
source

Multi-period OPF

PowerOptLab.solve_multiperiod_opfFunction
solve_multiperiod_opf(nets, devices; kwargs...) -> MultiperiodResult

Co-optimise a sequence of network snapshots nets (one BMOPFTools net dict per period, in chronological order) with a set of storage/EV devices whose state of charge couples the periods. The snapshots share one JuMP model and one objective (the sum of each snapshot's generation cost); the devices arbitrage across time subject to their power, energy, efficiency, and terminal/departure constraints.

Per-period economics come from the snapshots themselves — e.g. a time-varying slack import price set via each net's voltage_source cost, or differing loads.

Arguments

  • nets::VectorT network dicts (parse_bmopf output), one per period.
  • devices::VectorAbstractDevice instances implementing the validation/stamp/link/extract lifecycle. Built-in storage and EV devices require their bus/terminals to exist in every snapshot.

Keywords

  • dt_h=1.0 — uniform period duration in hours (compatibility shorthand).
  • time_grid=nothing — pass TimeGrid([Δt₁, Δt₂, ...]) for nonuniform durations. When supplied it takes precedence over dt_h.
  • per_unit=true, s_base=1e6 — engine unit handling (results are SI regardless).
  • optimizer=Ipopt.Optimizer, verbose=false, solver_options=() — solver control.

Returns

A MultiperiodResult with the per-period solutions and each device's SI charge/discharge/SOC trajectory.

source
PowerOptLab.MultiperiodResultType
MultiperiodResult

Result of solve_multiperiod_opf.

Fields

  • termination_status::String — JuMP status of the single shared solve.
  • objective::Float64 — optimal objective value.
  • snapshots::Vector{Dict{String,Any}} — the per-period BMOPFTools result dict (SI), one per input net, in order.
  • dispatch::Dict{String,NamedTuple} — per device id, SI trajectories over the horizon: p_charge, p_discharge, p_net (discharge positive), q (each length T), and soc (length T+1, energy in Wh at each step boundary, soc[1] = initial).
  • solve::SolveStatus — exact normalized status and publication decision.
source

Legacy WLS state estimation

PowerOptLab.MeasurementType
Measurement(; kind, bus, value, sigma, terminal="1", reference=missing)

A single scalar measurement for solve_state_estimation. value and sigma are SI (volts for :vmag, watts for :pinj, vars for :qinj); sigma is the measurement standard deviation (WLS weight 1/sigma²), and must be finite and strictly positive.

  • kind::Symbol:vr, :vi, or :vmag (a rectangular component or the magnitude of the voltage across (bus, terminal)reference), :pinj (active power injected into the network at that terminal pair), or :qinj (reactive power injection).
  • bus::String, terminal::String="1" — the measured phase conductor.
  • reference — the return terminal the quantity is referenced to. missing (the default) inherits the solve's neutral; a String names an explicit return terminal on the same bus; nothing references terminal-to-ground. Voltage and power measurements at a bus therefore share one reference — a smart-meter reading is phase-to-neutral for both.

Construction validates kind, finiteness, sigma > 0, and non-empty identifiers; it does not check that the identifiers exist in a particular net.

source
PowerOptLab.solve_state_estimationFunction
solve_state_estimation(net, measurements; kwargs...) -> StateEstimationResult

Estimate the network state of net — a physics-only BMOPFTools net (buses, lines, transformers, shunts, and a voltage source; no loads, generators, IBRs, or operational limits) — that best fits measurements in a weighted-least-squares sense.

The network is treated as a contract and validated up front (see allow_operational). Every non-source phase terminal must be either a measured injection (a :pinj+:qinj pair) or declared in zero_injection; an un-declared, un-measured bus is an error, never a silent zero injection.

Keywords

  • neutral="n" — default return terminal for injection measurements, free injections, and voltage references. Pass nothing if phase terminals are referenced directly to ground.
  • zero_injection=String[] — buses (or (bus, terminal) pairs) known to carry no injection. A bare bus id expands over its phase terminals.
  • allow_operational=false — when true, downgrade the network-contract check to a warning instead of an error (estimate against loads/limits deliberately).
  • check_observability=true — compute a local identifiability diagnostic.
  • per_unit=true, s_base=1e6 — engine unit handling; measurements stay SI.
  • optimizer=Ipopt.Optimizer, verbose=false, solver_options=().

Observability

The returned observability NamedTuple reports a LOCAL numerical check: the Jacobian of the measurement + zero-injection equations with respect to the rectangular node voltages is formed at the returned point (reusing ybus_passive), and observable = rank == n_states. It reports redundancy (surplus equations), the smallest singular value, and the condition number. This detects local rank deficiency / critically-weak measurement sets; it is not a global uniqueness proof, and solver convergence alone never establishes one.

Returns

A StateEstimationResult; its bus voltages are NaN unless primal_status == "FEASIBLE_POINT".

source
PowerOptLab.StateEstimationResultType
StateEstimationResult

Result of solve_state_estimation.

Fields

  • termination_status::String, primal_status::String — the solver's termination status and primal-point status. Trust the estimate only when primal_status == "FEASIBLE_POINT".
  • objective::Float64 — the optimal weighted-residual sum ∑ (z−h)²/σ² (NaN if no feasible point was found).
  • bus::Dict{String,Any} — the estimated SI bus voltages (vr, vi, vm, va per terminal). NaN throughout when primal_status is not FEASIBLE_POINT — an unconverged solver iterate is not an estimate.
  • residuals::Vector{NamedTuple} — per input measurement, in order: (kind, bus, terminal, reference, measured, estimated, residual, standardized) with residual = measured − estimated and standardized = residual/σ. standardized is the σ-normalised RAW residual, not the classical leverage-adjusted normalised residual (rᴺ = r / √(Sᵢᵢ)) used for bad-data identification; it is a scale-free residual, not a χ²/rᴺ test statistic.
  • observability::NamedTuple — a LOCAL numerical identifiability diagnostic (observable, n_states, rank, redundancy, min_singular, cond) from the measurement Jacobian at the returned point (see solve_state_estimation).
source

Constrained NLLS state estimation

PowerOptLab.ExactInjectionSpecificationType
ExactInjectionSpecification

Marker hierarchy for information which is genuinely exact. Meter readings and forecasts are intentionally absent: they belong in the stochastic residual model, not in this hierarchy.

source
PowerOptLab.ExactDeviceEquationType
ExactDeviceEquation(model)

Wrap an exact device model so its terminal KCL equations enter c(x)=0. Use only for a device law known exactly; uncertain load/generation information belongs in measurements or priors.

source
PowerOptLab.SEStructureType
SEStructure{Ti}

Immutable compiled structure for the voltage-only, four-wire state-estimation formulation. It imports BMOPFTools' passive, conductor-to-earth Ybus exactly once. free_state_map maps a free conductor to its position in the rectangular state [real(V_free); imag(V_free)]; ideal-source conductors are held in the parameter vector instead. Closed ideal switches are already represented by the node aliases in ybus_passive.

The evaluator supports terminal voltage components/magnitudes and terminal active/reactive injection measurements, exact zero-injection equations, and connection-aware exact constant-power, constant-current, and ZIP devices. Branch telemetry and sparse linear algebra belong to subsequent phases.

source
PowerOptLab.SEParametersType
SEParameters

Mutable numerical data paired with an SEStructure. Updating the measurement values, standard deviations, or fixed source phasors does not alter terminal ordering or symbolic sparsity. Standard deviations are the diagonal whitening factors for this first implementation.

source
PowerOptLab.compile_state_estimatorFunction
compile_state_estimator(net, measurements=Measurement[];
                        neutral="n", zero_injection=String[], exact_devices=[]) -> SEStructure

Compile the immutable voltage-state evaluator. The network is represented by BMOPFTools' passive I = YV relation in SI units. Every source terminal with a specified phasor is eliminated from the state; ungrounded neutrals and floating conductors remain explicit states, so gauge/reference deficiencies are visible to the later rank diagnostics rather than silently grounded.

source
PowerOptLab.evaluate_state_estimatorFunction
evaluate_state_estimator(structure, parameters, x) -> SEEvaluation

Evaluate SI phasors, whitened stochastic residuals (h(x)-z)/σ, and exact zero-injection residuals. The constraint vector is never whitened or otherwise softened.

source
PowerOptLab.ConstrainedStateEstimationResultType
ConstrainedStateEstimationResult

Result from the dense composite-step reference solver. status distinguishes a numerically converged but underobserved estimate from failure to establish the exact equations. history records the scaled trust-region radius, merit value, measurement objective, and exact-constraint norm at accepted iterates.

source
PowerOptLab.solve_compiled_state_estimatorFunction
solve_compiled_state_estimator(structure, parameters, x0; kwargs...)
    -> ConstrainedStateEstimationResult

Dense reference implementation of the plan's equality-constrained Gauss–Newton method. It uses a scaled Byrd–Omojokun composite step: a normal least-squares step for exact-equation violation, followed by a null-space tangential measurement step. An exact-penalty merit function globalises both quantities; rejected nonlinear constraint steps receive one trust-region-limited second-order correction before the radius contracts.

This intentionally transparent solver is for small-system verification. It uses dense SVD rank/null-space diagnostics; the compiled evaluator preserves the sparsity used by solve_sparse_state_estimator, the sparse Hachtel-system implementation for larger networks.

source
PowerOptLab.solve_sparse_state_estimatorFunction
solve_sparse_state_estimator(structure, parameters, x0; kwargs...)
    -> SparseConstrainedStateEstimationResult

Sparse augmented-system implementation for larger compiled networks. Each iteration solves the scaled Hachtel system using SuiteSparse QR, retaining the residual and constraint operators rather than explicitly forming H' * H or a dense null-space basis. The dense SVD helper is used only for the small final rank diagnostic/status; it is not part of the linear step.

The trust-region acceptance uses the same exact-penalty merit function as the dense reference solver. damping is a small scaled primal regulariser used only to select a stable representative in rank-deficient linear systems.

source
PowerOptLab.solve_with_continuationFunction
solve_with_continuation(structure, parameters, x0; alphas=0:0.25:1, kwargs...)

Advance exact constant-power/ZIP constraints from the regularised internal model (α=0) to their physical, unregularised equations (α=1). Each stage warm starts the dense constrained solver. A returned :power_flow_initialization_failed status means a stage could not establish feasibility; no final estimate is claimed unless the last stage is at α=1.

source
PowerOptLab.observability_diagnosticsFunction
observability_diagnostics(structure, parameters, x) -> SEObservability

Report local identifiability on the feasible tangent space, using H*Z where C*Z = 0. This is deliberately not the rank of the unconstrained measurement Jacobian. The current dense diagnostic is intended for small/reference cases; the sparse solver uses it only after convergence, not in its linear step.

source
PowerOptLab.selected_state_covarianceFunction
selected_state_covariance(structure, parameters, x, indices)

Return only the requested covariance block of the rectangular voltage state. The measurement residual Jacobian is already whitened, so this is the local Gauss–Newton covariance under the diagonal covariance model. It throws for a rank-deficient tangent space rather than returning a misleading finite matrix.

source
PowerOptLab.derived_covarianceFunction
derived_covariance(structure, parameters, x, jacobian)

Return the local covariance of selected derived quantities. jacobian has one row per requested quantity and one column per state variable.

source
PowerOptLab.solve_time_series_state_estimatorFunction
solve_time_series_state_estimator(structure, parameters, x0;
                                  previous_state_sigma=nothing, solver=:sparse, kwargs...)

Solve successive snapshots without rebuilding terminal maps, Ybus structure, or measurement/device incidence. parameters contains one mutable SEParameters per snapshot; callers update its numerical measurements, covariances, sources, and device values in place before calling. Each snapshot warm-starts from the preceding result. When previous_state_sigma is supplied, that preceding state becomes a whitened stochastic prior for the next snapshot, not an exact equation.

solver is :sparse (default) or :dense. The result preserves every per-snapshot result and stops at the first non-converged snapshot, reporting :time_series_stalled without claiming later estimates.

source

Parameter estimation

PowerOptLab.CalibLineType
CalibLine(; id, bus_from, bus_to, r_per_length, x_per_length, ...)

An uncertain line whose length is a free parameter estimated by solve_parameter_estimation. The series impedance is (r_per_length + j·x_per_length)·length [Ω]; only length is estimated (the nominal per-length impedance is treated as known, following the construction-code model Z = Zⁿᵒᵐ·ℓ of Vanin et al.).

The uncertain lines must be omitted from the physics nets — they are the unknowns, stamped by this function. Single-phase: it connects (bus_from, terminal) to (bus_to, terminal); add one CalibLine per phase for a multi-phase line.

Keywords

  • id::String — label for reporting.
  • bus_from::String, bus_to::String, terminal::String="1" — the endpoints.
  • r_per_length::Float64, x_per_length::Float64 — per-unit-length series R/X [Ω].
  • length_init::Float64=1.0 — starting guess handed to the solver.
  • length_min::Float64=0.1, length_max::Float64=10.0 — bounds on the estimate.
source
PowerOptLab.CalibTapType
CalibTap(; id, tap_min=0.9, tap_max=1.1)

An uncertain transformer/regulator tap estimated by solve_parameter_estimation. Unlike CalibLine, the transformer stays in the physics nets: this just names the transformer id and the tap bounds. The function sets tap_min/tap_max on that transformer so the engine creates its native free-tap variable, then couples the tap equal across all snapshots and reports the estimate.

The reported estimate is the tap multiplier τ on the nominal ratio N₀ = v_nom_from / v_nom_to (so τ = 1 is nominal); the effective turns ratio is N = N₀·τ.

Keywords

  • id::String — transformer id present in every snapshot net.
  • tap_min::Float64=0.9, tap_max::Float64=1.1 — bounds on the multiplier τ.
source
PowerOptLab.solve_parameter_estimationFunction
solve_parameter_estimation(nets, measurements; lines, taps, kwargs...)
    -> ParameterEstimationResult

Calibrate uncertain line lengths and transformer tap ratios from smart-meter time series. The uncertain elements' parameters are shared, time-invariant unknowns; each snapshot supplies noisy (P, Q, |V|) meter readings that are fit in a weighted-least-squares (or robust weighted-least-absolute-value) sense across the whole horizon.

The meters' loads are not baked in as exact injections — each measured user gets a free injection current fit to its noisy P/Q readings (as in state estimation), and the phase-to-neutral voltage magnitude is fit to |V|. Buses with no injection measurement are zero-injection.

Arguments

  • nets::AbstractVectorT per-snapshot physics nets (parse_bmopf output): the known source, the known lines, and the transformers named by taps (kept, so the engine frees their taps). The uncertain lines are omitted — they are the unknowns. No load objects are needed; injections come from the measurements.
  • measurements::AbstractVector — parallel to nets; measurements[t] is a Vector{Measurement} of that snapshot's meter readings. :vmag (SI volts), :pinj, :qinj (SI W/var, injection into the network — negative for a load) are all treated as noisy, weighted by their sigma.

Keywords

  • lines::AbstractVector=CalibLine[], taps::AbstractVector=CalibTap[] — the uncertain elements to estimate (at least one required).
  • neutral="n" — return terminal for the phase-to-neutral projections; pass nothing if phase terminals are referenced directly to ground.
  • objective=:wls:wls (weighted least squares, smooth) or :wlav (weighted least absolute value, less sensitive to isolated large residuals; the choice in Vanin et al.). WLAV does not automatically identify or remove bad data.
  • per_unit=true, s_base=1e6 — engine unit handling; measurements stay SI.
  • optimizer=Ipopt.Optimizer, verbose=false, solver_options=().

Returns

A ParameterEstimationResult with the estimated lengths and tap multipliers, the RMS voltage residual, and the per-snapshot fitted state.

source
PowerOptLab.ParameterEstimationResultType
ParameterEstimationResult

Result of solve_parameter_estimation.

Fields

  • termination_status::String, objective::Float64 — solver status and the optimal weighted-residual value.
  • line_length::Dict{String,Float64} — estimated length per CalibLine id.
  • tap::Dict{String,Float64} — estimated tap multiplier τ per CalibTap id.
  • residual_rms::Float64 — root-mean-square voltage-measurement residual [V], a quick goodness-of-fit / noise-floor check.
  • snapshots::Vector{Dict{String,Any}} — per-snapshot extract_result (SI), the fitted network state at the estimated parameters.
source

Inverse Carson reconstruction

PowerOptLab.SequenceLineObservationType
SequenceLineObservation(; z0, z1, frequency, ...)

Diagonal zero- and positive-sequence line data for solve_inverse_carson. z0 and z1 are complex impedances. Optional b0 and b1 are real shunt susceptances. Values are converted to SI per metre internally.

Keywords

  • z_units=:ohm_per_km:ohm_per_m or :ohm_per_km.
  • b_units=:micro_siemens_per_km:siemens_per_m, :siemens_per_km, or :micro_siemens_per_km.
  • sigma — standard deviations in the same input units, ordered (R0, X0, R1, X1[, B0, B1]). If omitted, a descriptive 1% tolerance is used with an absolute floor in the declared input units; candidate scores then have no formal statistical interpretation.
  • covariance — full positive-definite covariance matrix in the same ordered input units. It is mutually exclusive with sigma; correlations affect the Mahalanobis objective while standardized_residual remains marginal.
  • frequency [Hz] and earth_resistivity=100.0 [Ω·m].

The observation is assumed to come from a three-phase matrix, with any circuit neutral Kron-reduced before the symmetrical-component transform.

source
PowerOptLab.OverheadCarsonCandidateType
OverheadCarsonCandidate(; id, geometry, r_ac_ref, gmr, radius, lower, upper, ...)

One discrete overhead construction considered by solve_inverse_carson. Conductor arrays are ordered (a,b,c[,n]) and use SI units: r_ac_ref [Ω/m], gmr, radius, and cap_radius [m]. Separate phase and neutral conductors are supported.

The continuous parameter order is returned in each fit and depends on geometry:

  • :horizontal_3: (half_span, height, temperature)
  • :triangle_3: (half_span, height, temperature); angle is fixed
  • :horizontal_4: (inner_offset, outer_gap, height, temperature)
  • :neutral_under_4: (half_span, phase_height, neutral_drop, temperature)

Geometry values are metres and temperature is °C. lower, upper, and initial follow that order. alpha_20 may be a scalar or one value per conductor; resistance is corrected from temperature_ref using IEC's linear temperature relation.

source
PowerOptLab.solve_inverse_carsonFunction
solve_inverse_carson(observation, candidates; kwargs...) -> InverseCarsonResult

Fit every discrete overhead construction candidate to diagonal sequence data. Each candidate is solved independently as a bound-constrained smooth NLP with deterministic multistart. Fits are returned in increasing weighted-residual order; ambiguity is retained rather than collapsed to a single winner.

Keywords

  • starts=16 — deterministic starts per candidate.
  • acceptance_sigma=3.0 — a candidate is compatible only when every standardized residual is within this threshold.
  • rank_tolerance=1e-6 — relative threshold for the singular values of the standardized prediction Jacobian.
  • confidence_level=0.95 — level for the local linearized parameter intervals.
  • optimizer=Ipopt.Optimizer, verbose=false; solver_options accepts an iterable of name-value pairs or a named tuple. Ipopt receives safe defaults hessian_approximation="limited-memory" and bound_relax_factor=0.0 before user options are applied. Other optimizers receive no Ipopt-specific options.

The solver uses modified Carson only. Returned Z_primitive and C_primitive retain an explicit circuit neutral; Kron reduction is used solely to compare against the sequence observation.

source
PowerOptLab.profile_inverse_carsonFunction
profile_inverse_carson(fit, candidate, observation; kwargs...)

Compute connected one-parameter profile-likelihood confidence intervals around an inverse-Carson fit. Each parameter is fixed successively while all remaining parameters are reoptimized. An endpoint status of :threshold means the chi-square threshold was crossed, :bound means the candidate bound was reached, and :failed conservatively returns the bound because the profile solve failed.

The default threshold is the one-degree-of-freedom chi-square quantile implied by fit.confidence_level. points=12 traces each side before bisection; bisection_steps=16 refines the first crossing. A failed continuation solve is retried from deterministic alternative starts before the endpoint is labelled :failed. These are local connected profiles, not a guarantee that disconnected feasible regions do not exist.

source
PowerOptLab.materialize_inverse_carsonFunction
materialize_inverse_carson(fit, candidate) -> Dict

Create BMOPF-ready wire_data and line_geometry blocks for a successful inverse fit. The returned dictionary is not inserted into a network and no linecode is compiled automatically.

source

Dynamic operating envelopes

PowerOptLab.ConnectionPointType
ConnectionPoint(; id, bus, export_max=0.0, import_max=0.0, kwargs...)

An active connection point whose positive operating-envelope capacity is calculated by solve_operating_envelope.

  • export_max / import_max are the connection's active-power nameplate limits (W, both non-negative). Select which one is used with direction on the solve.
  • ibr_id=nothing retains the lightweight legacy port: aggregate unity-PF power is stamped directly at bus using phase_terminals and neutral.
  • ibr_id="pv1" binds the envelope to an existing BMOPFTools ibr. This is the recommended representation for PV and batteries: the IBR keeps its prescribed Volt-VAr/Volt-Watt or fixed-power-factor control law, apparent/current limits, phase topology, and DC coupling while the DOE controls active power only.
  • requested is an optional requested/forecast capacity (W), used by FairnessPolicy(normalization=:request).
  • normalization is an optional custom fairness reference (W), used by FairnessPolicy(normalization=:custom).
source
PowerOptLab.FairnessPolicyType
FairnessPolicy(; kind=:equal, normalization=:none, weights=Dict(),
                 alpha=1.0, epsilon=1e-6)

Parameterized policy for allocating active-power envelope capacity.

normalization defines the reference in xᵢ = capacityᵢ/referenceᵢ:

  • :none — absolute watts (the reference is 1 W);
  • :capacity — the connection's export/import nameplate for this direction;
  • :requestConnectionPoint.requested;
  • :customConnectionPoint.normalization.

Supported kind values:

  • :equal — require equal normalized allocations and maximize their level;
  • :max_total — maximize the weighted sum of normalized allocations;
  • :proportional — weighted proportional fairness, Σwᵢ log(xᵢ+ε);
  • :alpha — weighted alpha fairness (alpha=0 is weighted sum and alpha=1 is proportional fairness);
  • :max_min — maximize the minimum normalized allocation, then maximize total allocation while retaining that locally optimal minimum;
  • :equal_curtailment — require equal normalized curtailment from nameplate and minimize it.

weights is keyed by connection-point id and defaults to one. All weights must be finite and strictly positive. epsilon regularizes logarithmic/negative-power utilities at zero; it does not create physical capacity.

source
PowerOptLab.solve_operating_envelopeFunction
solve_operating_envelope(nets, connection_points; kwargs...)
    -> OperatingEnvelopeResult

Calculate active-power operating-envelope capacity for each interval.

nets accepts three shapes:

  • one network Dict — one interval, one forecast scenario;
  • Vector{Dict} — several intervals, one scenario each (backward compatible);
  • Vector{Vector{Dict}} — several intervals, each containing one or more forecast/model scenarios. One capacity allocation is shared by every scenario in an interval.

Important keywords:

  • direction=:export or :import; returned capacities are positive magnitudes;
  • fairness=:equal, the legacy symbols :sum / :proportional, or a FairnessPolicy;
  • security=:bound_point enforces only simultaneous full utilisation;
  • security=:corners embeds all 2^N zero/full-utilisation corners for every scenario. It is deliberately capped by max_exact_corners=10 and is reported as local AC feasibility at tested points, not a global robust certificate;
  • volt_var_watt_eps=2e-3 controls the engine's smooth approximation of mandatory IBR Volt-VAr/Volt-Watt curve corners.

Loads retain their known P/Q from each network snapshot. Connection-bound IBRs retain their prescribed Q-V law. Other network devices, including BMOPFTools STATCOM IBRs, remain available to the OPF; comparing otherwise identical nets with and without a STATCOM therefore quantifies its impact on active-power DOEs. Network voltage, phase-to-neutral, negative-sequence (vneg_max), branch thermal, neutral-current, and device limits declared by BMOPFTools remain in force.

source
PowerOptLab.compare_operating_envelope_policiesFunction
compare_operating_envelope_policies(nets, connection_points, policies; kwargs...)

Solve the same DOE study under several fairness policies. policies is a dictionary or vector of label => fairness pairs. The returned dictionary maps each label to an OperatingEnvelopeResult, whose fairness_metrics make the capacity/fairness trade-off directly comparable.

source
PowerOptLab.verify_operating_envelopeFunction
verify_operating_envelope(nets, connection_points, capacities; kwargs...)
    -> OperatingEnvelopeVerification

Check an already-issued capacity trajectory without optimising it. The active power capacities are fixed and the normal network physics, including prescribed Q-V IBR controls and any STATCOM model present in the network, is solved at every requested scenario and utilisation point. utilizations is :bound_point, :corners, or explicit vectors in [0, 1]^N.

source
PowerOptLab.OperatingEnvelopeResultType
OperatingEnvelopeResult

Result of solve_operating_envelope. Capacities are positive SI watts for the selected direction.

snapshots[t] is the first scenario at the all-active upper corner. When an interval has no feasible primal solution it contains only status metadata and all capacities for that interval are NaN; infeasible solver iterates are never published as envelopes.

total_export is retained as a backward-compatible alias of total_capacity. For a new import study use total_capacity and inspect direction.

fairness_metrics reports allocation and curtailment metrics for the published capacity at each interval. schedule records the issue/validity metadata and whether a non-optimised fallback was published.

source

Bilevel distribution-network proof of concept

PowerOptLab.BilevelPVResultType
BilevelPVResult

Result of solve_bilevel_pv_tap. tap is the user-facing tap multiplier, exported_power_W is the lower-level aggregate PV export, and voltages_V contains monitored voltage magnitudes keyed by (bus, phase). converged and termination_reason describe the upper-level search; a lower-level LOCALLY_SOLVED status alone is not an upper-level convergence certificate.

The lower-level consumers are represented by one BMOPFTools native IBR each. Their Volt-var and Volt-watt curves are therefore part of the differentiated network model. lower_level=:aggregate uses a shared aggregate-export objective; lower_level=:local_controller instead ties each PV's active output to its own Volt-watt response, with Volt-var priority enforced through a smooth apparent-power cap.

source
PowerOptLab.BilevelPVResponseType
BilevelPVResponse

Fixed-tap lower-level response used for sensitivity validation and experiment scripts. The response contains the solved PV export, monitored voltages, and the local DiffOpt derivative of each monitored voltage with respect to the utility tap multiplier.

source
PowerOptLab.solve_bilevel_pv_tapFunction
solve_bilevel_pv_tap(net; transformer_id, pv_ids, monitored_buses,
    tap_initial=1.0, tap_bounds=(0.95, 1.05), ...)

Solve the POC hierarchy. With lower_level=:aggregate, the lower level maximises aggregate PV active export with native BMOPFTools Volt-var/Volt-watt control curves active. With lower_level=:local_controller, each PV follows its own Volt-var/Volt-watt response and the lower-level objective is constant; the extra equations are differentiated as part of the network equilibrium. The upper level minimises a smooth fourth-power voltage-stress metric plus tap movement, using DiffOpt's implicit KKT derivative and a safeguarded one-dimensional derivative search. voltage_measurement selects the monitored phase-to-neutral, phase-to-ground, or cyclic phase-to-phase quantity.

This is intentionally a local, smooth-region experiment: it is not a global solution method for a nonconvex bilevel problem. Each upper-level trial builds and solves a fresh lower-level model, which avoids making the reported response path depend on a rejected warm start. At a Volt-watt kink or active-set transition, inspect differentiability_report and validate the final point by perturbing the tap and resolving the lower level.

source
PowerOptLab.solve_bilevel_pv_responseFunction
solve_bilevel_pv_response(net; transformer_id, pv_ids, monitored_buses,
    tap=1.0, tap_bounds=(0.95, 1.05), lower_level=:aggregate, ...)

Solve one fixed-tap lower-level response and return its local voltage sensitivity with respect to the tap. This is the public diagnostic counterpart to the differentiated response used by solve_bilevel_pv_tap: it is useful for comparing DiffOpt derivatives against finite differences and for checking sensitivity quality near Volt-watt transitions. voltage_measurement selects phase-to-neutral (:pn), phase-to-ground (:pg), or cyclic phase-to-phase (:pp) monitoring; outputs are keyed by (bus, phase).

source
PowerOptLab.solve_single_level_pv_tapFunction
solve_single_level_pv_tap(net; transformer_id, pv_ids, monitored_buses, ...)

Solve the centralized comparison: utility tap and all PV operating points are chosen in one BMOPFTools/JuMP OPF. The objective is the same voltage stress and tap movement used by the bilevel upper level. An optional normalized PV export reward can be enabled explicitly with export_weight. This is a benchmark for the coordination gap, not the consumer hierarchy.

source

Advanced inverter

PowerOptLab.AdvancedInverterType
AdvancedInverter(; id, bus, s_max, kwargs...)

An experimental inverter with an internal AC node behind an output filter. All parameters are SI. Features are opt-in: with only id, bus, s_max (and a zero filter) it is a plain grid-following converter at the POC.

Required

  • id::String, bus::String — identifier and point-of-connection bus.
  • s_max::Float64 — converter apparent-power rating (VA), applied on the converter side (internal-node voltage × current).

Connection

  • phase_terminals=["1"], neutral="n" — phase conductor(s) and return terminal (nothing ⇒ referenced to ground). The three-phase topologies and grid-forming require three phases.

Topology (three-phase switching-polytope DC-utilisation model)

  • topology=:SINGLE_PHASE — one of :SINGLE_PHASE (internal-EMF model), :THREE_LEG, :FOUR_LEG, :SPLIT_DC. The three-phase topologies apply the time-sampled switching-polytope voltage feasibility on the internal-node voltage and require v_dc and c_dc. The 4-wire topologies additionally need their neutral current bounded: :FOUR_LEG requires In_max (the 4th-leg rating), :SPLIT_DC requires In_max or i_cap_max (its neutral flows through the capacitors, so a bank rating bounds it on its own).
  • v_dc — DC-link voltage (V). c_dc — DC-link capacitance (F; per half for split).
  • c_dc_upper, c_dc_lower — optional split-link half-bank capacitances (F). Each defaults to c_dc; supplying either exposes unequal series capacitance, unequal neutral-current sharing, and a mean midpoint offset.
  • m_max=1.0 — utilisation factor applied to the ideal two-level switching hull (dimensionless, 0 < m_max ≤ 1).
  • In_max — neutral current limit (A). For :FOUR_LEG this is the 4th leg's device (thermal) rating. For :SPLIT_DC it is the half-bank capacitor ripple rating net of the 2ω allocation: the same capacitors carry both the fundamental neutral current and the 2ω bus current, which sit at different frequencies and so combine in RMS. For a symmetric link, In_max = 2·√(I_half_rated² − I_2ω,rms² − I_sw,rms²) with I_2ω,rms = |S̃|/(√2·v_dc). Passing the raw bank rating double-counts the capacitors and overstates neutral capability. Prefer i_cap_max, which makes that allocation endogenous instead of asking you to pre-compute it.
  • i_cap_max — capacitor RMS ripple-current rating (A), three-phase topologies only: per half-bank for :SPLIT_DC, the whole DC-link bank otherwise. Supplying it stamps the simultaneous thermal allocation on both split half-banks. In the symmetric, equal-weight case this is (|I_n|/2)² + I_2ω,rms² + i_sw² ≤ i_cap_max²; unequal banks use their capacitance-proportional neutral shares. The neutral term is present only for :SPLIT_DC; the 4-leg's neutral flows through its 4th leg, not the caps. Thus the split between capacitor-rating and neutral-current limits is decided by the solve at the operating point rather than fixed as a nameplate. Composes with In_max — whichever binds, binds. For :SPLIT_DC it can also be supplied instead of In_max, since it bounds |I_n| on its own. Rating convention: because electrolytic ESR falls with frequency, the equal-weight sum above is only conservative if i_cap_max is referred to the lowest frequency in it — the 50/60 Hz neutral term. Refer the datasheet rating to that frequency using the capacitor manufacturer's own multiplier; passing a raw 100/120 Hz rating understates 50/60 Hz heating.
  • i_cap_upper_max, i_cap_lower_max — optional per-half split-link thermal- equivalent current ratings (A). They compose with the common i_cap_max.
  • cap_thermal_weights=(1,1,1) — squared-current weights for the fundamental neutral, 2ω, and switching components, respectively. Use ESR ratios referred to the rating frequency; the default reproduces the original RMS sum.
  • esr_dc — monolithic-bank reference ESR (Ω). esr_dc_upper and esr_dc_lower are the split half-bank values. Together with cap_thermal_weights, these produce capacitor dissipation and add it to p_dc; zero defaults preserve the lossless-link model.
  • q_mid_balance_max — optional split-link charge-transfer authority (C). The actuator can shift mean midpoint voltage by 2q/(C_upper+C_lower).
  • v_mid_mean_max — optional magnitude limit on mean midpoint offset (V).
  • i_sw — optional constant switching-frequency RMS allowance (A) reserved out of a supplied common or half-bank capacitor rating (use when the electrolytics, not a parallel film cap, carry the f_sw component).
  • pwm_strategy=:NONE — optional carrier-level DC-link ripple model. :SPWM uses zero common-mode injection; :CENTERED uses centered carrier PWM (the carrier-based SVM equivalent). The convenience solver closes the resulting capacitor-current reserve by conservative outer iteration; direct device stamping requires pwm_strategy=:NONE and an explicitly calibrated pwm_current_factor (plus any independent i_sw residual).
  • f_sw — switching frequency (Hz), required when pwm_strategy != :NONE. pwm_fundamental_samples and pwm_carrier_samples set the two numerical quadrature grids used by the post-solve switching-state audit.
  • pwm_dc_source_r=nothing, pwm_dc_source_l=0 — optional series R–L impedance of the upstream DC source as seen from the link at carrier frequencies. With no branch the source is open to switching ripple and the capacitors carry all of it. pwm_dc_harmonics controls the finite-branch Fourier truncation.
  • pwm_current_factor=0 — direct-stamping approximation I_sw = pwm_current_factor*sqrt(sum(I_leg,rms^2)). The convenience solver updates this factor from the carrier audit; set it explicitly only when composing a larger model around an externally calibrated operating region.
  • pwm_ac_ripple=false — pass the ideal pole-voltage carrier harmonics through the conductor-domain reduced-L or LCL network. This reports converter/grid/ shunt and neutral switching currents and closes them into any supplied i_max, i_grid_max, and In_max ratings. pwm_ac_harmonics truncates the carrier Fourier series and must not exceed half the carrier sample count.
  • pwm_ac_converter_reserve, pwm_ac_grid_reserve, and pwm_ac_neutral_reserve are expert direct-stamping inputs (A RMS). The convenience solver calibrates them automatically; leave them zero otherwise.
  • dv2_max — optional cap on the 2ω bus-ripple amplitude (V).
  • dv_mid_max:SPLIT_DC only: optional cap on the RMS fundamental midpoint-to-ideal-midpoint voltage ripple (V).
  • i_zero_max, i_positive_max, i_negative_max — optional RMS symmetrical- component current limits (A). These are useful for grid-code limits and for separating zero-sequence neutral stress from negative-sequence 2ω stress.
  • n_samples=36 — time-sampling grid for an outer approximation of continuous- time voltage feasibility. Increase it for boundary-sensitive studies.
  • f=50.0 — fundamental frequency (Hz).

Output filter

  • r_filter=0.0, x_filter=0.0 — series filter impedance per phase (Ω).
  • r_filter_neutral=0.0, x_filter_neutral=0.0 — neutral-conductor series impedance (Ω). Its common voltage drop couples all phase-to-neutral KVL equations; it requires a physical neutral terminal.
  • r_filter_matrix=nothing, x_filter_matrix=nothing — optional primitive conductor-domain series-impedance matrices (Ω), ordered as phase_terminals followed by neutral when present. They capture unequal conductors and mutual coupling. Supplying either matrix supersedes the scalar impedances; mixing the two parameterisations is rejected. Matrices must be finite and symmetric, and the resistance matrix must be positive semidefinite.
  • r_filter_grid, x_filter_grid and their _neutral / _matrix variants — optional grid-side series arm. Supplying any grid-side arm or c_filter_mid activates an explicit LCL midpoint; the original filter becomes the converter-side arm.
  • c_filter_mid=0.0 — per-phase midpoint filter capacitance (F). r_filter_damping=0.0 is its optional series damping resistance (Ω).
  • i_grid_max=nothing — optional grid-side arm current limit (A), distinct from converter-side i_max when the midpoint capacitor carries current.
  • b_filter_shunt=0.0 — additional grid-side (POC) shunt susceptance (S), kept separate from the LCL midpoint capacitor for backward compatibility.

Internal EMF box / single-phase modulation

  • v_int_min, v_int_max — per-phase EMF magnitude box (V; applies to all topologies).
  • modulation_max — SINGLEPHASE only: legacy scalar DC-link convention `|Vint| ≤ modulationmax·vdc/√3`. This is not a bridge-specific full-/half-bridge switching model.

Grid-forming

  • grid_forming=false — balanced positive-sequence internal EMF; the magnitude v_gfm ∈ [v_int_min, v_int_max] is a decision variable (composes with a topology).

Converter losses

  • p_loss_fixed=0.0 (W), a_loss=0.0 (W/A), c_loss=0.0 (W/A²).

    The a_loss term needs the current MAGNITUDE, whose exact norm is non-differentiable at zero current. It is stamped as the shifted smooth norm sqrt(|I|² + ε²) − ε, which underestimates the exact norm by at most ε. The modelled loss is therefore biased LOW by at most a_loss·ε per conducting leg — a closed-form, one-sided budget, not a tuning knob. ε is 1e-6 of the leg's own current rating, so the relative bias is identical for every device in a heterogeneous fleet and identical in SI and per-unit. With a_loss == 0 no magnitude term is stamped at all. Reported currents are recomputed exactly after the solve and carry none of this bias.

Rating constraints and the per-unit base

Ratings are stamped as squared per-unit inequalities (p² + q² ≤ (s_max/s_base)² and similar). Ipopt relaxes bounds before solving by bound_relax_factor · max(1, |bound|) (default 1e-8; Eqn 35 of Wächter & Biegler 2006). The max(1, ·) floor is the problem: once the per-unit squared bound (s_max/s_base)² falls below 1 — which it always does — the relaxation stops scaling with it and becomes a fixed 1e-8 ABSOLUTE slackening of a bound that keeps shrinking as 1/s_base². The admissible physical violation is therefore

δ|S| ≈ bound_relax_factor · s_base² / (2·s_max),

quadratic in the base. A 20 kVA rating is honoured to ~0.25 VA at s_base=1e6, but exceeded by ~2.4 kVA (12 %) at s_base=1e8.

This is Ipopt's documented behaviour meeting a modelling choice made here, not a defect in either: a minimal max p s.t. p² ≤ (s_max/s_base)² in plain JuMP reproduces the numbers exactly, and bound_relax_factor=0 removes them. The durable fix belongs in this layer — normalizing the constraint by the rating, (p/s_pu)² + (q/s_pu)² ≤ 1, restores a bound of exactly 1 and was measured to hold the rating to <0.1 VA at every base. Until that lands: keep s_base within a couple of decades of the device ratings, or pass bound_relax_factor=0 (as solve_inverse_carson already does, for the same underlying reason). Independent of the square-root smoothing above.

Double-frequency ripple / current

  • p_ripple_max — SINGLE_PHASE only: bound on the 2ω power-ripple amplitude (VA); it does not by itself calculate capacitor voltage or RMS current.
  • i_max=nothing — optional per-phase-conductor current limit (A). Strongly recommended for every three-phase topology: the aggregate s_max circle does not bound phase-redistribution currents whose per-phase powers cancel.
source
PowerOptLab.solve_advanced_inverterFunction
solve_advanced_inverter(net, inverter; objective=:max_export, kwargs...)
    -> InverterResult

Solve the experimental circuit-aware inverter model. When inverter.pwm_strategy != :NONE, the solver alternates between the smooth NLP and a carrier-level switching audit. A current-norm coefficient is updated until its capacitor allocation covers the modeled carrier ripple; the residual manual i_sw allowance then composes in quadrature. This is a conservative sequential closure, not a mixed-integer switched-converter optimisation.

With inverter.pwm_ac_ripple=true, the same outer loop also updates per-path RMS reserves until the carrier-harmonic converter, grid, and neutral currents are covered by any supplied i_max, i_grid_max, and In_max ratings.

PWM closure keywords are pwm_tolerance=1e-3 (relative current tolerance), pwm_max_iterations=16, and pwm_reserve_factor=1.01. A cold start reaches its fixed point in a handful of solves, so the cap sits well clear of it: a closure reported at the iteration limit is a genuine non-convergence, not a budget that ran out. Inspect result.pwm_reserve_margin, result.pwm_modulation_margin, and result.pwm_iterations for every publishable PWM-enabled solve.

Objective

  • :max_export — maximise active power delivered to the grid at the POC.
  • :min_loss — minimise converter plus capacitor loss subject to p_set.

Other keywords

  • p_set=nothing — required active-power target (W) for :min_loss.
  • q_set=nothing — optional reactive-power constraint at the POC (var).
  • per_unit=false, s_base=1e6, optimizer=Ipopt.Optimizer, verbose=false, solver_options=(). Results are returned in SI regardless of formulation.
source
PowerOptLab.InverterResultType
InverterResult

Result of solve_advanced_inverter. Powers SI (W / var / VA), voltages V, currents A.

Fields

  • termination_status::String, topology::Symbol
  • p_poc, q_poc — active/reactive power injected at the POC (grid side).
  • p_conv, q_conv — converter-side power (at the internal node).
  • p_loss, p_cap_loss, p_dc — semiconductor loss, capacitor ESR loss, and DC-link power (p_dc = p_conv + p_loss + p_cap_loss).
  • p_filter_loss — total real loss in both series arms and midpoint damping (W).
  • v_int_mag::Vector{Float64} — internal EMF magnitude per phase.
  • v_filter_mag — LCL midpoint phase-to-neutral voltage magnitudes (V); equals the POC voltage in the reduced single-series-arm model.
  • i_mag, i_grid_mag, i_filter_shunt_mag — converter-side, grid-side, and midpoint shunt-branch RMS current magnitudes per phase (A).
  • i_neutral — neutral current magnitude |I_n| (0 for 3-wire / single-phase).
  • i_zero, i_positive, i_negative — RMS symmetrical-component current magnitudes (A; zero for :SINGLE_PHASE). For a four-wire connection, i_neutral = 3i_zero up to numerical tolerance.
  • ripple — 2ω power-ripple magnitude |Σ V_int·I| (VA).
  • dv2 — 2ω bus-ripple amplitude (V; three-phase topologies, else 0).
  • dv_mid — split-link midpoint fundamental-ripple RMS magnitude (V; :SPLIT_DC, else 0).
  • v_mid_mean, q_mid_balance — signed mean midpoint shift (V) used by the switching hull and balancing charge transfer (C; split link only).
  • i_captotal capacitor RMS ripple current (A; three-phase topologies, else 0): the 2ω component, neutral share, and i_sw. For an asymmetric split link it is the larger half-bank RMS current.
  • i_cap_thermal — maximum thermally equivalent current after applying cap_thermal_weights; this is the quantity constrained by current ratings.
  • i_cap_upper, i_cap_lower, i_cap_thermal_upper, i_cap_thermal_lower — split half-bank physical and thermal-equivalent currents (A; zero for monolithic links).
  • i_cap_switching, i_dc_bridge_switching_rms, and i_dc_source_switching_rms — carrier-model RMS currents (A) in the DC-link capacitor, ideal bridge input, and optional upstream source branch. i_cap_switching_reserved is the conservative capacitor value allocated by the NLP's calibrated current-norm term. pwm_reserve_margin is allocated modeled ripple minus the carrier prediction; the user's residual i_sw allowance is added separately in quadrature.
  • p_dc_source_switching_loss — dissipation (W) in pwm_dc_source_r; this is an upstream-network diagnostic and is deliberately not included in p_dc.
  • dv_switching_rms, dv_switching_pp — carrier-model total DC-bus switching voltage ripple (V RMS and maximum local peak-to-peak). These are distinct from the low-frequency dv2 amplitude.
  • dv_switching_upper_rms, dv_switching_lower_rms, dv_switching_upper_pp, and dv_switching_lower_pp resolve that total ripple across the two half-banks of a split link (zero for monolithic links).
  • pwm_dc_network_margin — minimum normalized magnitude of the parallel source-capacitor admittance. Values near zero flag a poorly damped carrier- harmonic parallel resonance; it is one for the default open-source model.
  • pwm_modulation_margin — minimum carrier-reference headroom (V); a negative value or NaN means the selected PWM strategy cannot realize the solved fundamental reference. pwm_iterations reports outer reserve solves.
  • i_ac_switching_rms, i_grid_switching_rms, and i_filter_shunt_switching_rms — carrier-harmonic RMS currents (A) on the converter arm, grid arm, and LCL midpoint branch. The corresponding phase peak-to-peak values are i_ac_switching_pp and i_grid_switching_pp.
  • i_neutral_switching_rms, i_neutral_switching_pp — converter-side neutral switching ripple. i_ac_switching_reserved, i_grid_switching_reserved, and i_neutral_switching_reserved are the values allocated by the smooth current constraints. i_ac_total_rms, i_grid_total_rms, and i_neutral_total_rms combine orthogonal fundamental and switching RMS values.
  • switching_margin — minimum rail headroom (V) on a dense, independent post-solve time grid. Positive values pass that audit; negative values reveal a between-sample violation accepted by the optimisation grid. This is a useful numerical audit, not a formal continuous-time certificate (0 for :SINGLE_PHASE).
  • filter_resonance_hz — undamped scalar LCL resonance estimate (Hz), or NaN when the filter is reduced, matrix-valued, or lacks two positive inductive arms.
  • bus::Dict{String,Any} — the BMOPFTools result["bus"] (POC voltages, …).
source

Phase-aware inverter controls

PowerOptLab.PiecewiseLinearLawType
PiecewiseLinearLaw(breakpoints, values; smoothing_epsilon)

A continuous, flat-clamped piecewise-linear scalar law. smoothing_epsilon is an absolute width in the same units as breakpoints; it is used only by the smooth JuMP representation. evaluate_exact retains exact corners.

source
PowerOptLab.WorstPhaseVoltVarWattType
WorstPhaseVoltVarWatt(; volt_watt=nothing, volt_var=nothing,
                       conflict_policy=:dominant, extrema_epsilon=0.05,
                       conflict_epsilon=0.01,
                       volt_watt_basis=:available)

Direction-aware positive-sequence policy for three-phase local voltage control. Volt-watt observes the largest phase-voltage magnitude. Volt-var observes both the smallest and largest phase: low-voltage injection and high-voltage absorption are retained. When both occur, conflict_policy=:net adds the two opposing requests, :dominant continuously blends toward the branch with the larger normalized response, :low_voltage prioritizes injection, and :high_voltage prioritizes absorption.

Volt-watt ordinates are fractions in [0, 1]; Volt-var ordinates are fractions of InverterControlRequest.q_scale. Both curves must be non-increasing. extrema_epsilon is an SI voltage width for smooth phase extrema; conflict_epsilon is the dimensionless transition width in normalized branch severity and is part of both the firmware and JuMP laws. volt_watt_basis is :available or :rated.

Both curve inputs are phase magnitudes and therefore move with the local zero-sequence voltage — a component a three-leg bridge cannot control, and one whose measured value depends on the sensing reference documented in SequenceController.

source
PowerOptLab.AverageVoltageVoltVarWattType
AverageVoltageVoltVarWatt(; volt_watt=nothing, volt_var=nothing,
                            extrema_epsilon=0.05,
                            volt_watt_basis=:available)

Legacy balanced-current comparator. Both PWL laws observe the arithmetic mean of the three local phase-voltage magnitudes. Like WorstPhaseVoltVarWatt, that input moves with the local zero-sequence voltage and with the sensing reference documented in SequenceController.

source
PowerOptLab.PositiveSequenceVoltVarWattType
PositiveSequenceVoltVarWatt(; volt_watt=nothing, volt_var=nothing,
                              worst_phase_watt_guard=true,
                              guard_epsilon=0.05,
                              volt_watt_basis=:available)

Balanced-current sequence comparator. Volt-var observes |U1|. Volt-watt observes the largest phase magnitude when worst_phase_watt_guard=true, so an overvoltage phase cannot be hidden by the positive-sequence magnitude.

source
PowerOptLab.NegativeSequenceAdmittanceDroopType
NegativeSequenceAdmittanceDroop(gain; impedance_angle=0,
    ripple_blend=0, voltage_floor=1)

Request negative-sequence current from local voltage only. gain maps eta = |U2| / sqrt(|U1|^2 + voltage_floor^2) to an admittance magnitude in A/V. impedance_angle fixes the configured negative-sequence actuation angle, and ripple_blend blends the voltage-oriented request with the analytical double-frequency-ripple-cancelling target.

source
PowerOptLab.CommonScaleLimiterType
CommonScaleLimiter(; current_epsilon=nothing, power_epsilon=nothing,
                     current_epsilon_fraction=2.5e-5,
                     power_epsilon_fraction=5e-8,
                     pq_priority=:proportional,
                     priority_headroom_fraction=1e-3)

Apply a common scalar to positive- and negative-sequence current commands so the reconstructed currents at the configured current target respect its declared per-leg limit. The physical plant independently retains every converter- and grid-side current constraint. The positive-sequence P,Q request is first scaled to the converter apparent- power rating. The exact evaluator uses hard maxima. The smooth model represents every magnitude by an implicit nonnegative square root and uses the stated SI epsilons only in smooth maximum selectors. pq_priority is :proportional, :watt, or :var for the positive-sequence apparent-power allocation. By default, smoothing widths are rating-relative fractions, so heterogeneous fleets receive the same dimensionless regularization. Supplying current_epsilon or power_epsilon explicitly opts into an absolute SI width for that quantity. priority_headroom_fraction reserves a small declared fraction of s_max before a watt- or var-priority component reaches the axis of the capability circle. It is a control/protection margin, not a square-root regularization.

source
PowerOptLab.SequenceControllerType
SequenceController(positive, unbalance, limiter;
                   current_target=ConverterCurrentTarget(),
                   power_voltage_floor=1)

Closed-form three-leg controller combining a positive-sequence Volt-var/Watt policy, a negative-sequence policy, and an algebraic feasibility limiter. current_target selects converter-leg or post-filter grid current. power_voltage_floor is the strictly positive SI voltage floor in the regularized P,Q-to-I1 conversion; it is not a magnitude-smoothing epsilon.

The curves observe the local POC voltage referred to the plant's declared neutral terminal. When the composed AdvancedInverter has neutral=nothing — the three-wire case, and the only one accepted for a native THREE_LEG fleet record — that reference is the network's ground, not a neutral conductor, so the measured phasors carry the local zero-sequence displacement. |U_1| and |U_2| are invariant to that choice, so PositiveSequenceVoltVarWatt Volt-var and NegativeSequenceAdmittanceDroop are unaffected; the phase-magnitude policies are not. See the sensing-reference discussion in the phase-aware control design.

The controller equalities themselves use only these voltage phasors, but the final plant-aware capability backoff in stamp_smooth_control! additionally reads converter-terminal internal voltage and both filter-arm currents. It is a protection surrogate, not part of the voltage-curve law.

source
PowerOptLab.InverterControlResultType

Numeric controller record shared by exact evaluation and smooth-model extraction. sequence_power, total_power, and ripple_power are command-space diagnostics formed from the POC voltage measurement and commanded current at the configured current target. They are not converter-terminal powers when an output filter separates those locations. Complex powers use the injection sign convention.

source
PowerOptLab.ConverterTerminalResultType

Converter-terminal phasors and powers derived from the physical plant solution. All phasors are RMS and powers follow the injection sign convention. The authoritative total converter power is sum(phase_power). Sequence powers use S_k = 3U_k*conj(I_k), and ripple_power is the complex twice-fundamental coefficient sum(U_phase*I_phase).

source
PowerOptLab.evaluate_exactFunction
evaluate_exact(controller, measurement, request, ratings)

Evaluate the exact local controller without JuMP. This is the firmware oracle for regression tests and exact-versus-smooth residuals. It uses only local RMS phasors and fixed controller/rating data.

source
PowerOptLab.stamp_smooth_control!Function
stamp_smooth_control!(ctx, controller, request, inverter, handles)

Constrain an already-stamped AdvancedInverter to the smooth, fixed-structure counterpart of evaluate_exact. The voltage-curve law uses local POC phasors referred to the plant's declared neutral (see SequenceController) and commands the configured converter- or grid-side phase currents.

The subsequent plant-aware capability backoff is deliberately wider than evaluate_exact: it also reads converter-terminal internal voltage and both filter-arm currents so that a per-leg, apparent-power, or dv2_max limit saturates the command instead of making the controller equality infeasible. solve_controlled_inverter applies the same backoff to the exact law at the solved point, which is why the two commands are comparable.

source
PowerOptLab.solve_controlled_inverterFunction
solve_controlled_inverter(net, controlled, request; kwargs...)
    -> ControlledInverterResult

Solve one network snapshot with a local phase-aware controller composed around an AdvancedInverter. The voltage-curve law uses only the inverter's own POC voltage phasors (see SequenceController for the sensing reference); the subsequent capability backoff also uses converter-terminal voltage and both filter-arm currents. The nonlinear controller formulation requires per_unit=true; controller configuration and returned results remain SI. This is a controlled power flow: the controller equalities determine the command, while selection_objective only selects remaining plant allocation freedom (:loss, the default, or :zero for an objective-invariance check).

exact_smooth_current_residual compares the exact law with the stamped smooth law at the smooth model's own solved operating point, using the same solved plant phasors for both. It therefore bounds the smoothing error of the controller algebra, not the distance between the exact-law and smooth-law network equilibria; an independent fixed-point oracle is still outstanding research work.

source

Current–voltage (IVQ) battery

PowerOptLab.IVQBatteryType
IVQBattery(; id, bus, chemistry, n_series, n_parallel, soc_init, inverter, kwargs...)

A current–voltage battery: n_series × n_parallel cells of a BatteryChemistry, coupled to the grid through an AdvancedInverter at the DC port. The pack terminal voltage is n_series · v_cell and the pack current n_parallel · i_cell.

Required

  • id::String, bus::String — identifier and point-of-connection bus (must match the inverter's bus).
  • chemistry::BatteryChemistry — the cell model.
  • n_series::Int, n_parallel::Int — pack configuration.
  • soc_init::Float64 — state of charge (0–1) at the operating point / horizon start.
  • inverter::AdvancedInverter — the AC↔DC converter (reused as-is).

Optional (reserved for multi-period use; ignored in the single-snapshot solve)

  • cyclic::Bool=true — require terminal SoC to return to soc_init.
  • soc_final::Union{Float64,Nothing}=nothing — pin terminal SoC (overrides cyclic).

The multi-period charge balance uses a forward update q[t+1] = q[t] − i[t]·Δt, which is exact for the piecewise-constant current of each period (there is one current variable per period, not per time node). A true trapezoidal rule needs currents at all T+1 nodes with consistent endpoint semantics and is deferred.

source
PowerOptLab.solve_ivq_batteryFunction
solve_ivq_battery(net, battery; objective=:max_export, kwargs...) -> IVQBatteryResult

Stamp battery (and its AdvancedInverter) into net and solve at a single operating point (SoC fixed at battery.soc_init), demonstrating the coupled cell + converter feasible region. The network supplies the surrounding grid (a voltage source, lines); the battery exchanges power at its POC bus through the inverter.

Objective

  • :max_export — maximise active power delivered to the grid (battery discharges until a cell voltage/current limit or the converter rating binds).
  • :max_charge — maximise power drawn from the grid into the battery.
  • :min_loss — minimise converter loss subject to a p_set (W) POC delivery.

Keywords

  • p_set=nothing — required active-power target (W) for :min_loss.
  • q_set=nothing — optional reactive-power constraint at the POC (var).
  • per_unit=true, s_base=1e6, optimizer=Ipopt.Optimizer, verbose=false, solver_options=(). Results are returned in SI regardless of per_unit. Per-unit conditions the voltage–current bilinear coupling and is markedly more robust for this nonconvex solve; pass per_unit=false only to reproduce a raw SI solve.
source
PowerOptLab.IVQBatteryResultType
IVQBatteryResult

Result of solve_ivq_battery. SI units throughout.

Fields

  • termination_status::String
  • p_poc, q_poc — active/reactive power at the grid POC (from the inverter).
  • p_conv — converter-side active power at the internal node (W).
  • p_dc — DC-link power (W; = pack discharge power, p_dc = p_conv + p_loss).
  • p_loss — converter loss (W).
  • soc — state of charge at the operating point (0–1).
  • v_cell, i_cell — cell terminal voltage (V) and signed current (A; > 0 discharge).
  • v_pack, i_pack — pack terminal voltage (V) and current (A).
  • bus::Dict{String,Any} — the BMOPFTools result["bus"].
source
PowerOptLab.solve_multiperiod_ivqFunction
solve_multiperiod_ivq(nets, batteries; kwargs...) -> MultiperiodIVQResult

Co-optimise a chronological sequence of network snapshots nets with a set of IVQBattery devices whose state of charge couples the periods. Each battery arbitrages across time subject to its cell voltage/current limits, SoC window, and terminal/cyclic condition, exchanging power through its AdvancedInverter. The snapshots share one model and one objective (the total generation cost across the horizon); period economics come from the snapshots (e.g. a time-varying slack import price via each net's voltage_source cost).

Arguments

  • nets::VectorT network dicts (parse_bmopf output), one per period.
  • batteries::Vector{IVQBattery} — each battery's bus/inverter must exist in every snapshot. soc_init sets the horizon start; cyclic/soc_final the terminal condition. The charge balance is a forward update, exact for the piecewise-constant per-period current, so it conserves charge exactly.

Keywords

  • dt_h=1.0 — uniform period duration in hours (compatibility shorthand).
  • time_grid=nothing — a TimeGrid for nonuniform durations; when supplied it takes precedence over dt_h.
  • per_unit=true, s_base=1e6, optimizer=Ipopt.Optimizer, verbose=false, solver_options=(). Results are SI regardless of per_unit. Per-unit conditions the coupled nonconvex solve and converges far more reliably across platforms/Ipopt builds; per_unit=false reproduces a raw SI solve.
source
PowerOptLab.MultiperiodIVQResultType
MultiperiodIVQResult

Result of solve_multiperiod_ivq. SI units throughout.

Fields

  • termination_status::String, objective::Float64.
  • snapshots::Vector{Dict{String,Any}} — the per-period BMOPFTools result dict.
  • dispatch::Dict{String,NamedTuple} — per battery id: i_cell, v_cell, i_pack, v_pack, p_poc, q_poc, p_dc (each length T) and soc (length T+1, 0–1, with soc[1] the initial state). SI throughout.

The coupled cell + inverter model is nonconvex, so solve_multiperiod_ivq finds a local optimum and may not converge for every configuration; a non-LOCALLY_SOLVED /OPTIMAL status returns NaN trajectories rather than an unconverged point.

source
PowerOptLab.BatteryChemistryType
BatteryChemistry

One battery cell in the voltage–current–charge (IVQ) variable space. The cell terminal voltage follows the Thévenin/Rint decomposition v(soc, i) = OCV(soc) − i · R(soc) with i > 0 on discharge, so charging (i < 0) raises the terminal voltage above OCV and discharging lowers it.

Construct one with thevenin_chemistry, linear_chemistry or tabulated_chemistry, or use a preloaded shape (illustrative_lfp, illustrative_nmc, illustrative_nca, illustrative_lead_acid, illustrative_leaf).

Fields

  • name::String — chemistry label.
  • ocv::Functionsoc ∈ [0,1] → open-circuit voltage (V); non-decreasing and smooth (C¹). linear/thevenin OCV is affine; tabulated OCV is a monotone cubic (PCHIP) so it is safe to embed as a function of the SoC variable.
  • r_internal::Functionsoc ∈ [0,1] → internal resistance (Ω) ≥ 0.
  • ocv_affine::Union{Tuple{Float64,Float64},Nothing}(intercept, slope) when OCV is affine (so the multi-period model embeds it as a plain expression rather than a registered operator), else nothing.
  • r_constant::Union{Float64,Nothing} — the resistance value when R is constant, else nothing.
  • q_cell::Float64 — cell capacity (Ah).
  • v_cell_min, v_cell_max::Float64 — terminal-voltage operating bounds (V).
  • i_charge_max, i_discharge_max::Float64 — current magnitude limits (A ≥ 0).
  • soc_min, soc_max::Float64 — usable SoC window; the device clamps to it so the optimiser cannot exploit extrapolation outside the fitted range.
  • source::String — data provenance / reference for the OCV and R values.
source
PowerOptLab.thevenin_chemistryFunction
thevenin_chemistry(; name, v_nominal, r_internal, q_cell, kwargs...)

A Thévenin cell: a fixed open-circuit voltage v_nominal behind a constant internal resistance r_internal (Ω). v(soc, i) = v_nominal − i·R. The paper's PE-model is the further special case r_internal = 0 (constant voltage, so power and current are proportional and the current limit reduces to a power limit). Useful as a floor-fidelity model and as a warm-start generator.

Keywords

  • name="Thevenin", v_nominal, r_internal, q_cell (Ah).
  • v_cell_min = 0.8·v_nominal, v_cell_max = 1.2·v_nominal — voltage bounds (V).
  • i_charge_max = q_cell, i_discharge_max = q_cell — current limits (A; default 1C).
  • soc_min = 0.0, soc_max = 1.0.
  • source="Thévenin / Rint model".
source
PowerOptLab.linear_chemistryFunction
linear_chemistry(; name, v_full, v_empty, r_internal, q_cell, kwargs...)

A state-dependent voltage source: the open-circuit voltage falls linearly from v_full at soc = 1 to v_empty at soc = 0, behind a constant internal resistance. OCV(soc) = v_empty + (v_full − v_empty)·soc. A good first model whenever only the charged/discharged voltage endpoints and a resistance are known, and the natural discretisation of a real OCV curve.

Keywords

  • name="Linear", v_full, v_empty (V, with v_full ≥ v_empty), r_internal (Ω), q_cell (Ah).
  • v_cell_min = 0.95·v_empty, v_cell_max = 1.05·v_full — voltage bounds (V).
  • i_charge_max, i_discharge_max, soc_min, soc_max, source — as thevenin_chemistry.
source
PowerOptLab.tabulated_chemistryFunction
tabulated_chemistry(; name, soc_points, ocv_points, r_internal, q_cell, kwargs...)

Open-circuit voltage (and optionally resistance) from data points — e.g. a published OCV curve or a slow-rate (≈ C/20) discharge test. OCV(soc) is a monotone cubic (PCHIP) interpolant of (soc_points, ocv_points), which must be strictly increasing in soc and non-decreasing in ocv (a physical OCV curve). Outside the fitted range it is held flat so the optimiser cannot extrapolate into non-physical voltage — note this makes the function only C⁰ at the two outer knots, so keep soc_min/soc_max strictly inside them (the default nudges them in). r_internal may be a scalar Ω or a matching vector r_points interpolated the same way.

Keywords

  • name, soc_points::Vector (strictly increasing), ocv_points::Vector (non-decreasing, V).
  • r_internal — scalar Ω, or pass r_points::Vector (Ω) for an R(soc) table.
  • q_cell (Ah), v_cell_min = minimum(ocv_points), v_cell_max = maximum(ocv_points), i_charge_max, i_discharge_max, soc_min = minimum(soc_points), soc_max = maximum(soc_points), source.
source
PowerOptLab.illustrative_lfpFunction
illustrative_lfp(; q_cell=100.0, r_internal=0.006, kwargs...)

Illustrative lithium iron phosphate (LFP) shape — a flat ~3.2–3.3 V plateau between sharp knees. The flat OCV is where a constant-voltage PE-model is least wrong in mid-SoC yet where the terminal-voltage and current limits still bite hardest at the knees — a good stress case for the IVQ model. Hand-drawn to a typical LFP voltage band (knee ≈ 2.5 V, nominal ≈ 3.2 V, charged ≈ 3.65 V); not a fit. q_cell/r_internal are representative round numbers, not a specific cell.

source
PowerOptLab.illustrative_nmcFunction
illustrative_nmc(; q_cell=5.0, r_internal=0.03, kwargs...)

Illustrative NMC (nickel-manganese-cobalt) shape — a monotonic slope from ≈ 3.0 V to 4.2 V, the workhorse EV form. Hand-drawn to a typical NMC voltage band; not a fit. For a calibrated NMC811 set to fit against, see PyBaMM Chen2020 (LG INR21700-M50, from GITT/EIS) — this preset is not that set.

source
PowerOptLab.illustrative_ncaFunction
illustrative_nca(; q_cell=3.2, r_internal=0.035, kwargs...)

Illustrative NCA (nickel-cobalt-aluminium) shape — a sloped ≈ 2.5–4.2 V profile similar to NMC (Panasonic/Tesla cylindrical form). Hand-drawn; not a fit.

source
PowerOptLab.illustrative_lead_acidFunction
illustrative_lead_acid(; q_cell=100.0, r_internal=0.004, kwargs...)

Illustrative lead-acid shape — a roughly linear OCV from ≈ 1.75 V (empty) to ≈ 2.15 V (full) per cell, via linear_chemistry. Consistent with the textbook flooded/AGM OCV–SoC relation (e.g. IEEE 1188); endpoints only, not a fit.

source
PowerOptLab.illustrative_leafFunction
illustrative_leaf(; kwargs...)

Illustrative shape in the voltage band of the 2013 Nissan-Leaf cell used by the source paper (Aaslid et al., 2020) — an LMO/NMC blend. The voltage bounds and capacity/current match the paper's Table 2 (Vmin/Vmax = 3.20/4.15 V, Qmax = 29 Ah, Ib,ch/Ib,dch = 30/90 A), but the OCV curve is a hand-drawn monotone line through that band — it does not reproduce the paper, which used an empirical current–SoC voltage surface f_v(soc, i). To actually reproduce the paper, fit that surface from the cell dataset (Wiggins, Allu & Wang, ORNL, 2020, https://doi.org/10.5281/zenodo.2580327) and supply an R(soc)/surface model.

source

HELM power flow

PowerOptLab.solve_pf_helmFunction
solve_pf_helm(net; config=_DEFAULT_CONFIG, switches=:alias,
              ideal_xfmrs=:constrain, max_order=40, tol=1e-8)
    -> Dict{String,Any}

Solve the power flow with HELM (helm_series) and return the standard result dictionary (SI units, same "bus" shape as the OPF results, compatible with write_result/read_result).

Top-level keys:

  • "termination_status""HELM_CONVERGED" | "HELM_SERIES_DIVERGED" (the computed coefficient tail is growing) | "HELM_MAX_ORDER" (series order exhausted before the tolerance was met). Neither non-converged status is a proof of power-flow non-existence.
  • "feasible"true iff converged.
  • "solve_time" — wall-clock seconds.
  • "bus"bus_id => terminal => {vr, vi, vm [V], va [rad]}. NaN-filled when HELM does not converge (the standard infeasible convention).
  • "coupling"id => conductor => {kind, ir, ii [A], im [A]}: the PHYSICAL current through each ideal coupling (closed-switch conductor with switches = :constrain, ideal-transformer winding core).
  • "helm" — diagnostics: order, residual (A), raw pade_spread, coefficient_tail_norms, coefficient_tail_ratios, and the heuristic singularity_estimate. load_margin is retained as a compatibility alias.
source
PowerOptLab.helm_seriesFunction
helm_series(net; config=_DEFAULT_CONFIG, switches=:alias,
            ideal_xfmrs=:constrain, max_order=40, tol=1e-8) -> HelmResult

Solve the 4-wire multiphase power flow of net with the Holomorphic Embedding Load-flow Method on the augmented nodal admittance matrix.

Deterministic and non-iterative: no initial guess, one LU factorization for the germ and every series order. A converged result is the operational branch continuously connected to the no-load state. A non-converged result reports whether its finite coefficient tail grew (:series_diverged) or merely exhausted the requested order (:max_order_reached); neither is a proof that no power-flow solution exists.

Keywords:

  • switches, ideal_xfmrs — forwarded to ybus_augmented; switches = :constrain additionally yields every switch-conductor current.
  • max_order — highest series order (default 40).
  • tol — relative convergence tolerance on the s = 1 current mismatch, scaled by the largest load-current magnitude.

Requirements/limitations (v1): at least one WYE / SINGLE_PHASE voltage source; loads restricted to their constant-power + constant-impedance (ZIP Z/P) parts — constant-current fractions and non-integer exponential models raise an ArgumentError naming the offending loads.

source
PowerOptLab.HelmResultType
HelmResult

Result of helm_series — the HELM power-flow solution and its diagnostics.

Fields:

  • VDict{(bus,terminal),ComplexF64} node-to-earth voltages at s = 1 (every declared terminal, including source/fixed, aliased, and earth-referenced ones).
  • w — unscaled bordered-row solution entries; the PHYSICAL current of couplings[j] is couplings[j].scale * w[j].
  • couplings — the IdealCouplings of the underlying ybus_augmented matrix, in w order.
  • coeffs(n_unknown + n_couplings) × (order+1) series coefficients (diagnostic; row order = non-fixed nodes then couplings).
  • convergedtrue iff the s = 1 nonlinear current mismatch is within tolerance.
  • status:converged | :series_diverged (the finite coefficient tail is growing) | :max_order_reached.
  • residual — max |current mismatch| (A) over non-source nodes and constraint rows, evaluated at the returned solution.
  • n_order — highest series order computed.
  • pade_spread — absolute difference between the last two Wynn-epsilon Padé estimates for each row of coeffs (node rows, then coupling rows). Values have the units of their corresponding row.
  • coefficient_tail_norms — max-norm of the final coefficient orders used by the status classifier.
  • coefficient_tail_ratios — adjacent ratios of those tail norms. A tail with every ratio above one is classified as :series_diverged.
  • singularity_estimate — heuristic Domb–Sykes estimate of the nearest coefficient-dominating singularity in the loading parameter. It is not a certified loading margin. NaN means the series is too short or featureless to extrapolate.

result.load_margin remains a compatibility alias for result.singularity_estimate; new code should use the latter name.

source