Repository navigation
Conversation
Add `Pipeline::fuse`, a final stage that merges consecutive compatible
instructions the way Stim's `Circuit` parser does (`H 0` / `H 1` becomes
`H 0 1`):
Source -> Parsed -> Validated -> Fused<Program>
\-> Lowered -> Fused<ExtendedProgram>
Fusion runs after every stage that can reject input, and `lower` is only
available on the unfused `Validated` state, so validation and lowering
always check one source line at a time. `finish()` without `fuse()`
still returns one instruction per line.
Instructions fuse when they have the same name, tag and arguments.
Spelling aliases (`CX`/`CNOT`) are not treated as the same. Annotations,
`REPEAT` blocks and `CORRELATED_ERROR`/`ELSE_CORRELATED_ERROR` never
fuse. The extended AST fuses by operation, so `S[T] 0` / `T 1` becomes a
single `T`.
`validate` and `lower` now record the spans of the nodes they skip, and
fusion never merges across one, so with a non-aborting sink an invalid
line still separates the instructions around it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e_extended `parse` and `parse_extended` now end with the fusion stage, so the parsed program has the same instruction structure as Stim's: `H 0` / `H 1` parses as one `H 0 1`. Expected outputs in `tests/fusion.rs` are taken from Stim 1.15. The AST print/parse property test re-parses through the unfused pipeline, since the printer emits one instruction per line. Closes QuEraComputing#291 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… errors
A fused instruction can span several source lines, and `ExecError`
reports the first. Record-control and MPP-product errors now quote the
offending operand (`pair 'rec[-5] 1'`, `X1*X1`) and, for a fused
instruction, name its line range:
invalid measurement-record control for 'CX' at line 2: rec[-5] looks
back before the start of the measurement record (1 measurement(s)
recorded so far; pair 'rec[-5] 1' in the instruction fused from
lines 2-3)
`ExecError` itself is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
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.
READ FIRST: Note from myself: opening this as draft PR for now, since there are quite a few design choices that are made based on how important/desirable several features are. Before having in-depth reviews on code, should probably briefly discuss code design choices so we don't waste time on this.
Closes #291.
Stim's parser fuses consecutive lines with the same instruction, tag and arguments (
H 0/H 1→H 0 1).parseandparse_extendednow do the same.Design
Fusion is a new final pipeline stage, after
validateandlower:Why fusion runs last. Fusing earlier would mean later checks see merged lines instead of source lines, which causes two problems:
I_ERROR[correlated_loss](0.1) 0/I_ERROR[correlated_loss](0.1) 1has an odd target count on each line, so both are invalid. Fused first, they would pass as a valid0 1.T 0/T rec[-1], the bad record target is on line 2. If the lines were fused first, the error would land on line 1.Running fusion after every check means each check sees one source line, and fusion itself can't fail. The order is enforced by the types:
lower()only exists on the unfusedValidatedstate.Why a separate stage.
validateandlowerkeep doing one thing each.finish()without.fuse()still gives one instruction per line, for tooling that needs a 1:1 mapping to the source.Which instructions fuse. The rules follow Stim's
CircuitInstruction::can_fuse:0.1and0.10match.TICKandREPEATnever fuse.CORRELATED_ERROR/ELSE_CORRELATED_ERRORnever fuse either: each line is a single joint error event, so merging two lines would change the noise channel.For everything else, a multi-target line means the same as its lines one after another, so fusion doesn't change behaviour.
Lines dropped for errors. With a sink that keeps going after errors (
Collect), an invalid line is dropped but still separates its neighbours:H 0/TIKC/H 1stays two instructions, sinceTIKCmay have been meant as a barrier. Fusion runs as a separate pass, after the dropped line is gone, sovalidateandlowerrecord the span of every node they skip. WithFailFast, whichparse/parse_extendeduse, that list is always empty.Differences from Stim
CX/CNOT,M/MZ). The AST keeps each spelling as its own variant so the printer can reproduce the source. Fusing them would mean choosing which spelling survives, which is left for a follow-up.parse_extendedfuses by operation.S[T] 0/T 1becomes oneT: the extended AST describes operations, and both lines are the same operation. Plainparsekeeps them apart, like Stim.ppvm-stimppvm_stim::validateruns on the fused program and reports an instruction's first line, which is what Stim does too. That's accurate for checks on the name and arguments, since those are the same on every line of a run. It isn't accurate for checks on individual operands. So record-control and MPP errors now quote the offending operand and name the line range, e.g.pair 'rec[-5] 1' in the instruction fused from lines 2-3. This goes in the message text rather than in new fields to avoid a breaking change toExecError. Stim reports these errors at sample time without any line number.Testing
New
tests/fusion.rs, with expected outputs taken from Stim 1.15. The AST proptest checks that print and parse are inverses, so it now re-parses through the unfused pipeline (the printer writes one instruction per line).cargo test --workspaceand the Python suite pass.Follow-ups
ppvm-stimerror lines for text input: validate before fusing.DisplayforTarget/PauliFactor.rec[...]on non-controlled gates at parse time, as Stim does.Assisted by Claude Code