Avatar for the Jij-Inc user
Jij-Inc
ommx
BlogDocsChangelog

Performance History

Latest Results

Auto-convert unsupported constraint types in SolverAdapter (#814) ## Summary - Add `Instance::reduce_capabilities(supported)` in the Rust SDK. For every capability in `required_capabilities() - supported`, it invokes the corresponding bulk conversion (`convert_all_indicators_to_constraints`, `convert_all_one_hots_to_constraints`, `convert_all_sos1_to_constraints`) and returns the list of converted capabilities. - Switch `SolverAdapter.__init__` from `check_capabilities` (which rejected unsupported types) to `reduce_capabilities`. Unsupported capabilities are now converted to regular constraints in place, and each conversion is logged at `INFO` level. - Remove `Instance::check_capabilities` and the `UnsupportedCapabilities` error from the Rust SDK (and `Instance.check_capabilities` from the Python binding). Internal callers in `as_qubo_format` / `as_hubo_format` now use `required_capabilities()` directly with an inline bail. Expose `Instance.required_capabilities` as a property on the Python binding so callers can diff it against an adapter's `ADDITIONAL_CAPABILITIES` themselves. - Rename `convert_one_hots_to_constraints` β†’ `convert_all_one_hots_to_constraints` so the bulk-conversion API is uniform across all three capability types. - Update the implement-adapter tutorial (en / ja) and the 3.0 release notes to describe the new flow. ## Motivation Previously, declaring `ADDITIONAL_CAPABILITIES` on an adapter meant rejecting any instance whose constraint types weren't in the declared set. Since regular constraints are always supported and we already have Big-M / linear-equality conversions for indicator, one-hot, and SOS1, rejection was unnecessarily strict. The new behavior lets every adapter accept any instance by automatically degrading unsupported capabilities into regular constraints. The resulting conversions are visible on the instance via `removed_indicator_constraints` / `removed_one_hot_constraints` / `removed_sos1_constraints` (each has a `reason` and a `constraint_ids` parameter pointing to the generated regular constraints), plus an `INFO` log line per converted capability. ## Notes - `reduce_capabilities` mutates the instance in place, matching the existing `convert_*` APIs. - Each per-type `convert_all_*` is atomic, but `reduce_capabilities` is not atomic *across* types: earlier conversions aren't rolled back if a later one fails. - SOS1 / indicator conversion can still fail (non-finite bounds, semi-continuous / semi-integer variables, domain excluding 0); those errors now surface from the adapter's `__init__` rather than from a separate capability check. - **Breaking**: `Instance.check_capabilities` is removed. Callers that relied on it should either use `reduce_capabilities` (to auto-convert) or read `required_capabilities` and diff against their supported set manually. ## Test plan - [x] `task rust:test` β€” 438 + 44 doctests pass, including 6 new tests for `reduce_capabilities` (no-op, partial conversion, full conversion, unrequired capability skipped, error propagation, integer SOS1 with fresh indicator) - [x] `task python:test` β€” all adapter and core SDK tests pass - [x] `task rust:clippy` β€” clean - [x] `task format` β€” applied πŸ€– Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
main
6 hours ago
Convert indicator constraints to regular constraints via Big-M (#811) ## Summary - Adds `Instance::convert_indicator_to_constraint(id)` / `convert_all_indicators_to_constraints()` in Rust and Python, following the SOS1 pattern from #810. Unlike SOS1, no fresh indicator variables are allocated β€” the existing `indicator_variable` of the `IndicatorConstraint` plays the role of $y$ in the Big-M. - For an indicator $y = 1 \Rightarrow f(x) \leq 0$ (or $= 0$), the upper-side Big-M `f(x) + u y - u <= 0` is emitted when $u = \sup f(x) > 0$. For equality indicators, the lower-side `-f(x) - l y + l <= 0` is additionally emitted when $l = \inf f(x) < 0$. Sides already implied by the variable bounds (e.g. $u \leq 0$) are skipped as redundant. Validation is all up-front: a non-finite bound on a required side fails the whole call without mutating state, and the bulk variant stays atomic across every active indicator. - Python surface mirrors the SOS1 / one-hot set: `RemovedIndicatorConstraint`, `Instance.removed_indicator_constraints`, `removed_indicator_constraints_df`. The original indicator is moved to `removed_indicator_constraints` with `reason = "ommx.Instance.convert_indicator_to_constraint"` and a comma-separated `constraint_ids` parameter (empty when no Big-M was emitted). ## Test plan - [x] `cargo test -p ommx --lib instance::indicator` β€” 7 new tests: inequality-only upper Big-M, equality emits both sides, redundant-side skipping, non-finite-bound rejection without mutation, missing-id without mutation, bulk per-indicator IDs, bulk atomicity on error - [x] `cargo test -p ommx --lib` β€” 431 pass (up from 424) - [x] `cargo clippy --all-targets` clean - [x] `task python:test` β€” full Python suite green; Rust doctest + Python doctest both pass - [x] 6 new Python integration tests in `python/ommx-tests/tests/test_indicator_constraint.py` cover inequality, equality, redundant skip, non-finite rejection, bulk atomic rollback, and `removed_indicator_constraints_df` round-trip ## Notes - Out of scope: updating solver adapters to advertise post-conversion capability. After this PR, callers can drop `AdditionalCapability::Indicator` requirements by converting first. - The `RemovedReason.reason` convention tightened in #805 and #810 is followed here (`ommx.Instance.convert_indicator_to_constraint`). πŸ€– Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
main
8 hours ago
Benchmark pyscipopt adapter: SOS1 vs big-M (#809) ## Summary Adds a Plant Placement Problem benchmark for `ommx-pyscipopt-adapter` that compares **eight** equivalent OMMX formulations of "at most one plant per region", spanning three orthogonal axes: - **`Ξ΄ + big-M`** β€” whether the binary opening indicator `Ξ΄_i ∈ {0, 1}` and the big-M link `c_i ≀ C_i Β· Ξ΄_i` are introduced. - **`Σδ ≀ 1`** β€” whether the explicit linear cardinality bound on `Ξ΄` is added. *Note*: when SOS1 is also declared (on `c`, on `Ξ΄`, or both), this linear bound is **logically redundant** β€” SOS1 already implies it. So the `_with_card` row in each pair is testing whether SCIP gets value out of the redundant hint, or whether it just adds overhead. The exception is (8) `bigm`, which has no SOS1 β€” there `Σδ ≀ 1` is the only thing enforcing "at most one plant per region", not redundant. - **SOS1 location** β€” on the continuous capacities `{c_i}`, on the binary indicators `{Ξ΄_i}`, on **both** (redundant), or absent. The eight builders enumerate every well-defined combination: | | Builder | Ξ΄ + big-M | `Σδ ≀ 1` | SOS1 on `c` | SOS1 on `Ξ΄` | |---|---|:---:|:---:|:---:|:---:| | (1) | `build_sos1` | – | – | βœ“ | – | | (2) | `build_sos1_on_c_with_delta` | βœ“ | – | βœ“ | – | | (3) | `build_sos1_on_c_with_delta_with_card` | βœ“ | βœ“ | βœ“ | – | | (4) | `build_sos1_on_delta` | βœ“ | – | – | βœ“ | | (5) | `build_sos1_on_delta_with_card` | βœ“ | βœ“ | – | βœ“ | | (6) | `build_sos1_on_both_with_delta` | βœ“ | – | βœ“ | βœ“ | | (7) | `build_sos1_on_both_with_delta_with_card` | βœ“ | βœ“ | βœ“ | βœ“ | | (8) | `build_bigm` | βœ“ | βœ“ | – | – | All eight have the same feasible region on `(s, c)` and the same optimum (verified by `test_placement_equivalence.py`). A note on (2): `Ξ΄` is introduced but is *not* constrained by anything except the big-M link `c_i ≀ C_i Β· Ξ΄_i`, which only enforces "`Ξ΄_i = 0` β‡’ `c_i = 0`" (one direction). Multiple `Ξ΄_i` could remain `1` simultaneously; "at most one plant per region" is enforced *entirely* by SOS1 on `c`. So `Ξ΄` in (2) is essentially a "free spectator" variable. As an `(s, c)` optimization problem this is mathematically valid (objective doesn't depend on `Ξ΄`), but `Ξ΄` carries no semantic meaning. (2) functions as a **control case** isolating "does adding `Ξ΄` to the model alone slow SCIP down" from any other effect. ## What is measured The benchmark measures **SCIP's own processing time** (presolve + B&B), isolated from the adapter: - `Input.random` β†’ `ommx.v1.Instance` β†’ `pyscipopt.Model` is built in **session-scoped fixtures**, outside the measured region. - Each benchmark calls `model.freeTransform()` (discards SCIP's transformed problem from any previous run) + `model.optimize()` on the pre-built models. This avoids biasing the comparison by adapter conversion cost, which scales with variable/constraint count and is therefore *cheaper* for `build_sos1` (no `Ξ΄`, fewer constraints) than for the seven Ξ΄-bearing variants. Since the measured time is essentially "how long SCIP takes" (the adapter is out of the loop), this benchmark is most useful as a **one-off comparison run locally** to understand how the formulation choice interacts with SCIP. It is **not** wired into `.github/workflows/bench.yml` β€” tracking it in the CodSpeed CI dashboard would mostly track SCIP's behavior across upstream changes, which is out of scope for this repo. ## Placement problem module (reusable) Lives at **`ommx.testing.placement`** so other adapters (`ommx-highs-adapter`, `ommx-python-mip-adapter`, …) can reuse the same comparison. The module docstring documents the full mathematical formulation β€” sets, parameters, decision variables, constraints, objective, and the 8-row table above β€” rendered under KaTeX in the API Reference (AutoAPI picks up `ommx.testing.placement` automatically under the existing `ommx.testing` entry). To keep existing imports `from ommx.testing import SingleFeasibleLPGenerator, DataType` working, `testing.py` was converted to a `testing/` package (contents moved to `testing/__init__.py` unchanged); `placement.py` is a sibling submodule. ## Files - `python/ommx/ommx/testing/placement.py` β€” `Input/Plant/Client` dataclasses, the eight builders, a shared `_build_with_delta` backbone parameterised by the three axis flags, and the full problem spec in the module docstring. - `python/ommx/ommx/testing/__init__.py` β€” renamed from the old `testing.py`, contents unchanged. - `python/ommx-pyscipopt-adapter/tests/test_placement_equivalence.py` β€” verifies all eight builders reach the same optimum on a small instance. - `python/ommx-pyscipopt-adapter/tests/test_bench_placement.py` β€” `@pytest.mark.benchmark` tests parameterised over problem sizes for each builder; measures pure SCIP time via fixture-prebuilt models + `freeTransform()` + `optimize()`. - `python/ommx-pyscipopt-adapter/Taskfile.yml` β€” `pytest` now excludes `test_bench_*.py`; adds `pytest-bench` (walltime) and `bench` (`--codspeed`). ## Local codspeed measurement (pure SCIP time) `task python:ommx-pyscipopt-adapter:bench` on a dev machine (3 instances per param set; walltime mode; total runtime β‰ˆ5.5 min): | size | sos1 (1) | sos1_on_c_with_delta (2) | …with_card (3) | sos1_on_delta (4) | …with_card (5) | sos1_on_both (6) | …with_card (7) | bigm (8) | |---|---|---|---|---|---|---|---|---| | 6 Γ— 10 | 1.8 ms | 1.8 ms | 4.5 ms | 4.8 ms | 5.2 ms | 4.6 ms | 4.6 ms | 5.1 ms | | 12 Γ— 20 | 17.5 ms | 17.5 ms | 1.04 s | 374.5 ms | 378.1 ms | 1.05 s | 1.04 s | 370.6 ms | | 24 Γ— 40 | 246.9 ms | 242.2 ms | 1.67 s | 1.90 s | 1.87 s | 1.71 s | 1.67 s | 1.88 s | | 48 Γ— 80 | 3.02 s | 3.02 s | 12.64 s | 13.48 s | 13.17 s | 12.59 s | 12.58 s | 13.18 s | ### How to read the table **Reproducibility vs typicality.** `random.seed(42)` is set in `placement_inputs` per size, and `Input.random` β†’ `Instance.from_components` β†’ `pyscipopt.Model` are all deterministic, as is SCIP in sequential mode. So these numbers are **fully reproducible**: anyone running `task …:bench` on a comparable machine sees the same SCIP B&B paths and ms values (modulo OS-level noise). However, the Plant Placement problem's difficulty depends strongly on the specific random draw β€” plant positions, east/west balance, capacity distribution β€” and a quick 5-seed sweep at 12Γ—20 shows the "slow" variants swinging by 3–5Γ— between seeds: | seed | (3) c+Ξ΄+card | (4) Ξ΄ | (5) Ξ΄+card | (6) both | (7) both+card | (8) bigm | |---|---|---|---|---|---|---| | **42 (table above)** | 501 | 654 | 721 | 603 | 499 | 729 ms | | 1 | 132 | 178 | 182 | 134 | 135 | 178 ms | | 7 | 143 | 148 | 139 | 162 | 143 | 139 ms | | 100 | 151 | 179 | 173 | 153 | 150 | 173 ms | | 2026 | 152 | 177 | 173 | 162 | 153 | 167 ms | So **individual ms values are reproducible but not representative** β€” seed=42 happens to draw 12Γ—20 instances that are notably hard. What we read off the main table should be the tier structure, not the fine-grained ordering within a tier. ### Observations - **(1) and (2) are robustly the fastest, and they are tied at every size.** Adding `Ξ΄ + big-M` to a SOS1-on-`c` model (going from (1) to (2)) does *not* slow SCIP down β€” even when `Ξ΄` has no role beyond the big-M link. The cost of `Ξ΄` per se is essentially zero. - **The gap opens up as size grows.** At 6Γ—10 all eight are within ~3Γ—. By 48Γ—80, (1)/(2) at 3s are roughly **4Γ— faster** than the cluster at 12–13s. The SOS1-on-`c`-alone advantage is the benchmark's strongest and most robust signal, and it holds across every seed in the sweep above. - **Any extra per-region structure layered on top of SOS1-on-`c` breaks the advantage.** The moment cardinality (3) or a second SOS1 on `Ξ΄` ((6), (7)) is added, the runtime jumps by roughly an order of magnitude compared to (1)/(2). The redundancy seems to confuse rather than help SCIP's branching. This is robust across seeds. - **Without SOS1 on `c`, the model lands in the same slow tier.** (4), (5), (8) cluster together (within noise of each other at each size). Replacing the cardinality bound with SOS1 on `Ξ΄` is a wash. - **Fine-grained ordering within the slow tier is instance-specific.** At 12Γ—20 seed=42, (3)/(6)/(7) are notably slower than (4)/(5)/(8); at 24Γ—40 seed=42, they're comparable; at 48Γ—80 seed=42, they're slightly faster; at other 12Γ—20 seeds, the ordering reverses or collapses. These reversals are not informative β€” read the table for tier-level structure, not for individual cell ordering. Bottom line: for this problem on SCIP, the lever is **declaring SOS1 on the continuous capacities alone**, with no other per-region structure layered on top. `Ξ΄ + big-M` may be present (it's a no-op in this regime), but adding either the linear cardinality bound or a second SOS1 on `Ξ΄` costs roughly an order of magnitude. Without SOS1 on `c` at all, SCIP lands in the same slower regime regardless of how cardinality is expressed. ## Test plan - [x] `task python:ommx-pyscipopt-adapter:test` passes (40 unit tests incl. eight-way equivalence test) - [x] `task python:ommx:test` passes β€” existing `from ommx.testing import ...` consumers (`highs`, `python-mip`, `pyscipopt` adapters) all green after the package-ification - [x] `task python:ommx-pyscipopt-adapter:pyright` / `task …:lint` clean - [x] `task python:ommx-pyscipopt-adapter:bench` runs end-to-end under `--codspeed` for all eight targets - [x] Sphinx docs build; `autoapi/ommx/testing/placement/index.html` is generated with KaTeX-rendered math (alignment blocks wrapped in `\begin{aligned}...\end{aligned}`) πŸ€– Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Hiromi Ishii <konn.jinro@gmail.com>
main
8 hours ago
docs: resolve all Sphinx warnings and gate builds on -W (#813) ## Summary - Resolve all 45 Sphinx warnings in the English and Japanese documentation builds. - Add `-W --keep-going` to every `sphinx-build` invocation so new warnings become hard CI failures. ## What changed - **`docs/conf_base.py`** - Register `sphinx.ext.doctest` so `.. doctest::` blocks in the PySCIPopt / Python-MIP adapters render (fixes 12 `Unknown directive type "doctest"` errors). - Suppress `autoapi.python_import_resolution` warnings β€” `ommx.v1`, `ommx._ommx_rust`, and `ommx.artifact` are intentionally documented via `pyo3_stub_gen_ext` rather than autoapi, so those import-resolution notices are expected (fixes 17 warnings). - Exclude `autoapi/ommx/index.rst` from Sphinx's toctree check. Every submodule is already linked from `docs/api/index.rst`, so the auto-generated package index is redundant (fixes 1 "document isn't included in any toctree" warning). - **`python/ommx/src/instance.rs`** β€” change the `Instance.stats()` return-shape fenced block from ` ```json ` to ` ```text `. The snippet uses `int` as a type placeholder, which is not valid JSON and was tripping Pygments (fixes 1 `misc.highlighting_failure`). - **`python/ommx-highs-adapter/ommx_highs_adapter/adapter.py`** β€” rewrite the module/`solve` docstring: - Replace GitHub-flavored Markdown tables with RST `.. list-table::` directives (fixes undefined-substitution errors caused by the ` | ---- | ---- | ` separators being parsed as RST substitution references). - Flatten the awkwardly-indented bullet list under `Parameters` that produced "Unexpected indentation" / "Block quote ends without a blank line". - **`docs/api/api_reference.json` + `python/ommx/ommx/_ommx_rust/__init__.pyi`** β€” regenerated via `task python:stubgen` to pick up the Rust docstring edit. - **`docs/en/Taskfile.yml`, `docs/ja/Taskfile.yml`, `python/Taskfile.yml`** β€” add `-W --keep-going` to every sphinx-build task (`book`, `book:test`, `book:test:ja`, `book:test:en`, `book_en:build`, `book_ja:build`). CI runs `task python:book:test:${lang}`, so the gate is now enforced in CI as well. ## Test plan - [x] `task book_en:build` (English docs) β€” build succeeded with `-W`, 0 warnings - [x] `task book_ja:build` (Japanese docs) β€” build succeeded with `-W`, 0 warnings - [x] `task python:stubgen` β€” regenerates `api_reference.json` and the `.pyi` stub cleanly - [ ] CI `test-book` job (ja and en) passes on this PR πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
main
9 hours ago
Convert SOS1 constraints to regular constraints via Big-M (#810) ## Summary - Adds `Instance::convert_sos1_to_constraints(id)` / `convert_all_sos1_to_constraints()` in Rust and Python, following the pattern of #805 (one-hot conversion). Unlike one-hot, SOS1 needs Big-M since the rewrite is not a mathematical identity, so the return type is `Vec<ConstraintID>` per SOS1 (and a `BTreeMap<Sos1ConstraintID, Vec<ConstraintID>>` for the bulk variant). - Per variable: if `x_i` is binary with bound `[0, 1]`, reuse it as its own indicator; otherwise introduce a fresh binary `y_i` and emit `x_i - u_i y_i <= 0` and `l_i y_i - x_i <= 0` (trivial sides are skipped). One cardinality constraint `sum_i y_i - 1 <= 0` is always added. Non-binary variables with non-finite bounds or bounds that exclude 0 are rejected; validation happens before any mutation so failures leave the instance unchanged. - Python surface mirrors the one-hot set: `RemovedSos1Constraint`, `Instance.removed_sos1_constraints`, and `sos1_constraints_df` / `removed_sos1_constraints_df` accessors. The original SOS1 is moved to `removed_sos1_constraints` with `reason = "ommx.Instance.convert_sos1_to_constraints"` and a comma-separated `constraint_ids` parameter. ## Test plan - [x] `cargo test -p ommx --lib instance::sos1` (7 new tests: binary reuse, integer Big-M pair, trivial-side skipping, infinite-bound rejection, bound-excluding-zero rejection, missing-ID atomicity, bulk) - [x] `cargo test -p ommx --lib` (421 tests pass, up from 414) - [x] `cargo clippy --all-targets` clean - [x] `task python:test` (full Python suite passes; doctests exercise single + bulk for the all-binary reuse path) - [x] 3 additional Python integration tests: integer Big-M pair with fresh indicators, domain-excluding-zero rejection without mutation, and `removed_sos1_constraints_df` round-trip ## Notes - Out of scope: updating solver adapters to advertise post-conversion capability. After this PR, callers can drop SOS1 support requirements by converting first. - The `RemovedReason.reason` convention tightened in #805 is followed here (`ommx.Instance.convert_sos1_to_constraints`). πŸ€– Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
main
24 hours ago
docs(book): update examples for #806 + gate CI on notebook errors (#808) ## Summary Three-part change prompted by #806 silently breaking every Book example while CI stayed green: 1. **CI gate** β€” set `nb_execution_raise_on_error = True` in [docs/conf_base.py](docs/conf_base.py) so `sphinx-build` (via `task python:book:test:{en,ja}`) exits non-zero when any `{code-cell} ipython3` cell raises. Before this, the `test-book` CI jobs reported `success` even with every Book example broken by #806 ([test-book (en) on #806 merge](https://github.com/Jij-Inc/ommx/actions/runs/24647276119/job/72062542623)). 2. **Book content** β€” update every EN/JA notebook under `docs/{en,ja}/{user_guide,tutorial}/` to the post-#806 API. 22 files, Β±80 lines. 3. **Migration guide** β€” backfill [PYTHON_SDK_MIGRATION_GUIDE.md](PYTHON_SDK_MIGRATION_GUIDE.md) with the two v2.5.1β†’v3 changes the Book fix revealed but the guide had missed (evaluate exception type, `ParametricInstance.parameters` DataFrame split). ## Book content changes Applied mechanically, once per language: - `Instance.from_components(constraints=[...])` β†’ `constraints={id: ...}` β€” `constraints` arg is now `dict[int, Constraint]` (Β§4.1 of the migration guide). - `for c in instance.constraints:` β†’ `for cid, c in instance.constraints.items():` when the loop body needs the ID β€” `.id` on `Constraint` no longer exists (Β§3.1, Β§4.2). - Drop `.set_id(...)` on detached constraints; ID comes from the `from_components` dict key (Β§3.1). Pre-existing breakages exposed once the gate started working (now also covered in the migration guide): - `except RuntimeError` β†’ `except ValueError` for `Function.evaluate` / `Linear.evaluate` / etc. (new Β§6.5). - `Parameter.new(id=...)` β†’ `Parameter(id, ...)` (already in Β§5.3 β€” Book just hadn't been updated). - `ParametricInstance.parameters` now returns `list[Parameter]`; use `parameters_df` for the DataFrame (new Β§6.6). ## Migration guide additions New sections in [PYTHON_SDK_MIGRATION_GUIDE.md](PYTHON_SDK_MIGRATION_GUIDE.md): - **Β§6.5** β€” `evaluate` / `partial_evaluate` raise `ValueError` (was `RuntimeError` via anyhow in v2.5.1), with a before/after snippet. - **Β§6.6** β€” `ParametricInstance.parameters` is `list[Parameter]`; `parameters_df` gives the DataFrame, mirroring the existing `_df` split on `decision_variables` / `constraints`. - Two matching entries in the migration checklist. ## Verification **Before Book fix** (only gate added): - [test-book (en)](https://github.com/Jij-Inc/ommx/actions/runs/24647753609/job/72063901646) **fail** with `myst_nb.core.execute.base.ExecutionError: docs/en/tutorial/implement_adapter.md` - [test-book (ja)](https://github.com/Jij-Inc/ommx/actions/runs/24647753609/job/72063901655) **fail** analogously **After Book fix** (local): ``` task python:book:test:en # β†’ build succeeded, 45 warnings (no execution errors) task python:book:test:ja # β†’ build succeeded, 45 warnings (no execution errors) ``` (The 45 remaining warnings are pre-existing `autoapi` issues β€” unknown `doctest` directive and undefined substitutions in RST generated from adapter docstrings. Unrelated to notebook execution.) ## Test plan - [ ] `test-book (en)` passes on GitHub Actions - [ ] `test-book (ja)` passes on GitHub Actions - [ ] Other CI jobs stay green (no code changes outside `docs/` and `PYTHON_SDK_MIGRATION_GUIDE.md`) ## Out of scope `release_note/ommx-1.*.md` are already excluded from notebook execution via `nb_execution_excludepatterns`, so outdated historical examples there (`set_id(...)`, list-form `constraints`) are left as-is to preserve the record of older APIs. πŸ€– Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
main
1 day ago
Convert one-hot constraints to regular equality constraints (#805) ## Summary A one-hot constraint over `{x_1, ..., x_n}` is mathematically equivalent to the linear equality `x_1 + ... + x_n - 1 == 0`. This PR adds helpers that perform that conversion, and along the way formalizes how `RemovedReason.reason` strings should be written so external libraries (solver adapters, preprocessing passes) can emit unambiguous, traceable values. ## Rust - `Instance::convert_one_hot_to_constraint(id) -> ConstraintID` β€” single-shot conversion. - `Instance::convert_one_hots_to_constraints() -> Vec<ConstraintID>` β€” bulk conversion of every active one-hot. - Each new regular `Constraint` records a `Provenance::OneHotConstraint(id)` entry on its metadata so the transformation chain stays traceable. - The original one-hot is moved to `removed_one_hot_constraints` with `reason = "ommx.Instance.convert_one_hot_to_constraint"` and a `constraint_id` parameter pointing at the new regular constraint. - The new `ConstraintID` comes from `ConstraintCollection::unused_id`, avoiding collisions with existing active or removed regular constraints. ### RemovedReason convention `RemovedReason.reason` is now documented as a fully qualified, dot-separated path identifying the code that performed the removal. External libraries are expected to mutate constraint collections, so having a stable identifier matters. Existing ommx-internal call sites are updated to match: | Before | After | | --- | --- | | `penalty_method` | `ommx.Instance.penalty_method` | | `uniform_penalty_method` | `ommx.Instance.uniform_penalty_method` | | `unit_propagation` | `ommx.Instance.partial_evaluate.unit_propagation` | | `convert_inequality_to_equality_with_integer_slack` | `ommx.Instance.convert_inequality_to_equality_with_integer_slack` | ## Python - `Instance.convert_one_hot_to_constraint(one_hot_id)` / `Instance.convert_one_hots_to_constraints()` β€” PyO3 wrappers with runnable doctests exercised by the existing `test_doctests` harness. - New `RemovedOneHotConstraint` class (exposed via `ommx.v1`) wrapping `(OneHotConstraint, RemovedReason)` so the removal reason and parameters are inspectable. - `Instance.removed_one_hot_constraints` β€” dict keyed by `OneHotConstraintID`, mirroring the existing `removed_constraints` accessor. - DataFrame getters: `Instance.one_hot_constraints_df` / `Instance.removed_one_hot_constraints_df`. Columns: `id` (index), `variables` (set), `num_variables`, `used_ids`, `name`, `subscripts`, `description`; the removed variant additionally emits `removed_reason` and `removed_reason.<key>` columns. ## Notes - SOS1 has an analogous shape, but converting it to regular constraints requires Big-M (not a mathematically-equivalent rewrite), so it's out of scope here. - The `RemovedReason.reason` field remains `String` on the wire (proto `string removed_reason`); this PR only tightens the convention documented on the Rust struct. Value updates will surface in any stored artifacts that round-trip through this code path. - Rebased on top of #806 (constraint types no longer carry `id` fields) β€” the new code uses BTreeMap keys as the sole source of ID throughout. ## Test plan - [x] `cargo test -p ommx --lib instance::one_hot` (3 new tests: single-shot, missing ID atomicity, bulk) - [x] `cargo test -p ommx --lib` (full Rust suite, 414 tests) - [x] `task python:ommx:test` (pytest + doctests, including the updated `penalty_method` / `uniform_penalty_method` doctest output) - [x] Manual: inspect `one_hot_constraints_df` / `removed_one_hot_constraints_df` output on a two-one-hot instance - [x] Each commit compiles and tests pass in isolation (bisect-friendly) πŸ€– Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
main
1 day ago

Latest Branches

CodSpeed Performance Gauge
N/A
Fix CodSpeed Action v4 mode requirement in benchmark workflow#637
7 months ago
7abe1e8
copilot/fix-cd351290-4957-4ccd-92ca-cb64db14baf1
CodSpeed Performance Gauge
N/A
9 months ago
187a53d
python-tsp-qubo-bench
CodSpeed Performance Gauge
N/A
10 months ago
326030a
migrate-solution-sampleset
Β© 2026 CodSpeed Technology
Home Terms Privacy Docs