add fixed point diagnostics#350
Conversation
| krylovdim::Int = 30, | ||
| ) | ||
| n = 2 * size(data.bus_type, 1) | ||
| F = KLU.klu(jac.Jv) |
There was a problem hiding this comment.
Creating a new factorization each time here is less-than-efficient. Might be not worth fixing, though--if I recall correctly, the KrylovKit eigensolve is the bottleneck here.
Also, I'm using closures and boxing here: F isn't an argument of matvec.
There was a problem hiding this comment.
Pull request overview
Adds a Newton fixed-point Jacobian spectral radius diagnostic to help detect likely Newton-Raphson convergence issues (based on the local fixed-point contraction property of (g(x)=x-J(x)^{-1}F(x))), along with tests and a solver option to enable per-iteration monitoring.
Changes:
- Introduces matrix-free fixed-point Jacobian spectral radius estimation using KrylovKit eigen-solves and KLU back-solves.
- Adds an
ACPowerFlowoption flag to enable per-iteration logging of ( \rho ), ( |F|_\infty ) location, and an estimated ( \kappa(J) ). - Adds tests for the Hessian-vector bilinear form, the fixed-point Jacobian-vector product, and logging/flag behavior.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
src/fixed_point_spectral_radius.jl |
Implements acpf_hvvp, spectral radius estimation, and condition estimation. |
src/power_flow_method.jl |
Adds optional per-iteration diagnostic logging (NR/TR) and an x0 warning/log line. |
src/power_flow_types.jl |
Adds compute_fixed_point_spectral_radius::Bool to ACPowerFlow and getter. |
src/PowerFlowData.jl |
Delegating getter for the new option flag on PowerFlowData. |
src/PowerFlows.jl |
Imports KrylovKit and includes the new implementation file. |
test/test_fixed_point_spectral_radius.jl |
Adds convergence-order tests and smoke/logging tests for the new diagnostic. |
Project.toml |
Adds KrylovKit dependency and compat entry. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Performance ResultsPrecompile Time
Solve TimePolar AC
Rectangular CI
Mixed CPB
DC
|
|
Copilot review responses:
|
|
Reverting to draft for now--maintence state of the KrylovKit dependence is mildly iffy |
|
Actually the maintenance status doesn't look too bad: KrylovKit.jl is maintained by one person, but there's commits from 3 months ago and last release was October of 2025. ArnoldiMethod.jl is an alternative: it's used elsewhere in the Julia ecosystem and has commits from a dozen maintainers. However, the most recent commit is 2 years ago. |
|
Decision: KrylovKit.jl is fine for now. We'll use this branch to prototype, the meta-algorithm of fallbacks, then open an new PR so as to avoid repeated rebases/merges. At that point we may implement our own. |
357772d to
ffdb69e
Compare
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ffdb69e to
a5ae37d
Compare
|
Okay I've pushed a first draft of an adaptive method. Strategy: start a default lightweight-ish Newton-type strategy (polar TR at the moment). Compute 3 metrics each iteration: spectral radius Here's what that looks like for one of the tougher time steps for CATS: [ Info: Finding subnetworks via iterative union find
┌ Warning: Using caller-provided x0; skipping improve_x0.
└ @ PowerFlows ~/Documents/julia/Sienna/psy5/PowerFlows.jl/src/power_flow_setup.jl:295
[ Info: Initial residual size: 78.18302991625828 L2, 54.67301427002515 L∞
[ Info: x0 (time_step 22): ρ = 3.874, ‖F‖_∞ = 54.67 at bus 1951 (P), κ̂(J) = 9.271e8
[ Info: Adaptive TR iter 0: ρ = 0.6472, ‖F‖_∞ = 33.76 at bus 4209 (Q), κ̂(J) = 7.897e8
[ Info: Adaptive TR iter 1: ρ = 0.9246, ‖F‖_∞ = 8.638 at bus 8268 (Q), κ̂(J) = 1.605e9
[ Info: Adaptive TR iter 2: ρ = 17.29, ‖F‖_∞ = 0.6821 at bus 2806 (Q), κ̂(J) = 1.09e10
[ Info: Adaptive TR iter 3: ρ = 0.664, ‖F‖_∞ = 0.015 at bus 595 (P), κ̂(J) = 9.401e8
[ Info: Adaptive TR iter 4: ρ = 0.9293, ‖F‖_∞ = 0.004697 at bus 3664 (P), κ̂(J) = 1.988e9
[ Info: Adaptive TR iter 5: ρ = 808.4, ‖F‖_∞ = 0.002076 at bus 595 (P), κ̂(J) = 7.621e10
[ Info: Adaptive TR iter 6: ρ = 786.1, ‖F‖_∞ = 0.002076 at bus 595 (P), κ̂(J) = 7.621e10
[ Info: Adaptive solver: ρ ≥ 1.0 for 2 consecutive iters; escalating to LevenbergMarquardt at iter 6
[ Info: LM iter 0: ρ = 1274.0, ‖F‖_∞ = 0.001881 at bus 595 (P), κ̂(J) = 7.621e10
[ Info: LM iter 1: ρ = 0.6429, ‖F‖_∞ = 0.001881 at bus 595 (P), κ̂(J) = 2.119e9
[ Info: LM iter 2: ρ = 282.2, ‖F‖_∞ = 0.0001008 at bus 3012 (P), κ̂(J) = 4.533e10
[ Info: LM iter 3: ρ = 53.01, ‖F‖_∞ = 0.0001008 at bus 3012 (P), κ̂(J) = 1.959e10
[ Info: LM iter 4: ρ = 53.01, ‖F‖_∞ = 0.0001008 at bus 3012 (P), κ̂(J) = 1.959e10
[ Info: LM hit numerical floor: ‖F‖_∞ = 0.000101, est. floor κ̂·ε·max(1,‖F‖_init) ≈ 0.000238
[ Info: Final residual size: 0.0005938791533058349 L2, 0.00010075019468018531 L∞.
┌ Warning: The AdaptiveACPowerFlow solver stopped at the numerical floor after 11 iterations: the residual is limited by κ̂·ε·max(1,‖F‖_init) backward stability, not the requested tolerance. Reporting non-convergence.
└ @ PowerFlows ~/Documents/julia/Sienna/psy5/PowerFlows.jl/src/power_flow_method.jl:964I'm not set on the particular methods here--seems like something we could experiment with--but I do want feedback on my approach. Do my "switching strategies" criteria make sense? Are there other cases I need to address, besides hitting the noise floor and converging? |
|
Okay the noise floor logic here needs work:
I'll replace it with "bail out when we stop making progress," and change the language so we're not attributing it to loss of precision. |
|
I've introduced a full-fledged convergence failure/search stagnation analysis. There's now scoped enum called
We may not want all of these long-term, but they're certainly useful for troubleshooting convergence failure and prototyping hybrid strategies. New
|
The per-iteration, stagnation, and limit-cycle diagnostics decoded residual indices assuming the polar [P,Q]-interleaved 2-per-bus layout (ix%2 for P/Q, div(ix+1,2) for the bus). That mislabels the rectangular-CI and mixed-CPB residuals, whose entries are current/voltage mismatches on variable-width per-bus blocks plus an LCC tail, and reports the wrong bus. - Add _describe_residual_entry, dispatched on residual type, that maps a global index to the correct bus (via bus_state_offset for the variable-block formulations) and labels the quantity (P/Q, ΔI_re/ΔI_im, |V|²-V_set², or an LCC equation row). - Guard the acpf_hvvp-based FOLD and directional-curvature checks in _stagnation_diagnostic to the polar formulation only; acpf_hvvp is polar-only, so on a non-polar Δx it either throws or returns a silently-wrong number. Non-polar runs now note the checks were skipped. - Thread residual/data/time_step through _log_diagnostics, _stagnation_diagnostic, and _limit_cycle_diagnostic and their call sites in the NR/TR/adaptive-TR loops and Levenberg-Marquardt. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
compute_min_jacobian_eigenvalue finds the eigenvalue of J closest to the origin via inverse iteration, reusing the KLU factor as matvec v -> J\v. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
compute_min_jacobian_eigenvalue is now a separate solver flag on all three AC
formulations, decoupled from compute_fixed_point_spectral_radius. The monitor
logs whichever of {ρ, κ̂, λ_min} were computed, so λ_min works for rect-CI,
mixed-CPB, and LCC systems (where the polar-only spectral radius throws).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fixed point jacobian diagnostics, in collaboration with @jmaack24 . Consider
g(x) = x - J^{-1}(x)F(x): our solution of NR is a fixed point of that function. If the largest eigenvalue of the Jacobian of that function is bigger than 1, then that indicates likely trouble. Performance penalty of computing the largest eigenvalue is very minimal, only 70ms per iteration for a 8k bus system.