Skip to content

feat: a decided variable becomes a supplied number, so a subproblem is a call on the whole model - #748

Closed
FBumann wants to merge 11 commits into
claude/mathspec-analysis-features-zy7x4n-convexity-advicefrom
claude/mathspec-analysis-features-zy7x4n-fix
Closed

FBumann wants to merge 11 commits into
claude/mathspec-analysis-features-zy7x4n-convexity-advicefrom
claude/mathspec-analysis-features-zy7x4n-fix

Conversation

@FBumann

@FBumann FBumann commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Prompt: Start with #303 and the cut formula
Draft PRs, stacked

Note

The following content was generated by AI.

What this changes

spec.fix(*names) turns each named variable into a parameter with the same name. A masked variable keeps its meaning: each read outside a sum is proven guarded, masked at its row, or refused with the reader named. Closes #303. Stacked on #747.

Rules, evidence, gates, open questions

The rewrite

  • The variable becomes a parameter over the same dims. binary and integer become int; continuous becomes float. The description is kept.
  • Its bounds, and a binary's 0 and 1, become the assumption <name>_within_bounds, under the variable's where:.
  • A constraint that named only fixed variables becomes an assumption under its own name, with its where: and description. The loader refuses such a row, and its message names this rewrite.
  • A set or a curve that names the variable refuses the fix. spec.expand().fix(...) works.
  • All names are fixed in one rewrite, which is validated once. fix('a').fix('b') equals fix('a', 'b').

The masked variable (what #249 found, re-made on current main)

A variable with where: and absence: undefined does not exist outside its mask, and a row reading it there is not built. A parameter reads 0 there, so the row stands: p <= cap * avail becomes p <= 0. #249 answered with coverage: masked, which main no longer has. This PR proves each read instead:

  • Inside sum, sum(by=) or sum_back, an absent summand and a 0 one give the same sum.
  • A read under a mask that holds every conjunct of the variable's mask (the row's where:, or a case's when) is guarded.
  • An unguarded read directly in a constraint whose frame carries the variable's dims gets the mask added to that constraint's where:.
  • A read through shift or at, or in an unguarded case, is refused, and the message names the reader.

Evidence

examples/pypsa.yaml on main at 22cdebd, fixing the seven *_nom_ext capacities and the three *_n_mod module counts:

  • No constraint gains a where:. Every pointwise read already stands under ..._extendable.
  • 34 rows that only limited capacity become assumptions: the _ext_*_nom_lower/upper, _nom_set, _p_nom_modularity, Carrier_growth_limit and GlobalConstraint_*_expansion_* families.
  • separability['snapshot'].linking_columns goes from 13 variables to the three CVaR columns, which the risk measure spans across snapshots by design.

Before #736 folded the stochastic and multi-period classes into this file, the same test fixed six variables and the snapshot axis linked no column.

specsolve's examples/benders/ monolith: fix('cap') gives an LP whose snapshot axis links no column. specsolve ships that subproblem as a second file, sub.yaml, with cap renamed to cap_hat.

Guards

Guard deleted Fails
a binary or integer becomes int 2 domain rows
masked reads are checked 4
absence: zero needs no check zero-outside-the-mask-is-what-a-missing-row-reads
a sum absorbs the absence 7
a guarded read is left alone 4
a read through a shift is refused through-a-shift
a read in an unguarded case is refused in-a-case-its-mask-does-not-guard
settled rows become assumptions 2, pypsa included
a set refuses 1
a curve refuses 1
a name given twice refuses 1

Gates

  • pixi run lint: pass.
  • pixi run test: 2714 passed, on the merge of main at 22cdebd.
  • docs-build and compile-tex were not run in the session (network). CI runs both.

Open, for review

  • A reported expression that reads the masked variable is not guarded: where it read nothing, it now reads 0. It is documented in the docstring, not refused.
  • dual(c) of a constraint that becomes an assumption stops resolving; the reload names it.
  • The mask is added as (<row where>) AND (<variable where>), text on text. It is proven only where the variable's dims are in the row's frame.

Why

#303: a hand-written fix in a driver is wrong twice and silently, and two drivers would decide it differently.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CvcU9JRnsinc9e9syvVcHS


Generated by Claude Code

…s a call on the whole model

Spec.fix(*names) turns each named variable into a parameter with the same
name. A binary or integer becomes an int. Its bounds become an assumption.
A row that named only fixed variables becomes an assumption too. A masked
variable with absence: undefined keeps its meaning: every read outside a sum
must stand under its mask, fix adds the mask to a constraint's where:, or it
refuses and names the reader.

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
…claude/mathspec-analysis-features-zy7x4n-fix

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvcU9JRnsinc9e9syvVcHS
…claude/mathspec-analysis-features-zy7x4n-fix

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvcU9JRnsinc9e9syvVcHS
…claude/mathspec-analysis-features-zy7x4n-fix

examples/pypsa.yaml now carries the multi-period and stochastic classes
(#736): Link_n_mod and Process_n_mod are capacity decisions like
Generator_n_mod, and the CVaR columns span every snapshot by design.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvcU9JRnsinc9e9syvVcHS
…claude/mathspec-analysis-features-zy7x4n-fix

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvcU9JRnsinc9e9syvVcHS
…claude/mathspec-analysis-features-zy7x4n-fix

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvcU9JRnsinc9e9syvVcHS
…claude/mathspec-analysis-features-zy7x4n-fix

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvcU9JRnsinc9e9syvVcHS
…claude/mathspec-analysis-features-zy7x4n-fix

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvcU9JRnsinc9e9syvVcHS
…claude/mathspec-analysis-features-zy7x4n-fix

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvcU9JRnsinc9e9syvVcHS
@FBumann FBumann added area: composition Assembling a model from files, and fixing a decision enhancement labels Sep 29, 2026 — with Claude
…claude/mathspec-analysis-features-zy7x4n-fix

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CvcU9JRnsinc9e9syvVcHS

FBumann commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

Prompt: "Then port 749 as a stacked pr onto it and close 748 and 749 as superseded"

Note

The following content was generated by AI.

Superseded by #824, which carries this diff onto main without the #746 → #747 base. #824 also fixes a bug: two masked variables fixed in one call kept only the second mask.


Generated by Claude Code

@FBumann FBumann closed this Oct 1, 2026
FBumann pushed a commit that referenced this pull request Oct 2, 2026
… so a subproblem is a call on the whole model

Each named variable becomes a parameter under the same name: binary and
integer become int, continuous becomes float. Its bounds become the
assumption <name>_within_bounds, and a constraint that named only fixed
variables becomes an assumption. A masked variable with absence:
undefined keeps its meaning: each read outside a sum is proven guarded,
masked at its row, or refused with the reader named.

Ported from #748 onto main, without its base (#746, #747). A given
variable 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: composition Assembling a model from files, and fixing a decision

Projects

None yet

Development

Successfully merging this pull request may close these issues.

a decided variable has no verb turning it into a supplied number, so every decomposition driver writes its own

2 participants