Conversation
3e21e13 to
cb8dc0c
Compare
… and every curve is written out before a model becomes one Resolved is gone. Lowering builds the Program as a model loads and holds it on the Spec; to_program(spec) returns that object, and returns the expansion's for a model with a curve, so a program holds the rows a curve states and never the curve. Named joins program.Expression, so a use of an expressions: entry stands where it is read and the typesetter prints from the program beside the file. ExpressionDeclaration carries its dims, and piecewise.curve reads a block's typed links and frame off the model for the expansion and the walk. PiecewiseDeclaration, Program.piecewise, Spec.resolved, lowering.inline and the refusal of a model still carrying a curve are removed. The typesetter's output is unchanged: the golden files and the generated pages regenerate byte for byte. Docs sentences, after (before): reading.md n 75 avg 16.1 median 15 over25 10 (72, 16.4, 14, 13); piecewise.md n 70 avg 17.6 median 15 over25 13 (69, 17.8, 15, 14). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TrvhjFQCQJ6ATBQkfhcoMi
…s included, and the typesetter reads it alone
to_program lowers the model as it arrived: a piecewise: block still in it
is a curve on the program, typed, with its links, signs, method, gate, mask
and frame, and the program of spec.expand('piecewise') carries the rows
instead. Every declaration carries its description, and the program the
file's. The typesetter takes a Program and reads nothing else; the walk no
longer re-resolves a curve's links at print time, and piecewise.curve is
gone. relations_of moves from Spec to Program. advice() writes curves out
itself and refuses a program still carrying one.
The typeset output is unchanged: the golden files and the generated pages
regenerate byte for byte.
Docs sentences, after (before): reading.md n 75 avg 16.8 median 15
over25 12 (75, 16.1, 15, 10); piecewise.md n 70 avg 17.6 median 15
over25 14 (70, 17.6, 15, 13).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TrvhjFQCQJ6ATBQkfhcoMi
… lowering, before any expression is read The nine after-validators on Spec that read one declaration against the others — name collisions, frames over declared dimensions, relation targets, bound names, set shapes and bounds, curve references and the names an expansion would collide with — are functions in validation.py, collected by reference_errors and run first by lowering.lower. Spec keeps the shape rules pydantic decides per block, and no longer imports sos or the operator table; side_columns is the public name of the relation helper the rules share. Every message is the same string, and every refusal the same class. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TrvhjFQCQJ6ATBQkfhcoMi
… is advised as its rows are The guard landed in the previous commit without the test that fails without it. With the guard deleted, the new test fails: a program with a block is advised on the file's own rows as if the curve stated none. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TrvhjFQCQJ6ATBQkfhcoMi
…t-faraday-coc7mj-3
… the program owns the vocabulary the file declares in Loading no longer expands every curve to validate the expansion: the rows a curve states are held to the language when expand() writes them out, since an expansion is a model like any other. A model with a curve is validated once at load rather than twice. The dtype, domain, absence, sense, set-order and method vocabulary moves from model.py into program.py, which model.py imports. program.py no longer imports model.py, so the type-only import of Program on Spec and its noqa go, and sos.py imports Spec plainly instead of inside a function. Load cost, before (after): examples/piecewise.yaml validated 2 (1) times and parsed 9 (4) expressions; examples/sos.yaml the same. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TrvhjFQCQJ6ATBQkfhcoMi
…he one door to both states is to_spec to_program is gone. Spec.program is the model typed, section for section, built once as the model loads; spec.expand(...).program is its rows. A consumer building rows reads the sections it takes and refuses a curve or a set it finds, the way it already had to for a set; advice() does so for a Program handed to it, and writes curves out itself for a file or a Spec. typeset and typeset_declaration take a Program as before. Docs sentences, after (before): reading.md n 75 avg 17.1 median 15 over25 14 (76, 16.8, 15, 12); piecewise.md n 70 avg 17.7 median 15 over25 13 (70, 17.6, 15, 14); what-counts-as-public-api.md n 18 avg 16.0 median 14 over25 3 (unchanged count); limits.md n 55 avg 18.3 median 19 over25 14 (unchanged count). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TrvhjFQCQJ6ATBQkfhcoMi
…the model loads The private attribute lowering filled and the property asserting over it are one cached_property computing lower(self); the after-validator forces it, so a Spec in hand has still passed the whole language. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TrvhjFQCQJ6ATBQkfhcoMi
cb8dc0c to
9e687c6
Compare
…ules beside the namespace _Resolver, one class over both grammars, is ExpressionResolver in _expression_resolver.py and WhereResolver in _where_resolver.py, the where walk building a side that is an expression through the expression walk. resolution.py keeps the Namespace and the doors lowering calls. The three methods the where walk reads from the expression walk are public on it; names_in and the literal-number helper move to the parser module beside the other helpers over parsed nodes. Every message is the same string. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TrvhjFQCQJ6ATBQkfhcoMi
…he program carries, and nothing else The Curve alias, a tacit protocol between the pydantic block and the program declaration, is gone: assumptions_of, Emitted.of and the curvature rule take a PiecewiseDeclaration. The expansion keeps the block for the link text its rows repeat and takes the declaration for the frame and the names it writes. The two emitted-name collision rules read the program rather than the file, so they run once the declarations exist; every message is the same string. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TrvhjFQCQJ6ATBQkfhcoMi
…s use off the program, and the walk keeps no record The glossary and the three kinds of note move from walk.py to legend.py. What they explain is read off the program before anything prints, by notice(), so the walk no longer fills a Noticed record as it prints and a subscript no longer mutates the walk through its context. Walk.line refuses a name declared as none of the five kinds or as two, so typeset_declaration is one call. Symbols is a frozen record built by symbols_for. The typeset output is unchanged: the golden files match byte for byte. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TrvhjFQCQJ6ATBQkfhcoMi
The unboundedness pass read a set as it is, every variable it restricts being named by a row, and refused a curve. A curve states its rows the same way: each link names the variables a link row would, so the pass reads the links and the answer is the expansion's with nothing expanded. The refusal goes, and check loses --expand, a flag that would change no answer. Guard: with the line reading the links deleted, three tests fail: the advice test over the file, the Spec and the Program; the boundedness case carried-by-a-curve; and the existing test that a curve holds its variables, which now runs on the block. Docs sentences, after (before): check.md n 20 avg 12.8 median 14 over25 1 (21, 13.3, 14, 2); what-counts-as-public-api.md n 20 avg 18.7 median 15 over25 4 (20, 18.1, 15, 4). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TrvhjFQCQJ6ATBQkfhcoMi
|
@FBumann I am seeing this PR and its |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GpnFcQDuvt2bExGngb6MYk
…eo-mu42ir main landed the stack this branch was built on as one squash commit per PR. The squash of #661 has the same tree as 279997a, the stack head this branch contains, so this merge records it and changes no file. The merge of main that follows then brings only what landed after it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GpnFcQDuvt2bExGngb6MYk
After the head of #661 and the recorded squash, main brought #666, #668 and the release. This branch rewrote piecewise.py around declaration_of and named links, so it keeps its own file and takes #666's four changes to it: the module docstring's first sentence, Emitted.written, the leaves_ungated rule and its use in the gate rows. PiecewiseDeclaration keeps this branch's attributes and gains main's description line. The reading page keeps this branch's link row names and main's spec.expand() == spec.expand(). main dropped the duplicate boundedness test, and it stays dropped. Tests #661 and #666 added in the old block syntax now write along:, dims: and links by name: carried-by-a-curve, the curve in the resolve-once test, which also resolves the curve's where once, and the symbol-table test, whose link rows are named after their links. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GpnFcQDuvt2bExGngb6MYk
…d as the model loads (#685) A walked link is read as at() reads its relation, so into must land on the key, the read must be single-valued, and every joined key column must be a dim of dims:. An empty over or into is refused. No two blocks may write the same name. A where over a dim the walk only joins on reads as written. The missing-breakpoint advice and the where-over-a-walked-dim refusal name a rewrite the block can take. Claude-Session: https://claude.ai/code/session_01WasiCKk1SebD7S1CsAFQog Co-authored-by: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q8Y8AoPsJCyMsURHvgzm7h
main renamed the package to mathspec and model.py to spec.py, renamed the test fixture override to varied, moved docstring links to [`name`][], and added canonical.py. The piecewise design keeps this branch's side; each conflict takes main's names, links and wording around it. - canonical: a block's links are a mapping, so the list-form expression is normalised per named link and a walk's mapping is left as it is. - test_canonical: the reversed-links case reverses the mapping. - curve-by-hand.md stays deleted; the curve pages sit beside the component library in the examples index and nav. - "model" reads "spec" where the added prose means the file. - CHANGELOG: the PR's line under Upcoming version. Schema, golden output and generated pages regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015VUkQfE5dT7mEGoTQXSxN3
Documentation build overview
17 files changed ·
|
Note The following content was generated by AI. Feedback from porting fluxopt: a walked Each ask, checked against the code1. A unit-commitment converter's on/off binary is activity: {variable: running, by: pw_status_of, over: status_entity, into: converter}
2. A block Reproduced on this head: the block loads without 3. A curve per flow whose cost link adds The shape is Generated by Claude Code |
Takes #782, which lets a where string name several columns in at's over= and into=. CHANGELOG keeps both lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015VUkQfE5dT7mEGoTQXSxN3
fluxopt's investment cost curve reads each effect of a sized flow off its curve, so its mask reaches the cost row as at(…, into=[flow, effect]). Without #782 the load fails on the assertion that what a method assumes is stated in the language. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015VUkQfE5dT7mEGoTQXSxN3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015VUkQfE5dT7mEGoTQXSxN3
Takes #788. CHANGELOG keeps both lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015VUkQfE5dT7mEGoTQXSxN3
Takes #788 through #630. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015VUkQfE5dT7mEGoTQXSxN3
Note
The following content was generated by AI.
Based on
main. #661's stack merged, andmainis merged in up to #782.What this changes
Every block states
dims:. Links are keyed by name, and a link finer than the curve always walks a relation. The block'swhere:says which curves exist and how far each runs, and reaches walked links through their relation, so only converters with a curve get one. This PR carries #569, now closed in its favour, withmain'spoints:reverted in favour ofwhere:.The expansion writes this row:
Breaking for the file language
piecewise:blocks sayalong:andwhere:;main'spoints:(a bool parameter naming how far each curve runs) is gone, sincewhere:says it.dims:is required on a block, andlinks:is a mapping: the emitted row is<block>_<link>.mainrefused it.The
sos: over → alongrename landed on its own in #646 and is no longer part of this diff.What the program carries
A program mirrors the spec, so a
piecewise:block still in the spec is a typed curve underprogram.piecewise. Here that curve isPiecewiseDeclaration(along, links, method, frame, where, activity), whereframeisdims:as written andwherethe typed mask, withraggedsaying whether it readsalong. EachLinkcarries itsname, its typedexpression, itsvalues, itssign, the walk (by,over,into) and two facts lowering decides:dims, the row the link builds, andreads, whether that row reads the where through the relation. The expansion and the typesetter read those and compute nothing of their own.validation.reference_errors(_piecewise_references,_piecewise_link_shape,_piecewise_walk_shape)along,dims:, each values parameter, a walk's relation and columns, and the gate exist as the block needs themlowering.lower→piecewise.declaration_ofalongonly; the where testsdims:andalongonly; a walked row reads the where through its relation or not at allvalidation.emitted_name_errorspiecewise.assumptions_of(name, curve, where)What #638 and #643 decided at load, re-ported onto this design
floatorint_piecewise_link_shapepiecewise.lp_domain_refusal, on the named linkalong:is one line, not one per link_piecewise_referenceswhere:outside the frame is refused on the block's line, never on an emitted rowdeclaration_of, as the spec loadsPiecewiseExpansionErrorno longer existsSchemaErroror aDimensionErrorlike any other declarationemitted_name_errors, shared withsos:Each change, and the finding it answers
dims:required. A missingdims:is refused, and the message names the rewrite. The inferred frame,_frame,_widenand the second rewrite message godims:dims:. A gate over fewer dims switches every curve it coversactivity:widened a declareddims: [snapshot]to[snapshot, generator]without a messagelinks:is a mapping. The emitted row is<block>_<key>, not_link<i>. A key that is not a name is refused, and so is a key whose row name the block already writes (convexity,lam,adjacency_below, …). Refusals namelink 'power', notlink 0into:split is removed. A link row finer thandims:is a walk withby,overandintotogether;into:alone is refusedintohad two meanings; inatandsumit only names produced columns. No example or known model used the splitovermust reach a dim ofdims:,intomust not reach a dim ofdims:oralong, and a column may not repeatdims:was refused only by the emitted row, namingConstraint 'coupling_link0'.into: [flow, flow]loadedwhere:reaches walked links. A walked row reads the mask through its relation,at(<where>, by, over, into), when the mask carries every dim the walk consumes or joins on. A ragged mask goes through asat(count(<where>, over=bp) > 0, …). A mask carrying none of them (e.g.snapshotonly) reads as written. A mask carrying some and not the rest is refused, naming the missing dims. A walk into several columns reads it asat(…, into=[flow, effect]), which #782 lets a where string parsewhere:beside a walk. So a model with some converters on a curve built weights and binaries for every converter<block>_<link>_completeasks for its values only at the rows it reads the curve at: under the mask read through the relation, or where the relation has a row.<block>_completekeeps the links ondims:. The per-link name is reservedbp_ratewas demanded at every flow, including flows of converters with no curvewhere:. With a frame-only mask it goes on as written, or conjoined with the position test. With a ragged mask, the frame-level conditions takecount(where, over=bp) > 0expand()→to_yaml()→ load,_completeand_breakpointswere asked at generators with no curvedims:. A walked link prints as a family,(rate_{f,t})_{f ∈ F : converter_of(f) = c}. A block with more than two links and a sign prints as(…) ∈ pwl(…) + {0} × ℝ≥0 × ℝ≤0, with three new operators:origin,nonnegative,nonpositivepinned = links[1 - bounded]assumed two links:q ≤ pwl(…)(q)droppedpandc. The coupling example printed∀ t, g, f, putting every flow on every curvemain'scanonical.py(#731) read links as a list, and indexed a walk's mapping with[0]examples/piecewise_coupling.yamlis fluxopt's mixed system. A CHP on a curve sits beside a fixed-ratio boiler, and onehas_curveis read both ways: the curve reads it, andfixed_ratioreadsNOT at(has_curve, …)reading.md, the examples, the golden model (chp_curve, andbus_curvemasked), the schema and the generated pageswhere:rowsHistory: the merges of main, the simplification, and the rebases
mainat alpha.118 plus #626 through #643 was merged in, then alpha.119. Nine files conflicted, and each tookmain's side except the piecewise design. The thirteen tests #643 added forpoints:, list links andlink 0now speakwhere:, named links andlink 'p'.The simplification then folded what the branch had added beside a
maincounterpart back into it:Curve,check()and five module-level emitters into_Block;NamesandASSUMEDintoEmitted;CurveMaskinto the three mask helpers plusis_ragged;ResolvedPiecewiseandResolved.expanded_piecewiseinto onepiecewise_wherefield;PiecewiseDeclaration.where,declaration_of(where)and the exists-mask resolution of written-out blocks are gone, since the assumptions carry the mask, so the program's curve record ismain's.After #646 merged, the branch was rebased onto
mainas one commit carrying the same tree: the 78 commits held two merges ofmainand would not replay one by one. After #654 merged, the commit was rebased again: the single-link and the four-element cases #654 added now write a mapping and meet the same messages, and the case refusing a sign among three links is dropped, since this PR admits it and the golden model'schp_curvecovers it.Onto #661's stack. That stack moved lowering into
Spec.program, removedResolved, and made the program carry a typed curve, so ten files conflicted and the piecewise design was re-ported onto its shapes rather than merged hunk by hunk. What moved: the reference rules fromSpec's validators intovalidation.reference_errors, as the stack keeps every such rule; the fit rules from_Block.__init__intodeclaration_of, which lowering calls to build the curve, so_Blockonly writes;Resolved.piecewise_whereintoPiecewiseDeclaration.where;is_raggedintoPiecewiseDeclaration.ragged;walk_readsandthrough(reads=)intoLink.reads; and the typesetter reads the program alone, as every other walk on the stack does.curve_frame, which the stack kept formain's design, is gone with the design.Up to date with
mainat #780 (a merge commit, no force-push).mainhad renamed the package tomathspecandmodel.pytospec.py, renamed the test fixtureoverridetovaried, moved docstring links from Sphinx roles to[`name`][], and called a file a spec. Fourteen files conflicted. Each kept this branch's piecewise design and tookmain's names, links and wording around it.canonical.pyand one case intest_canonical.pywere taught the link mapping.docs/howto/curve-by-hand.mdstays deleted, and the four curve pages sit besidemain's component library in the examples index and nav. The PR's line is now under## Upcoming versioninCHANGELOG.md.#782, and fluxopt's investment curve. A block
where:beside a walkinto: [flow, effect]failed the load on the assertion that what a method assumes is stated in the language: the where grammar could not parse the list the walk reads the mask through (#781). #782 fixed the grammar onmain, and a second merge brings it in, withCHANGELOG.mdthe only conflict.test_a_block_mask_reaches_a_walk_into_several_columnsloads fluxopt's investment cost curve (fluxopt/fluxopt#26), checks thewhere:on its cost row and on its breakpoint check, and loads the expansion again. With_where_parser.pyfrom before #782 it fails on that assertion.The generated files were regenerated after each rebase and merge.
Gates, and what was not run
On the head
21e31bd, in a uv venv (Python 3.12) with the pinned tool versions, since pixi cannot be installed here:pytest -q -n autoruff check,ruff format --check(0.16.1)pyrefly check(1.2.0)prettier --checkonCHANGELOG.md,typoszensical build --strict(0.0.63)d97fb41, not rerun since: one issue,pathlib.Pathunresolved, because the proxy refusesdocs.python.org's inventorycompile-texDecided, and deliberately not done
mainmis-prints, at the cost of three format operators and two walk methods.where:per link. With the block mask reaching walked rows, and one relation per role for a subset of flows, no model needs one yet.convex/lp, and walks under them: unchanged from feat(language): a piecewise curve applies to the members that have one, and ties as many expressions as the data says #569.activity:gate that walks a relation is accepted as a follow-up PR on top of this one, not added here.Why
It follows the review of #569: only the block decides the curves, a link that fans out walks a relation as
atdoes, and rows are named rather than numbered. fluxopt mixes converters on a curve with converters at a fixed ratio, one to five flows each, andexamples/piecewise_coupling.yamlnow holds that system.mainhad meanwhile said how far a curve runs withpoints:; this design says it withwhere:, and the choice went towhere:.🤖 Generated with Claude Code
https://claude.ai/code/session_01HxoXSQCyrcDs6vuRk3x6tz
https://claude.ai/code/session_015h57WkBDnpxrknuJ5zZy9F
https://claude.ai/code/session_015VUkQfE5dT7mEGoTQXSxN3