Conversation
… curve prints as the curve it states `Spec.expand()` is public and takes 'piecewise', 'sos', or nothing for both. `sos:` is the second formulation: it states binaries and the rows that link them, `method: adjacency` is `method: sos2` written out, and `big_m:` is renamed `bound:` — the coefficient those rows link a member by, rather than the tighter of it and the member's own upper bound. A set is refused at load unless its members start at or above zero and one finite coefficient links them, so nothing about a set waits for data. The typesetter prints the model it was handed: a `piecewise:` block is one line — the links on the locus through its breakpoints — and `typeset(spec)` no longer expands. `_ExpandedSpec` is gone; `resolved` and the record of what a curve derived live on `Spec`, and `to_yaml` refuses on an expansion whose curves derived parameters. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QGvzXkPDrzi6f1PNmvebhc
…ne symbol table spells both readings A symbol table entry may name what a `piecewise:` or `sos:` block emits, so the table that renames `<block>_lam` to lambda renders the file it came from too — the expansion is consulted only where an entry needs it. The three typeset verbs take `--expand`, because a shell cannot compose `spec.expand()` the way a caller does. The notation page shows both readings of every formulation: the curve or the set as the file states it, and the rows it is written out as, per `method:`. A linking row leaves out a coefficient of 1, which is the common one — a weight and a binary are both bounded by 1 — so `lam <= seg + shift(seg, …)` reads as the literature writes it rather than carrying a factor that multiplies nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QGvzXkPDrzi6f1PNmvebhc
Documentation build overview
18 files changed ·
|
…iting it out `lp` and `convex` are exact only for a curve of the right shape, which the program carries as a check for the consumer holding the data. Nothing said that `expand()` keeps it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QGvzXkPDrzi6f1PNmvebhc
…r carried by the data The expansion writes a second linking row, `x >= lower * admitted`, so an unpicked member is held at zero from below as well as above. A row multiplies by its coefficient rather than reading it, so a parameter-valued bound needs no knowledge of its value and the sign of the lower bound stops mattering. The load rule is symmetric and shorter for it: each side of a member carries a coefficient, or the set is refused. A `lower` of 0 writes no row, because the variable's own bound already states it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QGvzXkPDrzi6f1PNmvebhc
FBumann
marked this pull request as draft
September 20, 2026 19:16
This was referenced Sep 20, 2026
Closed
Contributor
Author
|
Superseeded by #602 |
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.
Closes #579.
Spec.expand()writes a formulation out on request,sos:is the second formulation, and the typesetter prints the block it was handed —spec.expand(), or--expand, prints the rows that block states.Important
Blocked, and a draft until it is not. A curve's data contract is still a side channel:
Program.piecewise[…].checks, which a consumer reads and words withcheck_message. It belongs in what the formulation states —assumptions:the expansion emits — so a written-out model carries its conditions as language, prints them in the document, and no consumer keeps a second mechanism for them. That needs the two PRs below, and then the work lands here before this merges.wherecompares arithmetic and parameter pairs. On this basewhere: "p_min <= p_max"is refused: "compares two parameters, which is not in the language", so no condition on breakpoints can be written at all.assumptions:,Program.assumptions,assumption_message, and the Assumptions section of the typeset document.piecewise:block's conditions as assumptions —<block>increasing, curvature, breakpoints, points — under the same rule the rest of this PR keeps: what a formulation emits, it derives.Check,check_messageandPiecewiseDeclaration.checksretire, or the PR says which one stays and why.Contiguousis the one that does not map today: "the marked breakpoints are one run" needs a predicate that counts run starts. Either the grammar grows one, or that check stays and this PR says so.On #471 as a dependency of what is already here: there is none. A set's two bound facts are decided at load from the declared
bounds:, and a bound the data carries is a coefficient a row multiplies by rather than something the language has to know.assumptionsappears nowhere insrc/on this base, so the branch isorigin/main— and the block above is about the curve's conditions, not about anything in the diff.What this changes
spec.expand(*kinds)—'piecewise','sos', or nothing for both, in that order whatever order they are asked in. It returns a plainSpec;_ExpandedSpecis deleted, andresolvedplus the record of what a curve derived live onSpecitself.to_yaml()refuses on an expansion that derived parameters, and namestypesetas the reader's route.sos:is a formulation — it states<set>_seg,<set>_pick(<= 1), and two linking rows that hold an unpicked member at zero from both sides:<set>_adjacency(order 2) or<set>_nonzero(order 1), each with a_belowsibling. Every coefficient is read off the member's ownbounds:, so a negative bound and one the data carries are both admitted;bound:replaces the one above where the file declares it, never the tighter of the two, which is the divergence a sink without a construct refuses the model and namesspec.expand(), rather than rewriting it behind the author fluxopt/specsolve#1702 records. A coefficient that states nothing — a1above, a0below — is left out.method: adjacencyismethod: sos2written out, so the binaries are spelled once, andbig_m:is renamedbound:.piecewise:block is one line: the links on the locus through its breakpoints,convformethod: convex, the bounded link as a function of the pinned one,points:narrowing the breakpoints, anactivity:as a factor.typesetno longer expands.<block>_lamto λ renders the file it came from too. The three typeset verbs take--expand, because a shell cannot composespec.expand(). The notation page shows every formulation twice: as the file states it, and as the rows it states.Why
An expansion that happens behind the author is a model they cannot read. Stating it on request makes the two readings one call apart — for review, for teaching, and for the engine that has no concept of a set, which can now refuse the model and name the rewrite instead of performing it (fluxopt/specsolve#1702). The blocked work finishes the same thought: a rewrite that is exact only under a condition should say that condition in the language, beside the rows it writes.
What breaks
big_m:→bound:. The closed schema's own error names the valid keys.sos:block is refused at load unless each side of its member carries a coefficient:bounds.lowerand eitherbounds.upper, the set'sbound:, ordomain: binary. Each may be a parameter. A model whose member is unbounded on a side stops loading, because no row can pull an unpicked member back to zero from there.piecewise:block prints as a curve rather than as its expansion. Every consumer of the typeset output sees different math for the same file.cost_curve_lam— is refused at load, because the load pass now resolves the file's own declarations as well as the expansion's.method: adjacencycurve emits issum(seg, over=bp) <= 1rather than== activity, and its second, ungated row is gone. The projection onto the author's own variables is unchanged — with the gate at 0 the convexity row pins every weight to 0 and the segment binaries reach nothing — so this is a looser integer formulation, not a different feasible set.Program.soscarriesboundrather thanbig_m, andResolvedcarries apiecewisefield: each block's link expressions, typed, which is what lets the walk print a curve without expanding it.Verified
Run in a
uvvenv (Python 3.12), becausepixiis not installable in this environment:pytest -n 4: 1403 passed, none skipped —coverageis installed here, so the walk's line census runs, and every arm of the new curve print is reached bytests/typesetting/golden/model.yaml.ruff check,ruff format --check,pyrefly check,prettier --checkon every.md/.yamlthis branch touches: clean.python -m tools.render_tex: all 30 models render to standalone LaTeX.docs/reference/notation.md, the gallery pages,README.mdanddocs/index.md.Increasing,CurvedandAtLeastTwochecks are identical onto_program(spec),to_program(spec.expand())andto_program(spec.expand('sos')), pinned bytest_what_a_curve_assumes_of_its_numbers_rides_on_the_expansion_too.Not run:
docs-build --strict— mkdocstrings fetcheshttps://docs.python.org/3/objects.invand this sandbox's proxy answers 403;compile-tex(notectonic);reuse,typos,taplo,zizmor.Mutation table
Each guard deleted in turn, the suite run, the file restored from a copy and the tree checked clean.
test_a_rule_decided_without_data[sos-over-a-member-with-no-floor]test_a_rule_decided_without_data[sos-over-a-member-with-no-coefficient]test_an_unpicked_member_is_held_at_zero_from_the_sides_its_bounds_state(the negative and the parameter case)test_an_unpicked_member_is_held_at_zero_from_the_sides_its_bounds_state[a-member-that-starts-at-zero-needs-one-row],test_docs[notation]test_a_coefficient_of_one_is_left_out_of_the_row_rather_than_printed,test_docs[notation]test_a_rule_decided_without_data[sos-whose-expansion-collides-with-a-declaration]to_yamlrefusing an expansion that derived parameterstest_an_expansion_that_derived_parameters_prints_rather_than_round_tripstest_a_kind_this_language_does_not_have_is_refused_naming_both(both cases)expandnot storing a model that expands to itselfTestTheFrontDoor::test_to_dict_reproduces_the_model,test_to_yaml_reproduces_the_modeltest_one_table_spells_the_blocks_a_file_states_and_the_rows_they_state,test_docs[notation]test_lowering_refuses_a_model_that_still_owes_rows_to_a_curveadded as the probe, and it fails with the guard deletedCoverage moved, defaults departed from, and what was left out
test_the_file_is_not_an_expansion_and_the_expansion_isandtest_an_expansion_will_not_be_built_around_a_curveasserted the deleted type; their claims are nowtest_the_file_keeps_its_curve_and_the_expansion_has_noneand the probe in the table above.test_a_name_declared_as_none_of_the_three_is_refusedbecame..._of_the_four_..., a curve being the fourth kindtypeset_declarationprints.sos-big-m-*validation cases aresos-bound-*. The two cases that refused a negative and a parameter-valuedlowerare gone with the rule: those models load now, and what they emit is asserted intest_sos.py's parametrized rows.sos-over-a-member-with-no-floorrefuses the case that remains.test_boundedness'scarried-by-a-setcase had to become loadable: it declaresbound:withbounds: {lower: 0}and noupper, which is the one shape that leaves a member of a set unbounded above, and is what that case needs.points:mask, and the hull — so the operator census and the line census both reach the new print. The piecewise sidecar symbol tables stay, and name the weights again.--expandanyway, because a shell hands over a path and cannot composespec.expand().typeset_declarationaccepts apiecewise:block's name, which the issue does not mention: a curve now prints as one line, so asking for that line by name is the same surface the other three kinds have. And the issue refuses a parameter-valuedlowerand defers it to feat(language): a model declares what it assumes of its data, and the typeset math prints it #471 — the second linking row makes it exact with nothing deferred, so it is admitted here instead.assumptions:, once feat(language): a where may compare arithmetic over parameters #469 and feat(language): a model declares what it assumes of its data, and the typeset math prints it #471 are in.spec.expand(), rather than rewriting it behind the author fluxopt/specsolve#1702) is the next change and waits on a math-spec release carryingSpec.expandandbound:.expandtakes no per-kind options,typesettakes noexpand=keyword, and no inexact rewrite is admitted.🤖 Generated with Claude Code
https://claude.ai/code/session_01QGvzXkPDrzi6f1PNmvebhc