Repository navigation
chore(engine): the engine calls each additive part of an expression a linear, quadratic or constant term rather than a piece - #2061
Conversation
…against each other, and keeps what it found as strict xfails `python -m tests.fuzz` draws a model and its data from each seed, checks it against every oracle (load, round trip, typeset, both builds, a read at one point, the solve and the written LP file, and a shuffle of the table rows), groups the findings, and `--reduce` shrinks a seed to its smallest failing case. `tests/test_fuzz.py` holds 22 reduced findings as strict xfails. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cg7FGcrnjcuYW4QnyXbCMN
… and on fresh seeds every night `pixi run fuzz` checks a thousand seeds and compares each one with `tests/fuzz/baseline.json`. It fails on a new finding, and on a held one that no longer occurs. `fuzz.yml` runs it on every pull request and on main. Each night it runs ten thousand fresh seeds through `pixi run fuzz-fresh`, which fails only on a kind of finding that `tests/fuzz/kinds.json` does not name, and uploads each kind's reproduction. The generator iterated a frozenset, so one seed drew a different model under another hash seed. It now sorts, and a test draws each seed under two hash seeds. A feasibility model is no longer solved through linopy, and a hang is reported as a hang. The strict xfails name #2048, #2049 and #1897, and five more cases from the longer runs join them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cg7FGcrnjcuYW4QnyXbCMN
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cg7FGcrnjcuYW4QnyXbCMN
… variable beside it is absent Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
…ividend does not carry the divisor's dimension Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
…le reads as a zero coefficient where it has no row, rather than being refused as a constant side Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
…nly where the case that applies has no value Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
…side a quotient in the other case Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
… when it refuses a divisor there Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
…pplies has no row Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
…f its dimensions builds rather than crashing The vacated positions a numeric `edge=` puts back into a presence were written over the operand's dimensions, while the moved presence was keyed by the narrower set the presence states, so polars refused the concat. Both `shift(shift(v, ...), ..., edge=0)` with `v` over two dimensions and a read of `shift(v / p, ..., edge=0)` reached it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
…e the shift vacates `shift(p, along=t, offset=1, edge=0)` with `p` a `missing: absent` parameter wrote its edge only for the other coordinates where `p` had some row, so a constraint lost the row the edge stands in and a read missed the value. An operand with no variable now gets its edge at every vacated coordinate; one with a variable keeps it where the variable has a row in the other dimensions, as the linopy lane builds it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
…sion it acts along `shift`, `sum_back`, `at` and `sum(by=)` over an operand such as `x + y`, where `y` does not carry the dimension the operator acts along, raised an internal error, and a shift by a parameter offset crashed polars where one term lacked the offset's dimension. A plain `sum(x + y, over=t)` built, but counted `y` at every `t`, also where a masked `x` makes the summand absent. Before a shape operator runs, every piece of its operand is now repeated along each dimension the operator reads and the piece lacks; a constant is still refused by name along the dimension the operator acts along. The repetition comes before absence propagates, so a repeated term loses the slots where a term beside it is absent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
… is refused by name `at(x + 3, by=r[h])` failed an internal check. The relational engine now refuses it as it refuses the same constant under `sum(by=)`, naming the operator and the dimension. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
…by the one helper the shape operators use Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
…ional missing value Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
…on both lanes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
… whole number its bound admits Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
… column it reads The oracle's at() left the labels it read behind as a coordinate, so a grouped sum onto that column, a where over a frame that carries it, or a sum_back over the lookup met a coordinate of the same name with other values and crashed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
…lation A bare relation has no value column, so the oracle's test for a row reduced over nothing and crashed. It now reads the relation's membership array, and #1897's repro holds on both lanes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
…sions Over two dimensions a combination the data leaves out arrives as a NaN before the reindex, so the oracle's fill never reached it: the value read as neutral in arithmetic and as absent in a where comparison. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
numpy answers nan ** 0 with 1, so the oracle read a value where the language and the relational lane read absence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
…a zero linopy drops a row of zero coefficients when it sanitizes zeros or writes an LP file, so `2 == 0 * v` solved as optimal on the oracle. The fuzzer now solves the oracle through HiGHS directly with its zeros kept, which reports the row and the infeasibility the language reads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
…ows it reaches The oracle refused or crashed on a zero divisor at a coordinate no row uses: one the where excludes, one a shift vacates, and one under a sum with no terms, where 0 / 0 in the constant marked the row absent and dropped the variable beside it. A constraint now asks a zero under a variable of the built row, and a term's zero constant stays zero over a zero divisor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
…meter with no row The absence rules refuse a quotient read where a `missing: neutral` divisor has no row, and the relational lane does. The oracle filled the gap with zero and read the quotient as absent there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
linopy multiplies an expression by a factor of one term at most, and a cases: block over a variable is an expression of two. The oracle now puts the factor with more terms first. The reduced case is quadratic, so it is marked so and reads its values at a point instead of solving. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
…he messages module, so the linopy lane imports no engine code Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
…ow, as the relational lane does Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
… rather than a missing value Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
…ows arrive, as before, since summing in value order slowed a build by up to two thirds and changed no answer Reverts ef24900 and 2fed53f. osemosys_utopia stays in MOVES as a strict xfail, now naming #1898. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
…me model to the last bit Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJp5GyXeQBB6V314mxYxiW
… linear, quadratic or constant term rather than a piece `Piece` is `Term`, in `_engine/terms.py`. Its kinds are `linear`, `quadratic` and `constant`, and a `CompiledExpression` holds them as `linear`, `constant` and `quadratic`. The helpers that named a piece name a term, and the products read as what they do: `times_constant`, `times_variable` and `to_the_power`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WjaoQERHiPueVjahe7oKSG
Documentation build overview
110 files changed ·
|
Merging this PR will degrade performance by 67.75%
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
…ratic, so the terms keep their plain names Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WjaoQERHiPueVjahe7oKSG
…the private modules main now has mathspec 0.4.0 refuses masks:, and #2055 put an underscore on every module outside types and errors. Both broke every seed after the merge; the baseline over seeds 0:1000 now holds exactly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018B3nA3mh6FgpSB3it5Peef
# Conflicts: # CHANGELOG.md # src/specsolve/_engine/attaching.py # src/specsolve/_engine/compiler.py # src/specsolve/_engine/readback.py # src/specsolve/_engine/shifts.py # tests/linopy_lane/builder.py # tests/linopy_lane/coverage.py # tests/linopy_lane/loader.py
…4.0 names ParameterReference and VariableReference In 0.4.0 program.Parameter and program.Variable are the declarations, so the isinstance checks this branch added matched nothing and raised no error. The oracle then read a sparse divisor as zero rather than refusing it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018B3nA3mh6FgpSB3it5Peef
… type check passes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018B3nA3mh6FgpSB3it5Peef
# Conflicts: # CHANGELOG.md
…e that raises keeps the others' findings - the campaign ends its pool after a hang, which kills the stuck worker, and runs the seeds still without a record in a new pool - check() catches an exception from the round-trip, typeset, read, solve and shuffle oracles as a crash finding of that oracle - --reduce -v writes the reducer's log to stderr, past the redirect that silences the checks - the nightly seed range follows the day since 1970, so it never repeats - a nightly run and a push to main no longer share a concurrency group Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018B3nA4oeSor7jjZ4vwux1G
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018B3nA4oeSor7jjZ4vwux1G
…-439bxq' into claude/specsolve-open-bugs-1y121a
…nson-irmuuz # Conflicts: # docs/development/architecture.md # src/specsolve/_engine/assembly.py # src/specsolve/_engine/compiler.py # src/specsolve/_engine/coverage.py # tests/test_compiler.py
…-bugs-1y121a # Conflicts: # CHANGELOG.md # tests/fuzz/baseline.json # tests/fuzz/check.py # tests/fuzz/kinds.json # tests/test_fuzz.py
…a' into claude/determined-johnson-irmuuz # Conflicts: # src/specsolve/_engine/compiler.py # src/specsolve/_engine/coverage.py # src/specsolve/_engine/readback.py # src/specsolve/_engine/shifts.py # src/specsolve/_engine/terms.py
…nson-irmuuz # Conflicts: # src/specsolve/_engine/compiler.py # src/specsolve/_engine/coverage.py # src/specsolve/_engine/readback.py # src/specsolve/_engine/shifts.py # src/specsolve/_engine/terms.py
…2061) The parameter dual follows the rename: Term in place of Piece, the linear, quadratic and constant kinds, and `admits_quadratic=` on Compiler.expression. The two changelog lines become one, since the threshold rates ship with the reader they extend. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018B3n9zCSFYs7CAuSt6mC2H
Note
The following content was generated by AI.
Pieceis nowTerm, in_engine/terms.py. A term islinear,quadraticorconstant. This removes the words "piece", "term piece", "const piece" and "constant part". The compiler'squadraticflag is nowadmits_quadratic. Behaviour does not change. The rename also covers the engine code that #2051 added.What was renamed
_engine/pieces.py_engine/terms.pyPieceTerm'term','quad','const''linear','quadratic','constant'CompiledExpression.terms,.consts,.quads.linear,.constant,.quadraticCompiler.expression(..., quadratic=)Compiler.expression(..., admits_quadratic=)map_piecesmap_termsrefuse_a_piece_without_the_dimsrefuse_a_term_without_the_dimsjoin_mul,join_quad,join_powtimes_constant,times_variable,to_the_powerspread_piece(from #2051)spread_term_parameter_piece,_variable_piece,_solved_piece,_dual_piece,_sum_piece,_group_piece,_at_piece,_remap_piece_termtranslate_piece,window_piecetranslate_term,window_termquadraticlist shadowed it. Now the locals inproduct,quotientand_build_constraintuse the plain nameslinear,quadraticandconstant, like the fields ofCompiledExpression. Its docstring no longer says that a constraint row cannot hold a product, which was false for a quadratic constraint.is_quadratic, anddeclares_quadraticalso decides which constraints the solver capability check sees.pieces, are nowframes(andalignedinreadback).verification.md, the test names and the test prose use the new words.grep -i piece src/specsolve/_enginefinds onlypiecewise.specsolve cannot buildandDeclare the parameter over, and both phrases are unchanged.Verification
The head
c8dc90bholds main with #2051 merged in. Its tree is the same as the tree of20db613, where these were run with Xpress 10 installed:ruff check .andruff format --check .pass.pyrefly checkreports 0 errors.pytest -n 4: 5976 passed, 458 skipped, 4 xfailed.python -m tests.fuzz --seeds 0:1000 --baseline tests/fuzz/baseline.json: every finding is one that the baseline already holds.pixi run checkwas not run, because pixi is not installed here. Its parts were run directly.Not done
tests/linopy_laneandtests/fuzzkeep the wordstermsandconst, because there they are linopy's own API._strategy.pyand_archive.py, "pieces" means slices of a table.piecewiseand the GenX curve's "piece" are language words.pfor a term, thecvalandcoeffcolumns,constant_scalarand the__const {i}__alias are unchanged. Renaming them would add churn and remove no concept.sumalong a dimension that the constant does not carry. Mathspec's per-nodedims(feat(program): each expression node states the dimensions it is read over energy-models/mathspec#899) may let the engine spread the constant term before the sum, instead of refusing it.🤖 Generated with Claude Code
https://claude.ai/code/session_01WjaoQERHiPueVjahe7oKSG
Summary by CodeRabbit