Skip to content

chore(engine): the engine calls each additive part of an expression a linear, quadratic or constant term rather than a piece - #2061

Merged
FBumann merged 66 commits into
mainfrom
claude/determined-johnson-irmuuz
Oct 31, 2026
Merged

FBumann merged 66 commits into
mainfrom
claude/determined-johnson-irmuuz

Conversation

@FBumann

@FBumann FBumann commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Prompt: Do a pr onto specsolve main now, renaming it
And adjust other related names too.
So it's more intuitive overall

Note

The following content was generated by AI.

Piece is now Term, in _engine/terms.py. A term is linear, quadratic or constant. This removes the words "piece", "term piece", "const piece" and "constant part". The compiler's quadratic flag is now admits_quadratic. Behaviour does not change. The rename also covers the engine code that #2051 added.

What was renamed
before after
_engine/pieces.py _engine/terms.py
Piece Term
kinds 'term', 'quad', 'const' 'linear', 'quadratic', 'constant'
CompiledExpression.terms, .consts, .quads .linear, .constant, .quadratic
Compiler.expression(..., quadratic=) Compiler.expression(..., admits_quadratic=)
map_pieces map_terms
refuse_a_piece_without_the_dims refuse_a_term_without_the_dims
join_mul, join_quad, join_pow times_constant, times_variable, to_the_power
spread_piece (from #2051) spread_term
_parameter_piece, _variable_piece, _solved_piece, _dual_piece, _sum_piece, _group_piece, _at_piece, _remap_piece the same names ending in _term
translate_piece, window_piece translate_term, window_term
  • The flag was renamed after a follow-up question about the names. It is a permission, not a kind of term: it says whether the position may hold a product of two variables. With its old name, a local quadratic list shadowed it. Now the locals in product, quotient and _build_constraint use the plain names linear, quadratic and constant, like the fields of CompiledExpression. Its docstring no longer says that a constraint row cannot hold a product, which was false for a quadratic constraint.
  • The flag stays, because its assert checks the compiler against mathspec's is_quadratic, and declares_quadratic also decides which constraints the solver capability check sees.
  • Lists of frames that were waiting to be concatenated, which were also called pieces, are now frames (and aligned in readback).
  • The docstrings, the architecture page (node table, module table and glossary), verification.md, the test names and the test prose use the new words. grep -i piece src/specsolve/_engine finds only piecewise.
  • One user-visible string changed. The a constant summed beside a term is accepted by check and the linopy lane, refused by the engine #1137 refusal now says "a constant term of the expression" instead of "a constant part". The tests match on specsolve cannot build and Declare the parameter over, and both phrases are unchanged.
Verification

The head c8dc90b holds main with #2051 merged in. Its tree is the same as the tree of 20db613, where these were run with Xpress 10 installed:

  • ruff check . and ruff format --check . pass.
  • pyrefly check reports 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 check was not run, because pixi is not installed here. Its parts were run directly.
Not done
  • tests/linopy_lane and tests/fuzz keep the words terms and const, because there they are linopy's own API.
  • Unrelated uses of "piece" stay. In _strategy.py and _archive.py, "pieces" means slices of a table. piecewise and the GenX curve's "piece" are language words.
  • The local name p for a term, the cval and coeff columns, constant_scalar and the __const {i}__ alias are unchanged. Renaming them would add churn and remove no concept.
  • a constant summed beside a term is accepted by check and the linopy lane, refused by the engine #1137 is a separate change: a constant term beside a linear one, under a sum along a dimension that the constant does not carry. Mathspec's per-node dims (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.
  • Departure from AGENTS.md: a pass that is only a rename normally gets sent back. This one was asked for, and it removes a concept, because "piece" and "term" were two words for one thing.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WjaoQERHiPueVjahe7oKSG

Summary by CodeRabbit

  • Refactor
    • Updated expression terminology to distinguish linear, quadratic, and constant terms.
    • Reorganized how expressions are represented and processed across compilation, evaluation, and assembly. Existing calculation behavior, including handling of missing values and quadratic expressions, is preserved.
  • Documentation
    • Updated architecture and verification guidance to use consistent terminology for expression components.

claude added 30 commits October 16, 2026 09:06
…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
… 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
…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
@read-the-docs-community

read-the-docs-community Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

@codspeed

codspeed Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Merging this PR will degrade performance by 67.75%

❌ 1 regressed benchmark
✅ 103 untouched benchmarks
⏩ 307 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
❌ test_read[commitment-s-specsolve-parquet] 220.7 KB 684.3 KB -67.75%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing claude/determined-johnson-irmuuz (c8dc90b) with main (582e5c1)2

Open in CodSpeed

Footnotes

  1. 307 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

  2. No successful run was found on main (603606c) during the generation of this report, so 582e5c1 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

claude added 13 commits October 19, 2026 18:00
…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
…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
…-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
@coderabbitai

coderabbitai Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 86892225-26e3-4a8a-8c97-7e8606f22d18
Commits

Reviewing files that changed from the base of the PR and between 678a2fd and c8dc90b.

Files selected for processing: 7
  • src/specsolve/_engine/assembly.py
  • 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
  • tests/linopy_lane/coverage.py
Files skipped from review as they are similar to previous changes: 1
  • tests/linopy_lane/coverage.py

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review.

Walkthrough

The engine replaces Piece with Term and stores compiled expressions in separate linear, quadratic, and constant collections. Compiler operations, assembly, downstream consumers, documentation, and tests use the updated terminology and fields.

Changes: Compiled expression term model

Layer / File(s) Summary
Term model and compiled expression contract
src/specsolve/_engine/terms.py, docs/development/architecture.md, docs/development/verification.md, tests/test_architecture.py, tests/test_docs_site.py
The engine defines Term with linear, quadratic, and constant kinds. CompiledExpression stores these components in separate fields. Documentation and fan-in imports use the term terminology.
Expression compilation and term operations
src/specsolve/_engine/compiler.py, src/specsolve/_engine/shifts.py, tests/test_compiler.py, tests/test_absence.py, tests/test_cases.py, tests/test_grouped_sum.py, tests/test_expression_sweep.py
Compiler and shift helpers now use Term values and the separated expression fields. The updated tests inspect the revised fields and retain their described behavior checks.
Assembly and downstream term consumers
src/specsolve/_engine/assembly.py, src/specsolve/_engine/coverage.py, src/specsolve/_engine/labels.py, src/specsolve/_engine/predicates.py, src/specsolve/_engine/readback.py, src/specsolve/_engine/scope.py, tests/linopy_lane/coverage.py, tests/test_expression_at_a_point.py, tests/test_expression_reader.py, tests/test_quadratic_objective.py, tests/test_relational.py
Constraint and objective assembly, coverage checks, predicates, readback, scope, and labels now use the updated term representation. Related tests and descriptions use the revised fields and terminology.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Refactor

Merge Risk: Minimal · up to c8dc9

No actionable merge-blocking issue was identified in the term-model migration; the change is ready for normal merge checks.

Security architecture risk: Low · up to c8dc9

The checked paths preserve validation, expression handling, and failure containment. No new security exposure was identified. Remaining risk concerns compatibility with code outside this repository that uses the renamed private interfaces.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The demonstrated propagation scope is expression compilation and its in-process consumers. The checked changes do not expand the public expression route or solver-handoff authority. Attacker provenance, tenant exposure, and external private-interface consumers remain outside the established evidence.

Trust Boundaries and Controls

  • observed — The checked data-validation controls remain in place: assembly checks null constants and coefficients before aggregation can conceal missing values, while readback retains coverage and divisor checks. No bypass of these controls was identified in the migrated consumers.

Resilience and Maintainability Implications

  • observed — The normal build entrypoint clears the published model, creates fresh construction state, and publishes only after Assembly.run succeeds. Partial assembly mutations therefore do not publish a partial model through that path. Direct reuse and unsynchronized concurrent access remain preexisting limitations, not changes introduced by this PR.
Pre-merge checks: Passed 5
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and accurately summarizes the main change: replacing engine “piece” terminology with linear, quadratic, and constant term terminology.
Docstring Coverage Passed Docstring coverage is 96.67% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentati…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 96.67% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 60 functions across 21 files.

Finishing Touches
Generate docstrings
  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

…-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
@FBumann FBumann mentioned this pull request Oct 31, 2026
@FBumann
FBumann merged commit 4e00952 into main Oct 31, 2026
21 checks passed
@FBumann
FBumann deleted the claude/determined-johnson-irmuuz branch October 31, 2026 22:50
FBumann pushed a commit that referenced this pull request Nov 11, 2026
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants