From f6a56cb1dec6bec48da12d35a9c8dfea3e2a1f3b Mon Sep 17 00:00:00 2001 From: arihantlodha-cmd Date: Sat, 29 Aug 2026 10:43:16 +0900 Subject: [PATCH 1/2] Report the size and location of a resource-constraint failure When the transition path fails the resource-constraint check, the error was a bare 'equilibrium not found (RC_error)' with no indication of how large the violation was or where it occurred, so diagnosing it meant reloading the saved path and profiling resource_constraint_error by hand. This puts the maximum absolute error, the period it occurs in, and the tolerance in the message, and notes how to read it: a violation confined to the first or last periods is usually an initial- or terminal-boundary artifact, while one spread across the path points to an inconsistent calibration. Also fixes the 'equlibrium' typo in that message. --- ogcore/TPI.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/ogcore/TPI.py b/ogcore/TPI.py index 2032d42c3..a9270d81d 100644 --- a/ogcore/TPI.py +++ b/ogcore/TPI.py @@ -1837,8 +1837,21 @@ def run_TPI(p, client=None): raise RuntimeError(msg) if (np.any(np.absolute(RC_error) >= p.RC_TPI)) and ENFORCE_SOLUTION_CHECKS: + # report where and how large the violation is: a residual confined + # to the first or last periods is usually an initial- or terminal- + # boundary artifact, while one spread across the path points to an + # inconsistent calibration. + abs_rc = np.absolute(RC_error) + rc_by_period = abs_rc.reshape(abs_rc.shape[0], -1).max(axis=1) + t_worst = int(np.argmax(rc_by_period)) raise RuntimeError( - "Transition path equlibrium not found " + "(RC_error)" + "Transition path equilibrium not found (RC_error): max " + f"|resource constraint error| = {rc_by_period[t_worst]:.2e} at " + f"period {t_worst} of {rc_by_period.shape[0]} (tolerance " + f"RC_TPI = {p.RC_TPI}). A violation confined to the first or " + "last periods is usually an initial- or terminal-boundary " + "artifact; one spread across the path points to an inconsistent " + "calibration (spending, revenue, debt_ratio_ss)." ) if ( From 8ff15ac4ae3af43f5ae5a548dcb6af9fccf48ce2 Mon Sep 17 00:00:00 2001 From: arihantlodha-cmd Date: Sat, 29 Aug 2026 20:54:05 +0900 Subject: [PATCH 2/2] Cover the RC-error message with a test Codecov flagged the new resource-constraint diagnostic lines as uncovered. Pull the message construction out of run_TPI into a small _rc_error_message helper and add test_rc_error_message, which checks it names the largest violation, the period it occurs in, and the tolerance. No behavior change. --- ogcore/TPI.py | 47 +++++++++++++++++++++++++++++++---------------- tests/test_TPI.py | 12 ++++++++++++ 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/ogcore/TPI.py b/ogcore/TPI.py index a9270d81d..5a216d711 100644 --- a/ogcore/TPI.py +++ b/ogcore/TPI.py @@ -731,6 +731,36 @@ def inner_loop(guesses, outer_loop_vars, initial_values, ubi, j, ind, p): return euler_errors, b_mat, n_mat +def _rc_error_message(RC_error, RC_TPI): + """ + Build the message for a transition resource-constraint failure. + + Reports the maximum absolute resource-constraint error, the period it + occurs in, and how to read it: a violation confined to the first or last + periods is usually an initial- or terminal-boundary artifact, while one + spread across the path points to an inconsistent calibration. + + Args: + RC_error (array_like): resource constraint error, time on axis 0 + RC_TPI (scalar): the tolerance the error is checked against + + Returns: + msg (str): the diagnostic error message + """ + abs_rc = np.absolute(RC_error) + rc_by_period = abs_rc.reshape(abs_rc.shape[0], -1).max(axis=1) + t_worst = int(np.argmax(rc_by_period)) + return ( + "Transition path equilibrium not found (RC_error): max " + f"|resource constraint error| = {rc_by_period[t_worst]:.2e} at " + f"period {t_worst} of {rc_by_period.shape[0]} (tolerance " + f"RC_TPI = {RC_TPI}). A violation confined to the first or last " + "periods is usually an initial- or terminal-boundary artifact; one " + "spread across the path points to an inconsistent calibration " + "(spending, revenue, debt_ratio_ss)." + ) + + def run_TPI(p, client=None): """ Solve for transition path equilibrium of OG-Core. @@ -1837,22 +1867,7 @@ def run_TPI(p, client=None): raise RuntimeError(msg) if (np.any(np.absolute(RC_error) >= p.RC_TPI)) and ENFORCE_SOLUTION_CHECKS: - # report where and how large the violation is: a residual confined - # to the first or last periods is usually an initial- or terminal- - # boundary artifact, while one spread across the path points to an - # inconsistent calibration. - abs_rc = np.absolute(RC_error) - rc_by_period = abs_rc.reshape(abs_rc.shape[0], -1).max(axis=1) - t_worst = int(np.argmax(rc_by_period)) - raise RuntimeError( - "Transition path equilibrium not found (RC_error): max " - f"|resource constraint error| = {rc_by_period[t_worst]:.2e} at " - f"period {t_worst} of {rc_by_period.shape[0]} (tolerance " - f"RC_TPI = {p.RC_TPI}). A violation confined to the first or " - "last periods is usually an initial- or terminal-boundary " - "artifact; one spread across the path points to an inconsistent " - "calibration (spending, revenue, debt_ratio_ss)." - ) + raise RuntimeError(_rc_error_message(RC_error, p.RC_TPI)) if ( np.any(np.absolute(eul_savings) >= p.mindist_TPI) diff --git a/tests/test_TPI.py b/tests/test_TPI.py index 2a2b680d1..1cc50567b 100644 --- a/tests/test_TPI.py +++ b/tests/test_TPI.py @@ -258,6 +258,18 @@ def test_get_initial_SS_values(baseline, param_updates, filename, tmpdir): ) +def test_rc_error_message(): + """The resource-constraint failure message names the largest violation, + the period it occurs in, and the tolerance.""" + T, M = 320, 2 + RC_error = np.full((T, M), 1e-8) + RC_error[T - 1, 1] = -0.05 # a terminal-boundary spike (signed) + msg = TPI._rc_error_message(RC_error, 1e-4) + assert "5.00e-02" in msg # the max absolute error, reported + assert f"period {T - 1}" in msg # the period it occurs in + assert "1e-04" in msg or "0.0001" in msg # the tolerance + + def test_firstdoughnutring(): # Test TPI.firstdoughnutring function. Provide inputs to function and # ensure that output returned matches what it has been before.