Skip to content

feat: trace the termination status inside a Reactant-compiled solve - #156

Merged
gdalle merged 1 commit into
JuliaDecisionFocusedLearning:gd/reac2from
gdalle-bot:claude/reactant-termination-status
Sep 10, 2026
Merged

gdalle merged 1 commit into
JuliaDecisionFocusedLearning:gd/reac2from
gdalle-bot:claude/reactant-termination-status

Conversation

@gdalle-bot

Copy link
Copy Markdown
Contributor

This PR was opened by a coding agent, please disregard it until @gdalle has reviewed it

Stacked on #150 (base gd/reac2). Addresses the first bullet of #149.

The problem

ConvergenceStats.termination_status was a plain MOI.TerminationStatusCode, assigned from inside three @trace if blocks. An enum cannot be traced, so only the trace-time value of those assignments survived: a compiled run that converged still reported MOI.OPTIMIZE_NOT_CALLED. max_kkt_passes and time_limit decided when to stop, but the run never said which one had fired.

The fix

Store the status as an integer code:

  • ConvergenceStats gains a type parameter and its field becomes termination_status_code::S, an ordinary number. to_rarray tracks it and the compiled program writes it like any other stat.
  • termination_status(stats) (@public) decodes it back to a MOI.TerminationStatusCode. solve returns exactly the same status it always did, one call away; the MOI wrapper reads it through the accessor.
  • status_code(status) is the encoding direction, used by try_solve_noconstraints!.

The elseif workaround goes away with it. With a traceable code, set_termination_status!! selects the criterion with nested ifelse calls instead of three separate @trace if blocks ordered by increasing priority and relying on last-write-wins (EnzymeAD/Reactant.jl#2563), so the priority between simultaneous criteria is now stated in one place.

Tests

Verified locally on the CPU backend that all three statuses (OPTIMAL, ITERATION_LIMIT, TIME_LIMIT) come back out of a compiled solve!, for both PDHG and PDLP. The Core (5676 pass), MOI (1827 pass) and Reactant (61 pass) test groups are green, and Runic is clean.

Not in scope

The other items of #149 (the uncounted kkt_errors! pass in initialize, the silent record_error_history / show_progress no-ops, the loosened type parameters, coverage) are untouched.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JPzdTAQVAXUNUAQsL4Nz71

`ConvergenceStats.termination_status` was a plain `MOI.TerminationStatusCode`,
written from inside three `@trace if` blocks. An enum cannot be traced, so only
the trace-time value of those assignments survived: a compiled run that
converged still reported `MOI.OPTIMIZE_NOT_CALLED`, and `max_kkt_passes` /
`time_limit` decided when to stop without ever saying so.

Store the status as an integer code instead. `termination_status_code` is an
ordinary number, so `to_rarray` tracks it and the compiled program writes it like
any other stat, and `termination_status(stats)` decodes it back to the enum.
`solve` still returns the same `MOI.TerminationStatusCode` it always did, one
call away.

That also removes the `elseif` workaround. With a traceable code, the criteria
can be selected with nested `ifelse` calls instead of three separate `@trace if`
blocks ordered by increasing priority and relying on last-write-wins
(EnzymeAD/Reactant.jl#2563), so the priority between simultaneous criteria is
stated in one place.

The Reactant coherence tests now compare statuses between the plain and compiled
runs, which JuliaDecisionFocusedLearning#153 had to leave out, and the time limit test checks that the
compiled solve reports `MOI.TIME_LIMIT` rather than just stopping at the right
time.

Verified locally on CPU: all three statuses (`OPTIMAL`, `ITERATION_LIMIT`,
`TIME_LIMIT`) come back out of a compiled `solve!` for both PDHG and PDLP, and
the `Core`, `MOI` and `Reactant` test groups pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JPzdTAQVAXUNUAQsL4Nz71
@codecov

codecov Bot commented Sep 10, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
src/CoolPDLP.jl 100.00% <ø> (ø)
src/MOI_wrapper.jl 99.23% <100.00%> (ø)
src/algorithms/common.jl 97.46% <100.00%> (ø)
src/components/termination.jl 92.30% <100.00%> (-0.55%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@gdalle
gdalle marked this pull request as ready for review September 10, 2026 10:28
@gdalle
gdalle merged commit 04927b9 into JuliaDecisionFocusedLearning:gd/reac2 Sep 10, 2026
13 checks passed
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.

2 participants