-
Notifications
You must be signed in to change notification settings - Fork 447
Keep the control when decomposing a controlled swap #5276
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
f5ff99a
e8bcab6
149c3b0
42d24ab
6f8d484
c280277
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -582,6 +582,12 @@ REGISTER_DECOMPOSITION_PATTERN(R1AdjToR1, {"r1<adj>", "r1"}); | |
| // quake.cnot b, a; | ||
| // quake.cnot a, b; | ||
| // quake.cnot b, a; | ||
| // | ||
| // quake.swap [c] a, b | ||
| // ─────────────────────────────────── | ||
| // quake.x [b] a; | ||
| // quake.x [c, a] b; // c is negated iff the swap control was negated | ||
| // quake.x [b] a; | ||
| struct SwapToCXType; // forward declare the pattern type, defined in the macro | ||
| // below | ||
| struct SwapToCX | ||
|
|
@@ -591,22 +597,54 @@ struct SwapToCX | |
|
|
||
| LogicalResult matchAndRewrite(cudaq::quake::SwapOp op, | ||
| PatternRewriter &rewriter) const override { | ||
| auto numControls = cudaq::getKnownNumControls(op); | ||
| if (!numControls || *numControls > 1 || !isEnabled(*numControls)) | ||
| return failure(); | ||
|
|
||
| // Op info | ||
| Location loc = op->getLoc(); | ||
| Value a = op.getTarget(0); | ||
| Value b = op.getTarget(1); | ||
|
|
||
| QuakeOperatorCreator qRewriter(rewriter); | ||
| qRewriter.create<cudaq::quake::XOp>(loc, b, a); | ||
| qRewriter.create<cudaq::quake::XOp>(loc, a, b); | ||
| qRewriter.create<cudaq::quake::XOp>(loc, b, a); | ||
| if (*numControls == 1) { | ||
| // This is a Fredkin gate. This decomposition does not support | ||
| // `quake.control` types. | ||
| if (containsControlTypes(op)) | ||
| return failure(); | ||
| SmallVector<Value, 1> controls(1); | ||
| if (failed(checkAndExtractControls(op, controls, rewriter))) | ||
| return failure(); | ||
| Value c = controls[0]; | ||
|
|
||
| qRewriter.create<cudaq::quake::XOp>(loc, b, a); | ||
| SmallVector<Value, 2> ccxControls{c, a}; | ||
| auto ccxOp = qRewriter.create<cudaq::quake::XOp>(loc, ccxControls, b); | ||
| // The outer cnots do not touch the control qubit, so a complemented | ||
| // swap control is equivalent to complementing the toffoli's first | ||
| // control. Let expand-control-negations materialize it downstream. | ||
| if (auto swapNegations = op.getNegatedQubitControls()) { | ||
| // One flag per control operand is required; pad for target a. | ||
| SmallVector<bool> flags{(*swapNegations)[0], false}; | ||
| ccxOp.setNegatedQubitControls( | ||
| DenseBoolArrayAttr::get(rewriter.getContext(), flags)); | ||
| } | ||
| qRewriter.create<cudaq::quake::XOp>(loc, b, a); | ||
|
|
||
| qRewriter.selectWiresAndReplaceUses(op, ValueRange{a, b}); | ||
| // The wires are ordered controls first, then targets. | ||
| qRewriter.selectWiresAndReplaceUses(op, ValueRange{ccxControls[0], a, b}); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. c280277 has both read the toffoli output: the final cnot is created with |
||
| } else { | ||
| qRewriter.create<cudaq::quake::XOp>(loc, b, a); | ||
| qRewriter.create<cudaq::quake::XOp>(loc, a, b); | ||
| qRewriter.create<cudaq::quake::XOp>(loc, b, a); | ||
| qRewriter.selectWiresAndReplaceUses(op, ValueRange{a, b}); | ||
| } | ||
| rewriter.eraseOp(op); | ||
| return success(); | ||
| } | ||
| }; | ||
| REGISTER_DECOMPOSITION_PATTERN(SwapToCX, {"swap", "x(1)"}); | ||
| REGISTER_DECOMPOSITION_PATTERN(SwapToCX, {"swap", "x(1)"}, | ||
| {"swap(1)", "x(1)", "x(2)"}); | ||
|
|
||
| // quake.h control, target | ||
| // ─────────────────────────────────── | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| // ========================================================================== // | ||
| // Copyright (c) 2022 - 2026 NVIDIA Corporation & Affiliates. // | ||
| // All rights reserved. // | ||
| // // | ||
| // This source code and the accompanying materials are made available under // | ||
| // the terms of the Apache License 2.0 which accompanies this distribution. // | ||
| // ========================================================================== // | ||
|
|
||
| // CircuitCheck is deliberately not used here: its UnitaryBuilder has no | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please file an issue for tracking purposes.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| // controlled multi-target path, so it miscomputes the unitary of a | ||
| // controlled swap on the reference side. | ||
|
|
||
| // RUN: cudaq-opt -pass-pipeline='builtin.module(decomposition{enable-patterns=SwapToCX})' %s | FileCheck %s | ||
|
|
||
| func.func @test_controlled(%qc: !quake.ref, %qa : !quake.ref, %qb : !quake.ref) { | ||
| quake.swap [%qc] %qa, %qb : (!quake.ref, !quake.ref, !quake.ref) -> () | ||
| return | ||
| } | ||
|
|
||
| // CHECK-LABEL: func.func @test_controlled | ||
| // CHECK: quake.x {{\[}}%[[VAL_2:.*]]] %[[VAL_1:.*]] : (!quake.ref, !quake.ref) -> () | ||
| // CHECK: quake.x {{\[}}%[[VAL_0:.*]], %[[VAL_1]]] %[[VAL_2]] : (!quake.ref, !quake.ref, !quake.ref) -> () | ||
| // CHECK: quake.x {{\[}}%[[VAL_2]]] %[[VAL_1]] : (!quake.ref, !quake.ref) -> () | ||
| // CHECK: return | ||
| // CHECK: } | ||
|
|
||
| // A complemented control becomes a negated first control on the toffoli. | ||
| func.func @test_complemented(%qc: !quake.ref, %qa : !quake.ref, %qb : !quake.ref) { | ||
| quake.swap [%qc neg [true]] %qa, %qb : (!quake.ref, !quake.ref, !quake.ref) -> () | ||
| return | ||
| } | ||
|
|
||
| // CHECK-LABEL: func.func @test_complemented( | ||
| // CHECK: quake.x {{\[}}%[[B_VAL:.*]]] %[[A_VAL:.*]] : (!quake.ref, !quake.ref) -> () | ||
| // CHECK-NEXT: quake.x {{\[}}%[[C_VAL:.*]], %[[A_VAL]] neg {{\[}}true, false{{\]}}{{\]}} %[[B_VAL]] : (!quake.ref, !quake.ref, !quake.ref) -> () | ||
| // CHECK-NEXT: quake.x {{\[}}%[[B_VAL]]] %[[A_VAL]] : (!quake.ref, !quake.ref) -> () | ||
| // CHECK: return | ||
| // CHECK: } | ||
|
|
||
| // The uncontrolled case must keep the plain three-CNOT lowering. | ||
| func.func @test_bare(%qa: !quake.ref, %qb : !quake.ref) { | ||
| quake.swap %qa, %qb : (!quake.ref, !quake.ref) -> () | ||
| return | ||
| } | ||
|
|
||
| // CHECK-LABEL: func.func @test_bare | ||
| // CHECK: quake.x {{\[}}%[[VAL_1:.*]]] %[[VAL_0:.*]] : (!quake.ref, !quake.ref) -> () | ||
| // CHECK: quake.x {{\[}}%[[VAL_0]]] %[[VAL_1]] : (!quake.ref, !quake.ref) -> () | ||
| // CHECK: quake.x {{\[}}%[[VAL_1]]] %[[VAL_0]] : (!quake.ref, !quake.ref) -> () | ||
| // CHECK: return | ||
| // CHECK: } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be the updated a from ccxControls?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, fixed in c280277. The toffoli create updates ccxControls and the target in place, so the final cnot now consumes ccxControls[1] and the replacement list reads {ccxControls[0], ccxControls[1], b} after that last update. Reference-semantics IR is unaffected; under wire semantics the stale a pointed at a wire the toffoli had already consumed. Added SwapToCXThreadsWireOperands to pin the threading.