Design typed instruction programs for logical QEC and physical lowering - #582
Design typed instruction programs for logical QEC and physical lowering#582qciaran wants to merge 17 commits into
Conversation
| The core direction is: | ||
|
|
||
| ```text | ||
| Typed resources + opaque instruction applications |
There was a problem hiding this comment.
I don't understand what "typed resources" means here. There's some sort of type system for the highest most opaque representation?
There was a problem hiding this comment.
ChatGPT: Yes. Here "resource" means a value flowing along graph edges, not necessarily an allocated hardware resource. Every value has a registered (possibly domain-opaque) type. The generic graph understands port compatibility plus traits such as linear versus copyable, but it does not interpret surface-code semantics. For example, QEC may define a linear LogicalPatch<RotatedSurfacePatch[d=3], Active>, while a measurement bit is copyable and a PauliByproduct may be linear. This lets the generic verifier catch wrong ports, incompatible input/output types, and double use of a consumed patch. "Typed values/resources" would be clearer than the current shorthand, so I will clarify that wording in the document.
| Typed resources + opaque instruction applications | ||
| | | ||
| v | ||
| InstrProgram / InstrModule / InstrGraph |
There was a problem hiding this comment.
Then sequences of statements representing instruction sequences on the code form a program, module, or graph? Is the ability to modularize definitions across files in scope?
There was a problem hiding this comment.
ChatGPT: That is the intended split:
InstrGraphis one typed body: calls, value edges, and structured control regions.InstrModuleis a reusable, parameterized definition with a signature and anInstrGraphbody.InstrProgramis the compilation/linkage unit that owns or imports module definitions, symbol identities, entry points, and exports.
Cross-file modularization is in scope at the artifact level: modules should be independently serializable, hashable/versioned, and importable by stable identity, so Rust or Python code in different files/packages can build and link them. A dedicated textual language and its filesystem/package syntax are not required for the first implementation. I will make that distinction explicit near this diagram.
There was a problem hiding this comment.
@qciaran seems like it wants a linear/affine type system lol
There was a problem hiding this comment.
ChatGPT: Yes—that is basically the ownership pressure here. QEC-block handles must not be duplicated or consumed twice, so successful control paths naturally want linear use. Affine use becomes useful for explicitly abandoned/discarded blocks and abort or failure paths (at most once rather than exactly once), while measurement and configuration values can be copyable/unrestricted. I do not think the MVP needs a general-purpose linear type calculus: it can begin with Linear | Copyable, require each linear value to be consumed or exported, and add an explicit discard plus richer affine/control-flow rules when those use cases are implemented. Rust and the thin Python API should infer and enforce this; ordinary circuit authors should not have to spell the ownership types themselves.
There was a problem hiding this comment.
Hmmm, linear types seem to add coding complexity though... when simple checks on whether resources have be reused or not might be sufficient...
There was a problem hiding this comment.
ChatGPT: I agree with this correction. Calling it a linear/affine type system overstates what the authoring API needs. The value can keep its ordinary domain type (patch, result, frame, and so on), while the graph verifier separately applies a small use policy such as SingleUse | Reusable. A patch-producing call returns a new handle; attempting to reuse the consumed handle is then just a clear validation error. Exact lifecycle requirements—such as whether a live patch must eventually be measured, exported, or explicitly discarded—can remain dialect validation rather than become general type-system machinery. This retains the useful safety check without making users write or reason about linear types.
| parameter/type/hierarchy/dialect elaboration | ||
| | | ||
| v | ||
| ElaboratedInstrProgram |
There was a problem hiding this comment.
I really don't know what elaboration means here? Is it some sort of down-selecting details of a code? I thought that would be more of the "explicit InstrSet resolution below, but I probably am misunderstanding that.
There was a problem hiding this comment.
ChatGPT: Elaboration is intended to be structural/template processing, not implementation selection. It resolves module/instruction symbols, binds and canonicalizes supplied parameters, specializes reusable module instances, unifies type variables, and computes concrete output types. For example, calling a generic teleport<P, rounds> module with a distance-3 patch and rounds=3 produces a concrete typed module instance. The following InstrSet resolution stage then chooses implementations such as SZZ versus CX syndrome extraction or transversal versus surgery. A named profile may elaborate into explicit resolution preferences, but it must not itself silently select an implementation. I agree the term and boundary need a short definition in the summary.
| 2. Keep instruction names and domain semantics out of `InstrGraph` itself. | ||
| 3. Separate a QEC instruction's semantic contract from the QEC protocol or | ||
| physical implementation that realizes it. | ||
| 4. Make every instruction declare its typed QEC-block inputs and outputs and |
There was a problem hiding this comment.
I see, I think I kind of understand the "types" now. We want to make sure a transversal CX for examples takes 2 code blocks as input, etc?
I guess my question becomes, is this program representation intended for program based construction (in which case a lack of type checking and just thinking about "well formalness" of a given series of operations suffices) or is it intended for by hand authoring (in which case, a more rigorous type system is good for putting guard rails on programmers so long as it isn't too onerous to deal with)?
There was a problem hiding this comment.
ChatGPT: Yes, a logical/transversal CX instruction has two typed code-block inputs and two replacement block outputs; arity is part of its public signature. Geometry matching or whether a particular transversal construction supports those blocks belongs to the selected implementation's supports check, not necessarily to the generic type system.
The representation is intended to be the shared artifact for both hand authoring and programmatic generation. Generated programs benefit from the same validation because generators, imports, rewrites, and implementation composition can still produce invalid ownership or port wiring. For hand authoring, the Python/Rust builders should infer result types and make valid composition easy.
I think of the checks in three layers:
- Generic construction checks arity, named ports, type compatibility, linear use, and branch/loop joins.
- Resolution checks implementation-specific facts such as matching patch geometry, orientation, adjacency, or supported bases.
- QEC verification checks that the resolved plan actually realizes the declared logical transformation.
So the type system should provide useful guardrails, but should stay a compact runtime/serialized type algebra rather than requiring users to write elaborate Rust-style type expressions. In this context, well-formedness is essentially what the first layer of type checking establishes.
There was a problem hiding this comment.
Yeah, I basically told it I want to think of "gadgets" as blocks that take typed code block inputs and returned typed code block outputs as a gadget might do some sort of transformation on the code blocks
There was a problem hiding this comment.
ChatGPT: Exactly—that is the central QEC mental model. Building on Ciaran's comment:
QecInstris the gadget interface/contract: named typed code-block inputs and outputs plus the declared logical relation or transformation.QecInstrImplis one selectable way to realize that gadget.QecInstrPlanis the concrete realization for the bound input patches and parameters.InstrGraphcomposes gadget instances by wiring compatible output values into later inputs.
This also covers more than ordinary gates: syndrome extraction can preserve the block type and declare logical identity; code switching can return a different code-block type; merge/split can change arity; preparation and destructive measurement can create or consume blocks. A transversal CX is then one implementation of a two-block logical-CX gadget, with its additional geometry restrictions checked during implementation resolution.
“Gadget” is a useful explanatory analogy. I would probably retain QecInstr as the API term because gadget is used in several narrower ways in QEC, but the document can use this analogy to make the abstraction more immediate.
There was a problem hiding this comment.
Ah, it got to the comment before me. lol
|
|
||
| ## Summary | ||
|
|
||
| PECOS should support constructing programs from typed resources and unresolved |
There was a problem hiding this comment.
It may be nice to start with a high-level purpose statement as the first part of the summary. The first sentence seems like a good scoping of creating a program format users can stitch together and then lower to different formats, which sounds very similar to SLR. So, there's not a clear sense to how this differs to SLR in terms of use case, just in representation/implementation details.
There was a problem hiding this comment.
It was inspired by SLR and this may be its replacement. As well as inspired by older representations I had... plus wanting to go in an HDL direction so we can reuse one representation at multiple levels of abstraction. But yeah, good call out.
There was a problem hiding this comment.
ChatGPT: Addressed in fdc488b. The summary now starts with the user-facing purpose: an HDL-like hierarchy that lets different users reason at the logical, gadget/protocol, resource and space-time, physical-circuit, or executable level while opening black boxes only when needed. It also now describes the QEC direction as an evolutionary successor to SLR—informally an "SLR 2.0"—and distinguishes it through typed gadget interfaces, hierarchy, late implementation selection, shared space-time/visualization views, and physical lowering. The design does not make SLR a dependency or assume immediate replacement; deprecation would follow working feature and migration parity.
peter-campora
left a comment
There was a problem hiding this comment.
Left a bunch of questions and comments.
|
ChatGPT: Incorporated additional design review in aa519b5. The main clarifications are:
The MVP remains intentionally narrow: it implements the portable protocol plan and a reference schedule, while target mapping, execution-trace import, dynamic control, and richer planning remain deferred behind the now-explicit artifact boundaries. |
Deep reviewReviewed at Verdict: revise. The problem is real and the architectural instincts are sound. The factual grounding is unusually good for a document this size — roughly a dozen claims about existing APIs were spot-checked ( 1. The MVP's cost is invisible
Plus Please name it as a work item with its own estimate, or drop the Rust-only requirement from the MVP. 2. Cross-call detector construction has no owner, and the MVP demo needs it
The design assigns detector definitions to per-call This is not a future concern: the MVP demo is exactly prepare / syn_extract / transversal-CX / syn_extract / measure, and Two adjacent gaps in the same area:
Related: the MVP has no concurrency or round-alignment construct at all. 3. Two prior generations of this design are in the repo and go uncited
Separately, Something caused the team to build Blocking correctness issues in the documents
Smaller corrections
A process note
Suggested sequence
How this review was runThe branch was checked out read-only at Findings were then fused rather than concatenated: each one was re-checked against the code before being recorded here, and the ones that did not hold were dropped. Corroboration across arms raised confidence; several of the sharpest items above are solo catches from a single arm. Two caveats worth stating. Two of the three arms are Claude-family, so cross-model independence rests on the Codex arm alone — though that arm did produce solo findings the others missed, including the |
Deep review, round 2Reviewed at Verdict: REVISE, 4 of 4 arms. Round 1's verdict stands and all three of its headline blockers were re-found independently. Round 2 adds thirteen findings round 1 did not have; three are blockers. New in round 2N1. Blocker. No existing PECOS reference produces the MVP demo. (all four arms) The demo mandates SZZ extraction plus transversal CX ( N2. Blocker. N3. Blocker. The design's own H-then-CX examples are rejected by the reference, for a physical reason. (Codex, QEC arm) N4. Major. Observable existence is program-global, not per-plan. (Codex, QEC arm) After CX the builder omits a non-deterministic logical observable while still incrementing the observable index ( N5. Major. SZZ carries a pending local Clifford across the instruction boundary. (QEC arm, solo) The SZZ device-flow lowering defers free-standing single-qubit Cliffords until the next SZZ or measurement host ( N6. Major. Rust and Python order Z checks differently. (QEC arm, solo) Python emits right boundary, then bulk, then left boundary ( N7. Major. The traced route is single-shot and branch-specialised. (QEC arm, solo) N8. Major. A serialised program cannot regain executable N9. Major. Byte-equivalence and "match" are untestable, and one of them is vacuous. (Codex, generalist, complexity) No wire format, map ordering, float encoding, ID allocation rule, or digest specification exists. Separately, since Python is a thin view over the Rust serialiser ( N10. Major. N11. Major. The generic dialect-agnostic substrate has no consumer. (complexity arm, solo) Goal 12 ( N12. Major. Stage 1a requires five things the MVP defers, and the stage order is inverted twice. (complexity arm, solo) N13. Minor. Further grounding gaps. (Codex, generalist, complexity) Neither Round 1 findings corroborated in round 2
Round 1 findings no arm re-raisedBevy/wgpu justification; Conflicts resolved
Must-fix, ranked and deduplicated across both rounds
What every arm might have missed
How this review was runThe branch was checked out read-only at
All four arms returned REVISE independently. Findings were fused rather than concatenated: corroboration across arms raised confidence, solo findings were kept on merit after verification, and the two conflicts between arms and round 1 were resolved by reading the cited code. No arm executed any code; all verification was by reading. |
| let data = graph.block("data", SurfacePatch::rotated(3)?)?; | ||
| let ancilla = graph.block("ancilla", SurfacePatch::rotated(3)?)?; | ||
|
|
||
| graph.parallel(|region| { |
There was a problem hiding this comment.
Do we explicitly need to produce parallel blocks for any operations that are data-parallelizable or is there some implicitness based on data-flow? The intention of this construct isn't clear to me.
There was a problem hiding this comment.
Good question. I think it is taking it from some of the experimentation in SLR. I think it might be nice to let it both fall out of data-flow based things but also be able to explicitly schedule it. Part of what we probably want out of this is to be able to try out ideas by hand and have more direct manipulation... But also be able to let this to be worked out programmatically when we don't want to bother. If we just allow space-time coordinate pinning and rely more on the space-time block picture... That might be better that having some sort of "parallel" block... More explicit and expressive...
| The relationship to existing artifacts is therefore: | ||
|
|
||
| ```text | ||
| Original QuantumCircuit symbol + locations + params |
There was a problem hiding this comment.
So, are we just thinking about logical quantum circuits with a dataflow based representation for this representation? No higher order constructs like function definitions with parameters, etc.? This would drastically simplify things so that we're just thinking about composing dataflow based building blocks instead of thinking about variables with scope, etc.
There was a problem hiding this comment.
I think so... Have more of a picture of how information is processed and ran on devices rather than abstracting to a high level language...
There was a problem hiding this comment.
Still need to think about branching etc... 🤔
| bell = body.input("bell") | ||
| destination = body.input("destination") | ||
|
|
||
| body.append(surface.h(patch=bell).using("transversal")) |
There was a problem hiding this comment.
I really hate string based parameters to control things if instead there's actually a limited set of enum values like for this using("transversal")
I would suggest instead of these things are built-in that there be some kind of language level module where you can import enum values to pass.
There was a problem hiding this comment.
That is fair... Strings as options suck. However we probably won't want to know a prior the enums. An implementation or whatever sits slightly higher than an implementation should have a list of enums/variants it provides rather than it being universally known by the "language."
| teleported, byproduct = graph.append( | ||
| surface.teleport_raw(source=data, destination=destination) | ||
| ) | ||
| teleported = graph.append( |
There was a problem hiding this comment.
I really worry that if this is an intended authoring environment for people writing QEC experiments that repeated .append(...) calls will be tiresome.
| program = InstrProgram(instruction_sets=[surface]) | ||
| graph = program.main() | ||
|
|
||
| data = graph.add_block("data", SurfacePatch.rotated(3)) |
There was a problem hiding this comment.
Could we considered maybe adding some syntactic sugar for .add_block and .apply. These feel file for use when tools are generating logical programs in this representation, but this feels like it could be tiresome.
There was a problem hiding this comment.
I think first maybe we go about this by building the "language" in Rust as just a data structure sort of like SLR is now and then think about turning it into a proper language once we like the shape of it and can add syntactic sugar then. Maybe macros could be a middle ground... Or creative use of Rust 😄
There was a problem hiding this comment.
But good to think about nicer ways to express this all 🤔
| `GeneratedTickProgram` artifacts must remain available. Direct lowering must | ||
| run entirely in Rust and must not import Guppy or Python. | ||
|
|
||
| ## Guppy lowering |
There was a problem hiding this comment.
I really hate the idea of producing Guppy just because it's submitted by users. It's not intended as a compilation target and its type system and limited functionality makes it really unsuitable for being a target for lowering and will make reasoning about the scheduling of operations difficult. I don't love HUGR but it's far more suitable for this. My preference would be generating QIR or quantum MLIR dialects.
There was a problem hiding this comment.
That is fair. I think it is a left over from SLR's goal of basically being a transpiler. Outputting Guppy is probably a burden we don't need
There was a problem hiding this comment.
ChatGPT: Addressed in 49a84ad. InstrProgram no longer lowers to Guppy, and the direct-versus-Guppy equivalence requirement has been removed. The design now lowers resolved gadgets to PHIR and/or a portable ProtocolPhysicalPlan; the PECOS reference route lowers that plan directly to normalized TickCircuit and native DEM, while target backends can consume the portable plan separately. Guppy remains only as an optional input through compiled HUGR. An imported HUGR body can be registered as a gadget implementation only with an explicit QEC contract, port/resource mapping, lifecycle and frame semantics, measurement roles, and a conformance obligation. QIR and suitable quantum MLIR dialects are retained as possible later exports after their semantic and crate-dependency boundaries are audited.
|
Thanks for all of the comments @peter-campora! These are really useful! |
|
ChatGPT: Follow-up in dbd80fb separates lowering from DEM analysis. Selected gadget implementations now contribute typed lower-level Rust |
|
ChatGPT: The readability pass is in 98e4f3d. The former architecture document mixed overview, normative requirements, implementation rationale, and roadmap, so it is now split by reader intent: |
|
ChatGPT: I pushed a follow-up that makes the older PECOS/SLR/HDL relationship concrete. The revision also treats each instruction call as an HDL-like cell. Typed resource/value wires enter and leave its ports, and a chosen implementation can attach a hierarchical space-time realization containing child cells. Shape is realization-specific (parameters, implementation, schedule, and mapping matter), with unplaced/constrained/placed states. I added a coarse/expanded space-time diagram while keeping placement and interactive visualization outside the MVP. The MVP uses the fully declared static subset but now preserves the declaration/linkage/body boundaries needed for the open model. |
Summary
InstrProgramdataflow model with thin ergonomic Python bindingsInstrGraphrepresentation, recursively composing lower-level instructions down to consumer-supported primitivesGateTypeoperations as a built-in typed instruction library rather than graph syntaxProtocolProgramas a validated portable-dialect phase of the same graph modelTickCircuit, and preserve frames, measurements, detectors, observables, and provenanceProtocolProgramto reference Tick output or target backends; treat DEM construction as a separate analysis consumerDocument map
design/instr-program.md: reader-oriented overview, surface-memory example, linkage/expansion model, and space-time picturedesign/instr-program-mvp.md: normative first implementation and acceptance testsdesign/instr-program-rationale.md: PECOS/SLR context, detailed Rust model, crate direction, alternatives, roadmap, and open questionsdesign/instr-program-spacetime.svg: coarse and expanded view of nested instruction volumes and resource wiresScope
This is a design-only draft. The MVP uses the fully declared, statically provided subset while preserving separate declaration, linking, and implementation phases. Guppy generation, PHIR integration, general packages, dynamic control, target mapping, higher-level noise dialects, placed space-time tooling, and interactive visualization are deferred from the first implementation.
Testing
Documentation-only change. Cross-link, Markdown fence, whitespace, reference-constructor, confidential-term, XML, rendered-diagram, and Git diff checks pass.