fix: raise clear error when solving model without objective#671
Merged
FabianHofmann merged 4 commits intomasterfrom May 4, 2026
Merged
fix: raise clear error when solving model without objective#671FabianHofmann merged 4 commits intomasterfrom
FabianHofmann merged 4 commits intomasterfrom
Conversation
Previously, `Model.solve()` on a model without an explicit objective silently wrote a bare ` x` token into the LP file's objective section, which solvers (e.g. HiGHS) interpreted as a phantom variable named `x`. Parsing the solution then failed in `set_int_index` with `ValueError: invalid literal for int() with base 10: 'x'`. Forgetting to set an objective is almost always a bug, so raise an informative `ValueError` early in `solve()` instead of silently solving a zero-objective feasibility problem. Users who genuinely want feasibility can pass `m.add_objective(0 * x)`. Fixes #668 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The new "no objective set" guard in `Model.solve()` correctly fires before the remote-handler branch, so the mock-based forwarding test needs a real objective to reach the path it is exercising. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The notebook calls `m.solve(...)` purely to trigger infeasibility diagnostics, but `Model.solve()` now requires an explicit objective. Add `m.add_objective(0 * x)` with a comment explaining why. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`Model.solve()` now requires an explicit objective; this test was exercising the cbc unsupported-solver path without one, so add a trivial objective to reach the assertion under test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
FabianHofmann
approved these changes
May 4, 2026
2 tasks
FBumann
added a commit
that referenced
this pull request
May 7, 2026
* docs: restructure upcoming release notes and fold in missing PRs Group the upcoming version block into Features / Performance / Bug Fixes / Breaking Changes / Documentation sections so the headline (piecewise) leads, and add the entries for #589, #595, #601, #614, #619, #635, #656, #671, #672, #674. Tighten the piecewise block to its final state. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: tighten upcoming changelog and drop internal-only entries Trim verbose phrasing in the piecewise / variables / model / solvers sections, fold subset-superset sub-bullets into one paragraph, and drop two entries that aren't user-facing for a release notes audience: sphinx-copybutton (doc tooling) and Model.__weakref__ (only relevant to extension authors). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: move align convention from breakpoints() to Slopes in changelog #673 removed the slopes-mode (and slopes_align kwarg) from breakpoints(); the align kwarg now lives on the Slopes class. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: move SOS reformulation bullet from Variables to Model SOS reformulation is a model-rewrite/solve-pipeline concern, not a variable attribute. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: split coord alignment into Expressions, move CPLEX to Bug Fixes - New *Expressions* subsection holds the subset/superset coord harmonization, which was misfiled under *Model*. - CPLEX quality-attribute handling is a fix for crashes on missing attributes, not a new feature — moved to **Bug Fixes**. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: fold as_dataarray MultiIndex fix into add_variables bullet #659 fixes a regression introduced by #614 in the same release cycle — no end user ever saw the broken state, so a standalone bullet overstates the change. Net behavior is captured by extending the add_variables bullet to mention MultiIndex coords. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: tighter pass on upcoming changelog Drop implementation details that belong in API docs (numpy-vs-pandas note, JSON encoding for netCDF, "with no auxiliary variables" piecewise detail), merge the two OETC bullets, and trim "Add X. Supports Y." wrappers across most lines. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: rephrase active gating bullet to avoid output-zeroing implication Previous wording ("zeros all auxiliaries when off") was true at the auxiliary level but glossed over the bounded-tuple case where the output is not automatically pinned to 0. Drop the implication and defer the detail to the docstring. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: drop option-name detail from upcoming changelog Trim references to specific kwargs/attributes the reader doesn't need in the high-level summary: method="auto" parens, align="pieces|leading", deep / include_solution, reformulate_sos="auto", solver_name / **solver_options, max_dual_infeasibility example, and the operator-by-operator coord-alignment breakdown. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <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.
Summary
Model.solve()now raisesValueErrorearly when no objective has been set, instead of silently writing a malformed LP file with a phantomxvariable that later crashesset_int_index.m.add_objective(...), withm.add_objective(0 * x)suggested for the pure-feasibility use case.examples/infeasible-model.ipynbto setm.add_objective(0 * x)before its feasibility-check solve.test/test_model.py.Fixes #668
Alternatives considered
varsrows out of the objective polars df inobjective_write_linear_terms. Smallest possible fix — the LP file would no longer contain a phantomx. Rejected because it leaves an emptyobj:block whose acceptance varies across solvers/formats, and because silently solving a zero-objective hides what is almost always a user bug.+0 x0term when the objective is empty. Avoids the empty-block fragility, but still treats "forgot to set objective" as valid input. Same hide-the-bug objection as (1).m.add_objective(0 * x)) for users who genuinely want a feasibility solve, which is exactly what the updated example notebook now demonstrates.Test plan
pytest test/test_model.py -k objectivepasses locallypytest test/test_oetc_settings.py::test_model_solve_forwards_to_oetcpasses (test was updated to add an objective)ValueErrorinstead ofinvalid literal for int() with base 10: 'x'examples/infeasible-model.ipynbexecutes end-to-end after the fix🤖 Generated with Claude Code