Skip to content

Release v0.3.0#3

Merged
wahln merged 30 commits into
mainfrom
rc/v0.3.0
Jun 25, 2026
Merged

Release v0.3.0#3
wahln merged 30 commits into
mainfrom
rc/v0.3.0

Conversation

@wahln

@wahln wahln commented Jun 24, 2026

Copy link
Copy Markdown
Owner

Release candidate for v0.3.0 (onto main). Version bump and changelog
finalization come after review per the AGENTS.md release workflow — this PR is
for CI + code review of the contents below.

Highlights (from ## [Unreleased])

Added

  • Two-sided linear inequalities (Problem.linear_ineq, l ≤ A x ≤ u) are now
    solved, lowered into the one-sided inequality machinery.
  • S2MPJ / CUTEst benchmark corpus (benchmarks/corpus/s2mpj.py + runner):
    pure-Python CUTEst translations bridged onto any CPU Array-API backend; full
    {lbfgs,exact} × {dense,krylov,sparse} matrix with the exact Lagrangian Hessian
    and sparse-direct route; per-route variable caps, sized instantiation, build-time
    guard, incremental persistence + resume/exclude, --config/--names-file, and
    objective-free (feasibility) problems. Download-gated, not in per-PR CI.
  • Dataset-sourced expected-outcome scoring: documented LO SOLTN objective
    oracle + documented-infeasible detection (BURKEHAN scored correct).
  • Expanded Hock–Schittkowski analytic oracles (HS9/HS21/HS28/HS71).
  • Published S2MPJ benchmark results docs page.

Changed

  • Gradient-based scaling is now the default (matches IPOPT).
  • Promoted the driver's vertical-stack operator to a public
    ipax.backend.operators.VStack.

Fixed

  • Near-optimal stalls salvaged as ACCEPTABLE instead of discarded.
  • Fixed variables (x_L == x_U) no longer fail at the first iteration.
  • Filter line-search overflow, restoration singular-solve, and duplicate console
    logging fixed.

See CHANGELOG.md ## [Unreleased] for the full list.

🤖 Generated with Claude Code

wahln and others added 27 commits June 21, 2026 16:09
- AGENTS.md: new "Releasing" section documenting the rc/vX.Y.Z -> PR -> review ->
  bump -> tag -> sync-develop process, with the single-source-version, Keep a
  Changelog, and vX.Y.Z tag conventions.
- Add the /release slash command (.claude/commands/release.md) that drives that
  workflow, stopping at each human/CI gate; register it in the Agent tooling list.
- Fix a stale note: benchmarks/runners/device_efficiency.py now exists (it was
  described as a future file).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… duplicate handler

- Reprint the per-iteration table header every HEADER_REPEAT_INTERVAL (10) rows so
  it stays readable on long runs.
- Mark iterates that already satisfy every enabled acceptable-stopping criterion
  (before the required consecutive count) with a trailing *, via a new
  non-mutating ConditionChecker.conditions_hold().
- configure_verbosity no longer adds a second console handler when the application
  has already attached its own handler to the ipax logger, which previously
  printed every iteration record twice. Propagation (and caplog) is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add four analytic oracle problems to ipax.testing.problems chosen for
structural coverage: HS21 (active bound multiplier + affine inequality),
HS28 (degenerate zero equality multiplier), HS9 (non-unique periodic
optimum, trig objective), and HS71 (full equality+inequality+bounds mix).

Each is exercised across every backend in the integration suite and wired
into the QC benchmark corpus. A new finite-difference derivative-consistency
test validates gradients, constraint Jacobians, and the Lagrangian Hessian
for all nine HS oracles, back-filling the previously untested ones.

HS21 declares its affine inequality through the nonlinear ineq_constraints
path because the driver does not yet support the two-sided linear_ineq
interface; that gap is addressed in a follow-up commit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Problem.linear_ineq was documented but solve() raised NotImplementedError.
Implement it by lowering the constant-data block into the standard one-sided
inequality machinery: finite lower rows become l - A x <= 0 (Jacobian -A),
finite upper rows become A x - u <= 0 (Jacobian +A), and a both-finite row
yields a range pair. The lowering is a thin Problem adapter applied before
scaling, so the IPM driver, gradient scaling, and every solver route (dense,
Krylov, sparse-direct) consume it unchanged (invariant #3) and the affine
block contributes no Lagrangian-Hessian term.

The driver's private vertical-stack operator is promoted to a public
backend.operators.VStack (now also exposing row_inf_norms for scaling) and
reused by both the equality assembly and the lowering. A matrix-free operator
linear_ineq matrix raises with guidance to use ineq_constraints instead.

Covered by multi-backend integration tests (lower-only, two-sided range,
mixed with nonlinear inequalities, with gradient scaling), unit tests for the
lowering algebra, and a VStack operator-contract battery.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Now that the solver supports two-sided linear inequalities, express HS21's
affine constraint 10 x1 - x2 >= 10 through Problem.linear_ineq instead of the
nonlinear ineq_constraints workaround. The optimum, bound multipliers, and the
derivative-consistency checks are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Expanded Hock-Schittkowski oracle coverage (HS9/HS21/HS28/HS71) plus a
finite-difference derivative-consistency harness, and two-sided linear
inequality support (Problem.linear_ineq, l <= A x <= u) lowered into the
one-sided inequality machinery with a public backend.operators.VStack.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
S2MPJ translates the CUTEst/Hock-Schittkowski SIF problems into pure Python
(no Fortran/SIF toolchain, cross-platform), chosen over pyCUTEst which needs a
gfortran runtime-compile and is Linux/macOS-only.

benchmarks/corpus/s2mpj.py loads the problems and bridges their NumPy/SciPy
evaluation onto any CPU Array-API backend: x is converted to NumPy for the
S2MPJ call and results converted back, so ipax's solver runs its linear algebra
natively on the target backend (numpy/torch verified) while the model is
evaluated on the host. S2MPJ's two-sided clower <= c(x) <= cupper constraints
are mapped onto ipax's eq/ineq split, lowering finite inequality sides to
one-sided rows. No analytic Lagrangian Hessian crosses the bridge, so the sweep
uses the default L-BFGS.

benchmarks/runners/s2mpj.py runs the L-BFGS sweep and scoring. Everything is
download-gated via IPAX_S2MPJ_DIR (S2MPJ has no license, so it is not vendored)
and excluded from per-PR CI: the loader returns [] and the gated multi-backend
tests skip when no checkout is present. There is intentionally no nightly job.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Feasibility restoration's damped Gauss-Newton step called xp.linalg.solve on
the normal matrix without guarding the factorization. Far from feasibility a
constraint Jacobian can blow up (e.g. HS7 reaching ~1e201 at a bad iterate),
making the matrix numerically singular; numpy then raised a LinAlgError and
crashed the entire solve, while torch silently returned a non-finite step.

Treat a raised or non-finite solve as a failed Levenberg-Marquardt step: grow
the damping (up to a ceiling) and retry, exactly as for a rejected step. The
solve now degrades to a reported status instead of raising. The exception type
is backend-specific and cannot be named without importing a concrete library
(invariant #1), so it is caught broadly in this localized step.

Surfaced by the S2MPJ HS7 sweep. Regression test reproduces the exact failure
mode via a direct restore() call and asserts it returns finite and infeasible.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add list_s2mpj_problems() to enumerate every problem in a checkout and a
runner --all mode that sweeps the entire CUTEst/S2MPJ collection, with
--max-vars / --max-iter / --max-time caps, per-problem isolation, and a
status summary.

Harden the adapter for the long tail of the collection: unconstrained
problems carry no clower/cupper attributes (S2MPJ only emits them when
constrained), and objective-free feasibility/least-squares problems cannot
be minimized and are now rejected at build with a clear message (run_case
records or the runner skips them). Bounds are optional too.

Gated tests cover the enumerator, an unconstrained solve (ROSENBR), and the
objective-free rejection; all skip without a checkout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The filter line-search switching test computes alpha * (-dphi) ** s_phi, a
pure-Python float power. Python raises OverflowError instead of returning inf
once the result exceeds the double range, so a badly-scaled iterate with an
enormous directional derivative crashed the entire solve. Compute the power
with overflow mapped to +inf, so the comparison stays meaningful and the solve
degrades to a reported status. Surfaced by the S2MPJ INDEF sweep.

Also harden the S2MPJ benchmark adapter: its NumPy-bridged objective/gradient
now catch OverflowError/FloatingPointError from S2MPJ's generated float**
evaluations and return inf, so a line-search trial point that overflows the
problem's own evaluation is rejected rather than crashing the solve (e.g.
LUKVLE4C, which then converges).

Regression tests cover both: an overflowing directional derivative through the
line search, and an overflowing objective/gradient through the adapter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A fixed variable has no strict interior, so the barrier dual z = mu/(x - x_L)
was singular and the first Newton step came out non-finite, failing the solve
with numerical_error at iteration 1. This is common in CUTEst-style models
(ALLINIT, BOX2, BIGGS3, BQPGABIM, ... all pin one or more variables).

Relax fixed / near-degenerate finite bound pairs symmetrically about their
midpoint before the solve (IPOPT fixed_variable_treatment='relax_bounds'),
leaving well-separated bounds untouched. The pinned variable then converges to
its fixed value through a well-conditioned tiny box.

Surfaced by the S2MPJ sweep, where fixed variables were the dominant cause of
the first-iteration numerical_error failures (266 of 274 failed at iter 1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Near a solution the condensed normal-equations system becomes ill-conditioned
(mu driven well below the achieved KKT residual), so the Newton step can be
non-finite even though the iterate is essentially optimal. The driver discarded
such iterates as numerical_error, throwing away a usable solution.

Classify a failed step solve: if the scaled KKT components are within a relaxed
multiple of the optimality tolerances (IPOPT acceptable_tol ~ 1e2 x tol), report
ACCEPTABLE; otherwise NUMERICAL_ERROR as before. Logic extracted to a pure
_classify_step_failure for unit testing.

Surfaced by the S2MPJ sweep: EIGMINA (kkt 2.6e-7) and EIGMAXA (2.9e-8) now end
ACCEPTABLE; genuinely-far failures (DIAGIQB ~1e2, BRATU1D/RAT42LS diverged)
remain numerical_error. Generalizes to any near-converged solve that hits a
final ill-conditioned step.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…restoration

Extend the near-optimal salvage to the line-search -> restoration handoff. Near
a solution the line search can stall (ill-conditioning) and trigger feasibility
restoration, which then declares a false INFEASIBLE even though the iterate is
already feasible and essentially optimal (e.g. DEGENLPA reaching kkt ~5e-8).

Factor the shared near-optimal test into _within_relaxed_tol and consult it
before entering restoration: if the iterate is within the relaxed KKT tolerance,
report ACCEPTABLE instead of restoring. Genuinely-infeasible/diverging points
(large KKT residual) still enter restoration as before. This also recovers
near-converged false-infeasibles in the baseline, independent of scaling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lets the sweep run with gradient-based scaling (or none) for off-vs-on
comparisons across the corpus.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
For one-sided or absent bounds the midpoint computation 0.5*(lower+upper) and
mid +/- relax operated on +/-inf, emitting RuntimeWarnings even though the
results are masked out by xp.where. Use finite stand-ins off the two-sided
pairs so only genuine fixed/degenerate pairs drive the arithmetic.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Flip ScalingOptions.method default from none to gradient-based, matching
IPOPT. Across the full CUTEst/S2MPJ corpus this is a net +67 solved problems
(~92 recovered, mostly from the slow-converging max_iter bucket; ~23 regressed,
of which only 3-4 genuinely fail -- hard nonconvex/minimax problems that diverge
under scaling -- and the rest merely converge slower under the sweep caps).

Results (x, objective, multipliers) are reported in the original problem units,
so the change is transparent to callers; pass scaling=none to opt out. The
full test suite passes unchanged with the new default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Document that the matrix-free KrylovSolver can stall (MINRES/GMRES) on
ill-conditioned equality-constrained saddles due to weak diagonal
preconditioning, surfacing as numerical_error. Affected problems solve via the
dense route (the automatic choice below ~1e4 vars), so default usage is
unaffected; a stronger saddle preconditioner is tracked future work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…scaling

Extend the S2MPJ sweep beyond L-BFGS to the exact Lagrangian Hessian S2MPJ
supplies and to the sparse-direct route:

- _S2MPJExactProblem wires LgHxy/LHxyv (L = f + yᵀc) into ipax's exact route,
  mapping (σ, y_eq, y_ineq) onto S2MPJ's single multiplier vector with correct
  signs for lowered inequality sides (lower −y, upper +y) and honoring σ on the
  objective term (correct under gradient-based scaling, where σ = s_f ≠ 1).
- sparse=True returns Jacobians/Hessian as SparseOperators (true COO sparsity)
  so the sparse-direct (Feral/cuDSS) route factors real structure.
- Runner matrix is now {lbfgs, exact} × {dense, krylov, sparse}; --scaling
  defaults to gradient-based to match the solver default rather than
  benchmarking a scaling-off config users do not get.

Tests: non-gated unit coverage of the multiplier sign-mapping and σ-scaling
(dense + sparse) against a known-Hessian fake; gated integration that the exact
route reaches known optima and exact/sparse matches exact/dense (numpy + torch).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ds, guard

A single --max-vars is the wrong knob: the linear-solver routes have very
different size ceilings. Tag each config with its route's variable cap (dense
2000, Krylov 10000, sparse 25000) and run a config only when the problem fits —
small problems are cross-validated on every route, larger ones fall through to
Krylov and the sparse-direct route. --max-vars stays as a global ceiling.

S2MPJ defaults to tiny SIF sizes (corpus median n=9), so add sized instantiation
(--size N → PROBLEM(N), SIF-default fallback for non-scalable) to reach the
sparse route's large-n regime, an optional subprocess build-time guard
(--max-build-seconds) to abandon pathological O(n^2) pure-Python builds before
they stall an unattended sweep, and per-problem instance caching so the
per-config fan-out rebuilds each problem once instead of up to five times.

Gated tests cover sized scaling + non-scalable fallback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Expand the S2MPJ matrix to a full 2x3 {lbfgs,exact} x {dense,krylov,sparse}:
- lbfgs/sparse is the typical radiotherapy setup (L-BFGS Hessian, sparse-direct).
- exact/krylov helps attribute numerical errors to the matrix-free subspace
  solver rather than the Hessian approximation.

Add --names-file (one problem per line, # comments) for focused re-runs of a
subset — e.g. re-running the budget-limited cases with a larger time/iteration
budget to separate slow-converging problems from genuine numerical failures.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…sweep

An unattended full-corpus sweep can hit a native crash — e.g. a backend
factorization on a model whose evaluation overflowed to inf/NaN (HYDCAR6LS) —
which kills the process. The runner previously wrote its report only at the end,
so one such crash lost the entire run.

Persist the JSON+Markdown report after every problem, and add --resume (keep an
existing report's rows, skip problems already covered, keyed by backend+name)
and --exclude (skip named problems). Together a died sweep continues instead of
restarting, and a known crasher can be stepped past.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… runs

Add --config (comma-separated labels) so one solver configuration can be swept
per process — enabling parallel, independently-recoverable per-config runs.

Write an <out>.inflight file naming the problem currently being solved, cleared
in a finally once it completes, so it survives only a hard native crash and names
exactly the culprit — a wrapper loop can read it, --exclude that problem, and
--resume to drive the sweep to completion unattended.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ity problems

Score S2MPJ cases against the dataset's own documentation, not convergence alone.
The loader parses each source file for the CUTEst classification (pbclass), the
SIF author's solution objective (# LO SOLTN, ~72% of the corpus), and an explicit
"Solution (infeasible)" / "Source: an infeasible problem" marker, and threads them
onto the built problem. run_case then scores a case correct when it reaches the
documented objective, or — for a documented-infeasible problem (BURKEHAN) — when
it detects infeasibility (previously a false failure). The report shows the gap to
the documented optimum and annotates "infeasible (exp)".

Also admit the objective-free problems (CUTEst feasibility / nonlinear-equation
systems) via --include-objective-free, running them as min 0 subject to the
constraints (zero objective/gradient), so the full corpus can be exercised.

CaseResult gains objective / expected_objective / expected_infeasible / pbclass.
Tests: metadata parsing (incl. Fortran D-exponent), feasibility admission, and
gated scoring of BURKEHAN (infeasible=correct) and the documented objective.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add docs/benchmarks/s2mpj.md (and a Benchmarks nav section) recording the latest
tracked run on the full S2MPJ corpus: system information, the dataset-sourced
scoring methodology (LO SOLTN objective oracle + documented-infeasible), and
per-configuration metrics for the {lbfgs,exact} x {dense,krylov,sparse} matrix,
with the optimization-vs-feasibility split.

Headline: exact/sparse is the strongest route (711/1101 correct, 823 optimal);
the lbfgs/krylov numerical-error spike (190) collapses to 7 under the exact
Hessian, attributing those failures to the L-BFGS approximation on the bordered
saddle rather than the Krylov solver.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.06145% with 32 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
ipax/backend/operators.py 63.26% 18 Missing ⚠️
ipax/problem/linear_ineq.py 91.56% 5 Missing and 2 partials ⚠️
ipax/ipm/driver.py 73.91% 5 Missing and 1 partial ⚠️
ipax/ipm/restoration.py 93.33% 1 Missing ⚠️
Files with missing lines Coverage Δ
ipax/__init__.py 100.00% <100.00%> (ø)
ipax/_logging.py 100.00% <100.00%> (ø)
ipax/ipm/filter_ls.py 100.00% <100.00%> (ø)
ipax/ipm/init.py 97.53% <100.00%> (+0.56%) ⬆️
ipax/ipm/kkt.py 90.99% <100.00%> (+0.16%) ⬆️
ipax/ipm/termination.py 100.00% <100.00%> (ø)
ipax/linalg/sparse.py 78.43% <100.00%> (+0.43%) ⬆️
ipax/options.py 100.00% <100.00%> (ø)
ipax/solve.py 92.51% <100.00%> (-1.24%) ⬇️
ipax/testing/problems.py 98.41% <100.00%> (+0.67%) ⬆️
... and 4 more

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Release-candidate changes for v0.3.0 that expand constraint support, improve solver robustness/logging, and add benchmark corpus tooling + documentation for broader regression/accuracy coverage.

Changes:

  • Add support for two-sided linear inequalities (l ≤ A x ≤ u) by lowering them into the one-sided inequality machinery.
  • Introduce S2MPJ/CUTEst benchmark corpus integration (loader + runner), dataset-sourced scoring, and a published docs page.
  • Improve robustness & UX: default gradient-based scaling, fixed-bound relaxation, near-optimal stall salvage (ACCEPTABLE), sparse symmetry hints, and iteration-log formatting/duplication fixes.

Reviewed changes

Copilot reviewed 42 out of 42 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/unit/test_step_failure_salvage.py Unit coverage for near-optimal stall salvage classification.
tests/unit/test_sparse_operator_symmetry.py Verifies sparse-operator symmetry hint behavior and lazy CSR matvec.
tests/unit/test_s2mpj_adapter.py Unit tests for S2MPJ bridge edge cases and exact-Hessian sign/σ handling.
tests/unit/test_restoration.py Regression test for restoration handling of singular Gauss–Newton solves.
tests/unit/test_operator_coo.py Adds tests for operator-level symmetry hints in condensed/saddle operators.
tests/unit/test_logging.py Tests for duplicate-handler prevention and acceptable-iterate row marking.
tests/unit/test_linear_ineq_lowering.py Unit tests for linear-inequality lowering algebra and validation errors.
tests/unit/test_init_bounds.py Unit tests for fixed/degenerate bound relaxation at initialization.
tests/unit/test_filter_ls.py Regression test for overflow-safe switching-condition power.
tests/property/test_derivative_checks.py Adds FD-vs-analytic derivative checks for HS oracle problems.
tests/integration/test_s2mpj_corpus.py Gated integration tests for S2MPJ checkout-backed correctness across backends.
tests/integration/test_linear_inequalities.py End-to-end solver tests for two-sided linear inequalities across backends.
tests/integration/test_known_optima.py Integration regression test for fixed-variable solving via bound relaxation.
tests/integration/test_hock_schittkowski.py Expands HS integration coverage (HS9/21/28/71).
tests/integration/test_callback_and_logging.py Tests periodic header reprint and acceptable-iterate marking in iteration logs.
tests/contracts/test_builtin_operator_contracts.py Adds contract tests for new public VStack operator.
mkdocs.yml Adds benchmarks section entry for S2MPJ/CUTEst docs page.
ipax/testing/problems.py Adds HS9/21/28/71 analytic oracle problems.
ipax/solve.py Enables linear-ineq lowering and bound relaxation; wires them into the solve flow.
ipax/problem/linear_ineq.py New lowering wrapper that appends lowered linear rows to inequality system.
ipax/options.py Makes gradient-based scaling the default and updates docstring accordingly.
ipax/linalg/sparse.py Forwards symmetry hints to sparse adapters when assembling from COO.
ipax/ipm/termination.py Adds non-mutating conditions_hold for acceptable-iterate marking.
ipax/ipm/restoration.py Makes restoration tolerate singular/non-finite LM steps via retry/λ growth.
ipax/ipm/kkt.py Declares condensed/saddle operators structurally symmetric via symmetry_hint().
ipax/ipm/init.py Adds relax_fixed_bounds to widen fixed/near-degenerate bound pairs.
ipax/ipm/filter_ls.py Adds _safe_pow to avoid OverflowError in switching condition.
ipax/ipm/driver.py Promotes VStack, adds periodic header printing, acceptable marking, and stall salvage logic.
ipax/backend/sparse/numpy_scipy.py Adds symmetry hint support and lazy CSR/CSC materialization + cached symmetry verdict.
ipax/backend/sparse/cupy.py Adds symmetry hint support and cached symmetry verdict for GPU sparse operator.
ipax/backend/sparse/_routing.py Routes symmetric hint through adapter factory.
ipax/backend/operators.py Introduces public VStack and adds base symmetry_hint() capability.
ipax/_logging.py Adds header repeat interval, acceptable row marker, and avoids duplicate console handlers.
docs/concepts/linalg.md Documents a known Krylov limitation for equality-constrained saddles.
docs/benchmarks/s2mpj.md New documentation page for S2MPJ/CUTEst benchmark methodology and results.
CHANGELOG.md Populates [Unreleased] with v0.3.0 candidate highlights and fixes.
benchmarks/runners/s2mpj.py New S2MPJ sweep runner with per-route caps, resume/exclude, and reporting.
benchmarks/harness/init.py Adds dataset-sourced expected-outcome scoring fields and report formatting updates.
benchmarks/corpus/s2mpj.py New S2MPJ problem loader + NumPy→backend bridge, including exact-Hessian bridge.
benchmarks/corpus/init.py Extends default benchmark corpus with added HS problems.
AGENTS.md Adds a documented release workflow and references the new /release command.
.claude/commands/release.md Adds an agent command describing the release workflow gates and steps.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread benchmarks/corpus/s2mpj.py
Comment thread ipax/_logging.py
Comment thread ipax/ipm/restoration.py
Comment thread ipax/problem/linear_ineq.py
wahln and others added 3 commits June 25, 2026 10:04
Review comments (PR #3):
- s2mpj loader: close the source file via `with open(...)` (no fd leak over a
  full-corpus sweep).
- _logging.configure_verbosity: drop ipax's owned console handler when the
  application has attached its own (even if ours was created first), so they don't
  both emit; regression test added.
- restoration LM step: re-raise MemoryError rather than treating it as a rejected
  step under the broad backend-agnostic solve guard.
- linear_ineq: reword the operator-rejection message — an explicit Dense/sparse
  operator is rejected too, not only matrix-free; only rank-2 arrays are supported.

qc gate: HS71 × the Mehrotra/Gondzio correctors sits at the convergence edge of
this nonconvex problem and stalls on some backends/platforms (CI Torch) while
converging on others. Add a per-problem `exclude_configs` to the QC corpus and
skip those configs for HS71 so the gate is deterministic; HS71 still runs on every
stable route and is covered under the default solve by the integration tests.
The corrector-robustness gap is tracked as follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The operator-rejection message was reworded (review fix) and no longer contains
"matrix-free"; assert on the stable "ineq_constraints" guidance instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Finalize CHANGELOG (## [0.3.0] - 2026-06-26, move Unreleased items in, update
compare links) and bump ipax.__version__ to 0.3.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@wahln wahln merged commit 627aa07 into main Jun 25, 2026
10 checks passed
@wahln wahln deleted the rc/v0.3.0 branch June 25, 2026 23:20
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