Conversation
- 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 Report❌ Patch coverage is
... and 2 files with indirect coverage changes 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
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.
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release candidate for v0.3.0 (onto
main). Version bump and changelogfinalization come after review per the AGENTS.md release workflow — this PR is
for CI + code review of the contents below.
Highlights (from
## [Unreleased])Added
Problem.linear_ineq,l ≤ A x ≤ u) are nowsolved, lowered into the one-sided inequality machinery.
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 Hessianand sparse-direct route; per-route variable caps, sized instantiation, build-time
guard, incremental persistence + resume/exclude,
--config/--names-file, andobjective-free (feasibility) problems. Download-gated, not in per-PR CI.
LO SOLTNobjectiveoracle + documented-infeasible detection (BURKEHAN scored correct).
Changed
ipax.backend.operators.VStack.Fixed
ACCEPTABLEinstead of discarded.x_L == x_U) no longer fail at the first iteration.logging fixed.
See
CHANGELOG.md## [Unreleased]for the full list.🤖 Generated with Claude Code