fix(language): dual(c) is the rate at which the optimal objective rises with the right side of c, so an equality has a sign too - #751
Merged
FBumann merged 10 commits intoSep 29, 2026
Conversation
…es with the right side of c, so an equality has a sign too named.md defined dual(c) as the rate at which the objective improves as c is relaxed in the direction its comparator points. That left an equality without a sign, and it made a <= dual non-negative under minimize. specsolve returns the rate in the right side on HiGHS, Gurobi and Xpress. The page and the Dual docstring now state that definition, the same for every comparator and under either sense. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvcU9JRnsinc9e9syvVcHS
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvcU9JRnsinc9e9syvVcHS
Documentation build overview
56 files changed ·
|
…sign Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvcU9JRnsinc9e9syvVcHS
…sign Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvcU9JRnsinc9e9syvVcHS
…sign Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvcU9JRnsinc9e9syvVcHS
…sign Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvcU9JRnsinc9e9syvVcHS
…sign Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvcU9JRnsinc9e9syvVcHS
…sign Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvcU9JRnsinc9e9syvVcHS
…sign Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvcU9JRnsinc9e9syvVcHS
…sign Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvcU9JRnsinc9e9syvVcHS
FBumann
pushed a commit
that referenced
this pull request
Sep 29, 2026
…ec-analysis-features-zy7x4n-sensitivity named.md takes the dual definition as #751 merged it, and keeps the paragraph on spec.sensitivity after it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CvcU9JRnsinc9e9syvVcHS
FBumann
pushed a commit
that referenced
this pull request
Sep 29, 2026
Brings in #751. CHANGELOG.md keeps both sides. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N66KMDkSv1rZbhr8jqqJzJ
FBumann
pushed a commit
that referenced
this pull request
Sep 29, 2026
Brings in #778, #779 and #751. CHANGELOG.md keeps both sides; nothing else conflicts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N66KMDkSv1rZbhr8jqqJzJ
FBumann
added a commit
to fluxopt/specsolve
that referenced
this pull request
Sep 29, 2026
…th its row's right side, on every sink (#1774) > **Prompt:** Let's do a pr in mathspec and specsolve fixing the dual ambiguity > [!NOTE] > The following content was generated by AI. `Result.dual` states its sign: the rate at which the optimal objective rises with the row's right side, for every comparator and sense. That is the definition energy-models/mathspec#751 gives `dual(c)`. A test pins it on every sink against finite differences. <details><summary>Method, mutation, gates, departures</summary> ### What was ambiguous `Result.dual` said "shadow prices" and nothing about sign. mathspec's page said a dual is the rate at which the objective *improves* as the row is *relaxed*. That makes a `<=` dual under `minimize` non-negative, where every sink here returns it non-positive, and it leaves an `==` row without a sign. No code changes here: all three sinks already return the rate in the right side, as written in the YAML. energy-models/mathspec#751 moves mathspec's definition to match. ### The test `test_a_dual_is_the_rate_of_the_optimum_in_its_rows_right_side`, in `tests/test_duals.py`. One row, "p is at least 2 + b", is written six ways: `p >= 2 + b`, `2 + b <= p`, `-p <= -2 - b`, `p - b >= 2`, `p == 2 + b` and `2 + b == p`. Each runs under `minimize` and `maximize`, on every sink `solver_name` yields. The dual has to equal the finite difference of the objective in `b` (step `1e-3`; the row binds, so the difference is exact), times how `b` moves `rhs - lhs`. Result: 36 passed, highs, gurobi and xpress. Gurobi and Xpress ran on the size-limited licences their PyPI wheels carry. ### Mutation | Mutation | Result | |---|---| | HiGHS's `row_dual` negated in `highs.py` | 12 failed: every HiGHS case | ### Gates `pixi install` could not run in the session: it fetches the conda-to-PyPI mapping from a host the session's network refuses. The gates ran from `uv sync --group dev --extra gurobi --extra xpress` on Python 3.12, with the tool versions the `dev` group pins. - `ruff check .`: clean. - `ruff format --check .`: clean. - `pyrefly check`, both configs: 0 errors. - `pytest -q -n auto`: 4078 passed, 320 skipped, 1 xfailed. `docs-build` was not run. ### Not done - `examples/benders/run.py` already reads the slope with this sign, so it is unchanged. - `dual_ray` has its own convention, stated in `test_dual_ray.py`, and is untouched. </details> 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01CvcU9JRnsinc9e9syvVcHS --- _Generated by [Claude Code](https://claude.ai/code/session_01CvcU9JRnsinc9e9syvVcHS)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
FBumann
pushed a commit
that referenced
this pull request
Oct 1, 2026
The Upcoming section becomes 0.3.0, grouped into composition, language, typesetting and advice, and documentation. The notes name the two breaks against 0.2.0 (#788, #751). The #742 line is left out: #763 replaced `empty: true` before any release carried it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PBmqeqBrqpQC6MVoSrguHK
Merged
FBumann
pushed a commit
that referenced
this pull request
Oct 1, 2026
…s working #788 and #751 change only documentation and a docstring. mathspec neither attaches data nor computes duals, so neither breaks a file or an import. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PBmqeqBrqpQC6MVoSrguHK
FBumann
added a commit
that referenced
this pull request
Oct 1, 2026
* fix(language): a where that reads a given expression names it as a given expression, not as a variable The namespace files a given expression with the variables, because it is read as a column. A where that read one was refused as if it read a variable. The three refusals (the left name, the right name, a side of a comparison of expressions) now name the given expression and say why a mask may not read it: it may hold a variable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PBmqeqBrqpQC6MVoSrguHK * docs: the changelog line links #809 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PBmqeqBrqpQC6MVoSrguHK * chore: release 0.3.0 The Upcoming section becomes 0.3.0, grouped into composition, language, typesetting and advice, and documentation. The notes name the two breaks against 0.2.0 (#788, #751). The #742 line is left out: #763 replaced `empty: true` before any release carried it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PBmqeqBrqpQC6MVoSrguHK * docs: the 0.3.0 notes say that nothing a 0.2.0 file or call does stops working #788 and #751 change only documentation and a docstring. mathspec neither attaches data nor computes duals, so neither breaks a file or an import. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PBmqeqBrqpQC6MVoSrguHK * chore: the release is 0.2.1, since nothing in it breaks a file or an import Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PBmqeqBrqpQC6MVoSrguHK * chore: a release with no break raises the patch version, features included Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PBmqeqBrqpQC6MVoSrguHK * docs: the 0.2.1 notes say that every fragment typesets and gets advice on its own Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PBmqeqBrqpQC6MVoSrguHK --------- Co-authored-by: Claude <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.
Note
The following content was generated by AI.
What this changes
dual(c)is now the rate at which the optimal objective rises with the right side ofc, for every comparator and under either sense. The old wording left an equality without a sign and disagreed with specsolve on<=. fluxopt/specsolve#1774 pins every specsolve sink to this definition.The ambiguity, the evidence, what moves
Before
named.md: "dual(c)is the rate at which the optimal objective improves ascis relaxed in the direction its comparator points."==row points in no direction, so its dual had no sign.<=row underminimizecame out non-negative. specsolve returns the opposite sign, and specsolve's ownexamples/benders/run.pybuilds its cut slope on the returned sign.After
named.mdand theDualdocstring: readlhs <= rhsaslhs <= rhs + d; the dual is the rate indatd = 0. The same for>=and==, underminimizeandmaximize. Which side a term is written on decides the sign:p <= capand-p >= -capare one row with opposite duals. A table gives the sign of a binding row of each comparator underminimize.Evidence
specsolve
mainate78c1a3, HiGHS, Gurobi and Xpress. One row, "p is at least 2 + b", written six ways, under both senses; the dual against a finite difference inb(step1e-3), times howbmoves the right side:binrhs − lhsp >= 2 + b2 + b <= p-p <= -2 - bp - b >= 2p == 2 + b2 + b == p36 of 36 cases match (6 rows × 2 senses × 3 sinks). The test is in fluxopt/specsolve#1774. On specsolve's Benders subproblem the same reading gives the cut slope that finite differences give (67.5 for
cap[wind]); the old reading gives 112.5.What does not change
examples/operators/dual.yamluses a>=row underminimize, where the two readings agree.Gates
pixi run lint: pass.pixi run test: 1684 passed.docs-buildandcompile-texwere not run in the session (network). CI runs both.Relation to the open stack
#749 (
spec.sensitivity) carries the samenamed.mdhunk, because its formula depends on this definition. Once this merges, that hunk is already onmain.🤖 Generated with Claude Code
https://claude.ai/code/session_01CvcU9JRnsinc9e9syvVcHS