Repository navigation
Accept controlled(par) @(control, target) wire counts - #528
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| "expected a 3-qubit controlled par: {text}" | ||
| ); | ||
| assert!( | ||
| text.contains(r#"gate_name = "CNOT""#), |
There was a problem hiding this comment.
This is the issue's surface program, but the assertions do not lock the tuple. Any lowering that emits one CNOT inside a 3-qubit ctrl_layer passes, including both arms on (0, 1) or a dropped second arm.
controlled_parn_explicit_target_tuple does check pairs (0, 1) and (0, 2), but it calls decompose_controlled with an already-split target. It never parses @(0, (1, 2)) and never runs split_control_target. Please assert the CNOT operand pairs produced from this surface spelling (and from @(0, 1, 2) / @(ctrl, start) if those are part of the contract).
There was a problem hiding this comment.
frontend/tests/lower.rs::controlled_par_nested_targets_lower now parses and lowers the surface programs, and asserts the CNOT operand pairs rather than a bare gate_name = "CNOT":
controlled(par { H @0, H @0 }) @(0, (1, 2))controlled(par { H @0, H @0 }) @(0, 1, 2)controlled(par { H @0 } * 2) @(0, 1)
Each must emit CNOT pairs (0, 1) then (0, 2). The pairs come from tracking each quantum.circ.gate SSA value back to its block argument, so both arms landing on (0, 1), or a dropped second arm, fails the test.
The elaborator tests build the same nested and flat GateApp values and run them through elaborate_circuit_body, which calls split_control_target, then assert those same CNOT operand pairs. controlled_parn_explicit_target_tuple still calls decompose_controlled with an already-split target; it is no longer the only coverage of this spelling.
| // contiguously. The two written indices are control and that start. | ||
| let par_body = matches!(inner.0, Expr::Par(_, _) | Expr::ParN(_)); | ||
| if par_body && body_is_single_index && items.len() == 2 && arity > 2 && indices.len() == 2 { | ||
| let implied_last = literal_index(indices[1]).map(|start| start.saturating_add(arity - 2)); |
There was a problem hiding this comment.
The contiguous-start shorthand typechecks when the start is not a literal (implied_last stays None), but elaboration does not occupy start .. start+(arity-2).
wire_slice shifts that start through shift_qubit_targets, which only rewrites Expr::Int and clones any other expression. controlled(par { H @0 } * 2) @(0, s) therefore places both arms on s. This spelling used to be GateTargetArity (2 slots vs width 3), so the PR newly accepts it while the wires still do not match the typechecker.
Reject a non-literal start here, or build start+i and fail closed when it does not evaluate to a literal. Bounds checking has the same hole: with implied_last == None, s+1 is never compared to the register width.
Requesting changes. I am not approving.
There was a problem hiding this comment.
A non-literal start is now a type error (NonLiteralControlledStart). The contiguous shorthand only succeeds when start is a non-negative integer literal, so implied_last is always Some(start + arity - 2) and the existing register-cap check compares that index to the width. controlled(par { H @0 } * 2) @(0, 2) inside Circuit<3, …> is IndexOutOfBounds { index: 3, width: 3 }: the written start is in range, and wire 3 is not.
wire_slice fails closed when a non-Expr::Int start would be shifted or expanded. shift_qubit_targets still only rewrites integer literals, and that path is no longer reachable for a symbolic @(control, start). fn f(s: Int) … @(0, s) is rejected in the typechecker, and decompose_controlled on a Var start returns an elaboration error instead of placing both arms on s.
| | `repeat(k, c)` (concrete & symbolic count) | ✓ | ✓ | ✓ | ✓ | — | Grover/QFT fixtures | | ||
| | `controlled(named_gate)` / `controlled(Rz(θ))` | ✓ | ✓ | ✓ | ✓ | — | Distributed via `decompose_controlled` | | ||
| | `controlled(par { … })` / `controlled(par { c } * k)` | ✓ | ✗ | ✗ | ✗ | — | Typechecker rejects multi-target tuple; see [#369](#known-limitations) | | ||
| | `controlled(par { … })` / `controlled(par { c } * k)` | ✓ | ✓ | ✓ | ✓ | — | `@(ctrl, (t₁, …))`; `par` repeat also accepts contiguous `@(ctrl, start)` | |
There was a problem hiding this comment.
This page defines ✓ as stable and covered by a CI fixture, and QASM/NA ✓ specifically as test/lit FileCheck. This PR adds no lit or neutral-atom fixture; the only pipeline test is the MLIR substring check in lower.rs.
A multi-qubit arm still does not lower. controlled(par { CNOT @(0, 1) }) @(0, (1, 2)) typechecks (controlled width 3, three named wires) and then fails in the GateApp arm of decompose_controlled (controlled() of a multi-qubit gate in a named circuit body). controlled_named_gate rejects the same shape with controlled() of a multi-qubit gate. The row should stay partial until that is supported or explicitly excluded.
"Fixes #369" plus deleting the known-limitation note will close the issue while QASM and NA are still unchecked and that arm still fails after typecheck.
There was a problem hiding this comment.
The closing keyword is gone from the title, the body, and the branch commit. This does not close #369.
The support-matrix row is partial again. Typecheck, QASM, and NA are ◐. QASM and NA are not marked stable: there is no test/lit fixture and no neutral-atom fixture. The known-limitation note is restored.
controlled(par { CNOT @(0, 1) }) @(0, (1, 2)) still typechecks (controlled_par_multiqubit_arm_typechecks) and then fails elaboration of the multi-qubit arm (controlled_par_multiqubit_arm_does_not_lower). That path stays partial until it lowers.
|
Requesting changes. Not approved. For literal indices,
No new |
controlled(par { ... }) @(control, (t1, t2)) names k+1 wires, but the
typechecker counted the outer pair as two targets and rejected it. Count
the control wire plus the body's target tuple (and the flat and
contiguous-start spellings), and distribute those wires across par arms
when elaborating.
A contiguous start must be a non-negative literal so every implied index
is bounds-checked. Width-1 arms lower; a multi-qubit arm still fails
elaboration. Refs #369.
Co-authored-by: Arnab Ghosh <ag2583@cornell.edu>
4d9f8c9 to
7b34086
Compare
|
Approved. I would merge The three earlier requests are met:
No new |
controlled(par { … }) @(control, (t1, t2))names one control wire plus the body's wires, which is the controlled circuit's width. The typechecker counted the outer pair as two flat targets and rejected the placement withGateTargetArity, even thoughdecompose_controlledalready distributes control overpararms.This counts those wires instead of the outer slots:
@(control, (t1, …, tk))and the flat@(control, t1, …, tk)must namek + 1qubits.controlled(par …) @(control, start)means contiguous body wires beginning atstart, andstartmust be a non-negative integer literal. A symbolic start is rejected, so every implied index is compared to the register width. Elaboration fails closed if a non-literal start would have to be shifted.pararm takes a slice of that width. The offset is the body's width, not the controlled step's max index (that index includes the control wire).controlled(H) @(0, 1)is unchanged. A non-parbody wider than one qubit still has to name every wire; a bare start index is rejected.Width-1 arms lower to
quantum.circ.controlled(par { CNOT @(0, 1) }) @(0, (1, 2))typechecks and then fails elaboration of the multi-qubit arm. The support matrix keeps this row partial: there is notest/litOpenQASM fixture and no neutral-atom fixture.Refs #369.