Restore the resource-constraint check on interior periods - #75
vahid-ahmadi wants to merge 3 commits into
Conversation
|
@vahid-ahmadi This looks like a nice addition to the API. Will merge when you resolve conflicts with main. |
_build_specs sets RC_TPI = 0.2 so TPI can complete despite a known boundary discontinuity at t = T-1. OG-Core compares RC_TPI against every period at once (np.any, TPI.py:1839), so that also waives interior violations up to 0.2 -- the same order as the terminal error the setting was written to accommodate. A transition path that goes wrong mid-path returns numbers instead of raising. RC_TPI also carries a paramtools validator of range [1e-13, 0.01], so 0.2 is 20x the schema maximum and only takes effect because it is set by attribute assignment after update_specifications. There is no in-contract value that accommodates the terminal artifact. Adds _check_interior_resource_constraint, run after both the baseline and reform TPI solves, which checks RC_error[:-1] against 1e-4 and leaves the terminal period exempt. Trailing axes are collapsed so the reported index is a period. Refs PSLmodels#72, PSLmodels/OG-Core#1210 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XCKMb1aicxYaeUC1us2nvF
be90c1d to
ad30d7b
Compare
|
@jdebacker Rebased on main — conflicts resolved. The clash was with #78 (now merged): both added a function at the same point in Verified after the rebase: 13 tests pass across |
…ured data
The first version of this check used a 1e-4 tolerance over RC_error[:-1].
Measuring the actual error profile from four production runs (S=80, J=7,
T=60; baseline and a CIT reform, each under both TPI_outer_method
settings) shows that would have raised on every real transition path:
t = 0 6.7e-03 initial-condition artifact
t = 1 ~1e-07
t = 2 6.3e-04 largest genuine interior value
t >= 3 <= 3e-06
t = T-1 1.58e-01 truncation artifact
Both ends carry artifacts that are not solution failures. The terminal
period is truncated (PSLmodels/OG-Core#1216: I_d[T-1] is formed from a
steady-state-filled K_d[T] against the actual b_sp1[T-1], so the whole
gap lands there). At t = 0 the initial conditions are imposed rather
than solved.
So: exempt one period at each end, and set the interior tolerance to
1e-3, which passes real runs with margin while still catching a
violation of either the terminal magnitude (0.16) or the interior
magnitude that RC_TPI = 0.2 would otherwise hide.
Adds a regression test built from the measured profile, which fails
against the previous 1e-4-over-rc[:-1] version. Verified that all four
production TPI outputs now pass the check.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XCKMb1aicxYaeUC1us2nvF
faeeff1 to
fe857a8
Compare
|
@jdebacker Please don't merge the version you approved — I found a bug in it and have pushed a fix. Flagging rather than quietly amending, since your approval is against the earlier commit. The problem. My original check used a 1e-4 tolerance over
This also corrects the premise in #72 and in OG-UK's own in-code comment, both of which claim "all other periods are well within 1e-4". That is not true — t=0 and t=2 are not. The fix. Exempt one period at each end, not just the terminal one, and set the interior tolerance from the measurement:
Verification I should have done the first time: the fixed check is run directly against all four production 12 tests, ruff clean, and the diff is still just |
Three fixes from an independent review round. 1. Non-finite errors passed silently. `nan >= tol` is False, so a solve producing NaN resource-constraint errors fell through every comparison and returned a TransitionPathResult with no exception. OG-Core's own guards share the blindness -- `np.any(|RC_error| >= RC_TPI)` and `|TPIdist| > mindist_TPI` are both False under NaN -- so a diverged path was exactly the case this check was meant to catch and exactly the case it waved through. Now raises explicitly, and also when the non-finite value sits in the exempt terminal period. 2. t = 0 was blanket-exempt. Its initial conditions are imposed rather than solved, so it carries a larger artifact (measured 6.7e-3) than the interior -- but it is the impact year, and a genuine period-0 calibration inconsistency should not be invisible. It is now checked against a looser INITIAL_RC_TOL = 1e-2 rather than skipped. It stays exempt only when it is also the terminal period, i.e. a single-period path. 3. The interior tolerance was 1.58x above the largest measured genuine interior value (6.3e-4 at t=2), with that headroom set by a single calibration. Raised 1e-3 -> 5e-3, giving ~8x margin while still catching both the terminal magnitude (0.16) and the 0.109 that motivated RC_TPI = 0.2. Verified: all four production TPI outputs still pass. 19 tests in this file, 32 passed across the suite. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XCKMb1aicxYaeUC1us2nvF
|
Pushed three more fixes from an independent review round, and updated the description (which had drifted from the code). The one that matters: non-finite resource-constraint errors passed silently. Two others: 19 tests, all four production paths still pass, 32 passed across the suite. |
|
The failing It fails in Note the failure mode differs by environment: with no token at all those tests skip cleanly (that fix went in with #69), but with a token present and rejected they error. So CI is hitting the rejected-credential path. Everything else is green on all three PRs, and the 32 passing tests here include all 19 for this change. Flagging because it will block merging anything until the secret is refreshed — happy to open a separate issue if that is more useful than a comment here. |
|
@jdebacker The conflicts are resolved — this has been Two things worth knowing before you merge, since both landed after your approval: The check had a real bug, now fixed. The version you approved used a 1e-4 tolerance over The failing It fails in Verified here: 19 tests for this change, the check run directly against all four production |
Why
_build_specssetsRC_TPI = 0.2so TPI can finish despite a boundary artifact at the final period. But OG-Core comparesRC_TPIagainst every period at once (np.any), so that also waives violations across the whole path — including magnitudes of the same order as the terminal error it was written for. A transition path that goes wrong mid-path returns numbers instead of raising.RC_TPIalso has a paramtools validator ofrange: [1e-13, 0.01].0.2is 20x the schema maximum and only takes effect because it is assigned directly afterupdate_specifications. There is no in-contract value that accommodates the terminal artifact, which is why this is handled after the solve rather than by tuning the parameter.What changes after merging
A resource-constraint violation on the interior of the path, or a non-finite one anywhere, raises instead of passing silently — naming the magnitude and the period. Real runs are unaffected: verified directly against four solved production paths.
RC_TPI = 0.2is unchanged.The measured error profile
Tolerances are set from data, not chosen. Measured across four production runs (S=80, J=7, T=60; baseline and a CIT reform, each under both
TPI_outer_methodsettings):The terminal cause is now diagnosed:
I_d[T-1]is formed from a steady-state-filledK_d[T]against the actualb_sp1[T-1], so the entire gap lands in that one period. The gap term accounts for it to better than 1 part in 3,800 across all four runs, and it shrinks as T grows relative to S (at T=160/S=40 it is 4.4e-06). Filed upstream as PSLmodels/OG-Core#1216.This corrects the premise in #72 and in
_build_specs' own comment, both of which claim "all other periods are well within 1e-4". They are not — t=0 and t=2 exceed it.Change
_check_interior_resource_constraint, called after both the baseline and reform TPI solves:nan >= tolisFalse, so without an explicit check a diverged path falls through every comparison. OG-Core's guards share this blindness —np.any(|RC_error| >= RC_TPI)and|TPIdist| > mindist_TPIare bothFalseunder NaN — so a diverged path is precisely what this must catch.t = 0is checked againstINITIAL_RC_TOL = 1e-2, not skipped. It is the impact year, so a genuine period-0 calibration inconsistency should not be invisible. It is exempt only when it is also the terminal period (a single-period path).tin[1, T-2]againstINTERIOR_RC_TOL = 5e-3— about 8x headroom over the largest measured genuine value, while still catching both the terminal magnitude (0.16) and the 0.109 that motivatedRC_TPI = 0.2.t = T-1is exempt as a truncation artifact.resource_constraint_erroris(T, M), so trailing axes are collapsed and the reported index is a period.Evidence
oguk/tests/test_resource_constraint.py— 19 tests, 0.15s. Includes a regression test built from the measured profile above (which fails against an earlier 1e-4-over-rc[:-1]version of this check), non-finite cases parametrised over ±nan and ±inf including in the exempt terminal period, a real violation at t=0 still raising, t=1 not being treated as exempt, trailing-axis collapse, and the degenerate inputs.The check is also run directly against all four production
TPI_vars.pkloutputs — all pass.Full suite: 32 passed, 3 skipped.
ruff format --checkandruff checkclean.Not verified
No test exercises
run_transition_pathend to end, so the two call sites are covered by reading rather than execution. Both sit afterpickle.loadand before_tpi_dict_to_result.Fixes #72. If PSLmodels/OG-Core#1210 lands a separate terminal tolerance upstream, this can be simplified to set it and restore a tight
RC_TPI.