Skip to content

Use Anderson acceleration for the TPI outer loop: 2x faster transition paths - #78

Open
vahid-ahmadi wants to merge 1 commit into
PSLmodels:mainfrom
vahid-ahmadi:perf/anderson-outer-loop
Open

Use Anderson acceleration for the TPI outer loop: 2x faster transition paths#78
vahid-ahmadi wants to merge 1 commit into
PSLmodels:mainfrom
vahid-ahmadi:perf/anderson-outer-loop

Conversation

@vahid-ahmadi

@vahid-ahmadi vahid-ahmadi commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Why

OG-Core ships an Anderson accelerator for the TPI outer loop, but TPI_outer_method defaults to "picard" — damped functional iteration at nu=0.4. OG-UK has never used it. It is available under the current ogcore<0.18 pin (shipped in 0.17.0), so this needs no version change and no migration.

What changes after merging

Single-sector transition paths take half as long: 16 outer iterations → 8, ~95s → ~47s at production shape. Same equilibrium. Multi-sector runs are unchanged.

This is the only PR in this set that changes numerical behaviour, so the evidence is below in full.

Benchmark

Run through _build_specs with live calibration at OG-UK's own defaults — S=80, J=7, T=60, M=1, pooled GS, ogcore 0.17.0. SS solved once per scenario and reused by both arms; sequential runs, identical single-worker client; only TPI_outer_method differs.

arm method iters final TPIdist wall clock
baseline picard 16 7.291e-05 94.9 s
baseline anderson 8 3.979e-05 47.7 s
reform (CIT 27%→32%) picard 16 7.456e-05 95.3 s
reform anderson 8 4.709e-05 46.9 s

Same equilibrium — max relative difference between the two paths, all inside mindist_TPI = 1e-4:

Y K L r w C
baseline 7.7e-06 2.1e-06 6.9e-06 3.1e-05 8.5e-06 1.5e-05
reform 8.6e-06 3.9e-06 7.2e-06 3.7e-05 1.0e-05 1.3e-05

Diagnostics — no stall, no divergence, nothing non-finite. Each anderson run logged exactly one accel step worsened; trust radius -> 0.5: the trust-region guard firing and recovering, after which the residual falls monotonically (0.106, 0.188, 0.082, 0.031, 9.5e-3, 1.5e-3, 3.7e-4, 4.0e-5). Picard is monotone but slow after iteration 4.

A smaller run (S=40, J=2, T=120, OG-Core test params) gave the same 1.9x and was insensitive to TPI_anderson_m ∈ {2,5,10} and TPI_trust_radius ∈ {0,1,2}, defaults best of the set.

Change

tpi_outer_method(multi_sector) returns "anderson" for single-sector and "picard" otherwise; _build_specs uses it. The trust-region guard is left at its default — it demonstrably does work here, so the unguarded variant is not used.

Multi-sector deliberately keeps Picard: M=8 uses hybr with relaxed SS tolerances and has no evidence either way. Obvious follow-up if someone wants to benchmark it.

Extracted as a standalone function so it can be tested without a live calibration — _build_specs itself calibrates, which makes it unusable in a fast test.

Evidence

4 tests, 1.25s: single-sector selects anderson, multi-sector stays on picard, both values still accepted by OG-Core's update_specifications (guards against an upstream rename), trust radius positive by default.

Full suite 13 passed, 3 skipped. ruff clean.

Incidental

Every arm reported max_RC_error ≈ 0.158 (baseline) / 0.160 (reform), method-independent to six significant figures. That is the RC_TPI = 0.2 waiver discussed in #72 and fixed by #75 — independent confirmation at production shape, unrelated to this change.

Addresses the performance half of #76.

OG-Core ships an Anderson accelerator for the TPI outer loop, but
TPI_outer_method defaults to "picard", so OG-UK has never used it.

Benchmarked at OG-UK's production shape (S=80, J=7, T=60, M=1, GS tax
functions, ogcore 0.17.0), via _build_specs with live calibration. SS
solved once per scenario and the pickle reused by both arms; arms run
sequentially with an identical single-worker client.

  arm                    method     iters  TPIdist    wall clock
  baseline               picard        16  7.29e-05        94.9s
  baseline               anderson       8  3.98e-05        47.7s
  reform (CIT 27%->32%)  picard        16  7.46e-05        95.3s
  reform                 anderson       8  4.71e-05        46.9s

Same equilibrium: max relative difference between the picard and
anderson paths is 7.7e-6 (Y), 3.9e-6 (K), 7.2e-6 (L), 3.7e-5 (r),
1.0e-5 (w), 1.5e-5 (C) -- all inside mindist_TPI.

No stalls, no divergence, nothing non-finite. Each anderson run logged
one "accel step worsened; trust radius -> 0.5", the trust-region guard
firing and recovering, after which the residual falls monotonically.
The guard is left at its default; the unguarded variant is not used.

Multi-sector keeps the default damped Picard: M=8 uses hybr with relaxed
tolerances and has no evidence either way.

The choice is extracted into tpi_outer_method() so it can be tested
without a live calibration.

Refs PSLmodels#76

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XCKMb1aicxYaeUC1us2nvF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant