feat(spec): a spec reports how its optimum moves with a parameter, which for a fixed decision is a Benders cut - #826
Draft
FBumann wants to merge 7 commits into
Conversation
FBumann
pushed a commit
that referenced
this pull request
Oct 1, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XjTvqu5LubaVCgWeKogCqj
Documentation build overview
39 files changed ·
|
FBumann
added this pull request to stack #827
October 1, 2026 16:49
FBumann
marked this pull request as draft
October 2, 2026 07:17
FBumann
removed this pull request from stack #827
October 2, 2026 09:05
…ich for a fixed decision is a Benders cut spec.sensitivity(*names) reports <name>_sensitivity, the rise of the optimal objective per unit of a parameter, written over dual(). It carries arithmetic, sum(over=) and named expressions from a row down to the parameter, and refuses a bound, a divisor, a masked row read pointwise, and any other operator on the path. Ported from #749 onto spec.fix. A given parameter is refused with a message that names it as given. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XjTvqu5LubaVCgWeKogCqj
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XjTvqu5LubaVCgWeKogCqj
… until a parameter can be absent The test passes only when fix gives the parameter absence: undefined and sensitivity writes the rate, so removing the refusal alone fails it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XjTvqu5LubaVCgWeKogCqj
…ameter is absent where it was `fix` now gives the parameter the variable's `missing: absent` and adds no `where:` to a row, so `within` is absent outside `flag` exactly where `cap` is, and `2.0 + dual(within)` is the rate. The expected failure is a plain test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QiDTE4WvKobVxTDxUeWhc9
FBumann
force-pushed
the
claude/quirky-newton-fm9fev-sensitivity
branch
from
October 2, 2026 09:09
c22090d to
d3b7d30
Compare
FBumann
added this pull request to stack #832
October 2, 2026 09:11
…o claude/quirky-newton-fm9fev-sensitivity # Conflicts: # CHANGELOG.md
…o claude/quirky-newton-fm9fev-sensitivity
…o claude/quirky-newton-fm9fev-sensitivity # Conflicts: # CHANGELOG.md
This branch has not been deployed
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
spec.sensitivity(*names)reports<name>_sensitivity: the rise of the optimal objective per unit of a parameter, written overdual(). Of a variable thatspec.fixmade a parameter, it is the slope of a Benders cut. Stacked on #824 (itself on #810), and it supersedes #749.fixandsensitivitynow combine on a masked variable. On #810,fixgives the parameter the variable'smissing: absentand adds nowhere:to a row.spec.fix('cap').sensitivity('cap')on acapthat exists only whereflagwrites2.0 + dual(within), and outsideflagbothcapandwithinare absent, so a missing rate there is right. The strict expected failure that pinned this is now a plain test.Formula, provenance, guards, gates, what was not done
The formula
Σ_c dual(c) · ∂(rhs − lhs)/∂q, plus the objective's own∂f/∂q. It is one expression for every comparator and under either sense, because #751 (onmain) definesdual(c)as the rate at which the optimal objective rises with the right side ofc. On #824's Benders monolith:#749 checked these against finite differences on HiGHS:
cap[wind]67.5 andcap[gas]30.0, derived and measured, also withbalanceas==and undermaximize. That check was a scratch script, because mathspec has no solver dependency, and I did not run it again.What it carries, and what it refuses
sum(over=)and named expressions from the row down to the parameter, and sums back over each dimension the parameter does not carry. Onmain,over=takes several dims.sum(p * cost, over=[snapshot, generator])givessum(p, over=snapshot)forcostovergenerator.merge. A term that a fragment adds withadds_to:is read through the named sum.shift,at,sum(by=),sum_backor a case between the row and the parameter;where:that reads the parameter pointwise. Where the mask deletes the row, the dual is absent and would take the whole rate with it, although the parameter may still be read elsewhere.fixno longer writes such awhere:, so this refusal now meets only a mask the file wrote itself.Provenance
This is #749's diff on top of #748, applied to #824, and now rebased onto #824 as stacked on #810. The rebase replayed its three commits without a conflict; one commit on top turns the expected failure into a test. Changed in the first port:
mathspec.spec.Specpath.Raises:lists the two refusals it left out: nothing reads the parameter, and the entry is already declared.named.mdsays what the call reports.Guards, measured before the rebase
tests/test_sensitivity.pytest_the_rate_of_a_fixed_masked_variablefails without #824's copy ofmissing:.Gates
On
d3b7d30:pixi run lint: pass.pixi run test: 2713 passed.docs-build: not run on this head. On feat(language)!:missing:says what a missing row means, a table short of a row is refused unless the file says otherwise, and a variable'sabsence:is nowmissing:#810 and feat(spec): a decided variable becomes a supplied number in one call, so a subproblem is not a second file #824 the only warning ispathlib.Path, which needsdocs.python.org/3/objects.inv; the proxy here refuses it. This PR's docs change is one paragraph innamed.md.compile-tex: not run. tectonic cannot download its bundle here. The diff changes no typeset output.Pushed with
--force-with-lease, at your instruction, against the AGENTS.md rule.Not done
shift,at,sum(by=)and cases on the path. The adjoint of each is an operator the language has (sum(by=)is the adjoint ofat), exceptsum_back, whose adjoint would besum_forward#49'ssum_forward.named.mdlinks to it underdual().Why
A Benders loop needs the cut slope, and a modeller reads the marginal value of capacity or load. Both are this derivative. A driver that writes it by hand has to get the dual's sign right, and #749 measured the old sign giving 112.5 where the true value is 67.5.