Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
(or a ctrl region coming from the frontend) can be reduced to op-level controlled gates
before calling into the graph solver.
[(#3089)](https://github.com/PennyLaneAI/catalyst/pull/3089)
[(#3090)](https://github.com/PennyLaneAI/catalyst/pull/3090)

* The `local-random` unitary folding option for :func:`~.mitigate_with_zne` is now implemented,
reproducing Mitiq's ``fold_gates_at_random``: every gate is folded ``floor((scale_factor-1)/2)``
Expand Down
4 changes: 2 additions & 2 deletions mlir/include/Quantum/IR/QuantumOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -980,8 +980,8 @@ def CtrlOp : Region_Op<"ctrl", [SingleBlockImplicitTerminator<"YieldOp">,
region gains the control qubits/values (appended to any controls it already carries),
and the control qubits are threaded through the region. Structural ops (extract, insert,
alloc, dealloc) are passed through unchanged, and a nested `quantum.ctrl` region has its
controls merged. Measurements, `scf` control flow, and nested `quantum.adjoint` regions
inside a `quantum.ctrl` region are rejected.
controls merged. Measurements, and `scf` control flow inside a `quantum.ctrl` region
are rejected.
}];

let regions = (region SizedRegion<1>:$region);
Expand Down
11 changes: 11 additions & 0 deletions mlir/lib/Quantum/Transforms/AdjointLowering/AdjointLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,17 @@ struct AdjointSingleOpRewritePattern : public OpRewritePattern<AdjointOp> {
/// correspondence with a notable exception caused by `insert`/`extract` API asymmetry.
LogicalResult matchAndRewrite(AdjointOp adjoint, PatternRewriter &rewriter) const override
{
// Defer (not an error) if the region still contains a nested quantum.ctrl region.
// ctrl-lowering must reduce it to op-level controlled gates first; reversing and adjointing
// those gates is then trivial ((C(g))^dagger = C(g^dagger)). The pipeline runs
// (ctrl-lowering, adjoint-lowering) to a fixpoint, so this adjoint op lowers on a later
// iteration. Pre-scanning here avoids the ReversePass "Unhandled operation" error path.
if (adjoint.getRegion()
.walk([](CtrlOp) { return WalkResult::interrupt(); })
.wasInterrupted()) {
return failure();
}

QuantumCache cache =
QuantumCache::initialize(adjoint.getRegion(), rewriter, adjoint.getLoc());

Expand Down
17 changes: 12 additions & 5 deletions mlir/lib/Quantum/Transforms/CtrlLowering/CtrlLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,18 @@ struct CtrlLoweringRewritePattern : public OpRewritePattern<CtrlOp> {

LogicalResult matchAndRewrite(CtrlOp ctrl, PatternRewriter &rewriter) const override
{
// Defer (not an error) if the region still contains a nested quantum.adjoint region.
// Distributing controls needs an op-level body, so the inner region must be reduced first.
// The pipeline runs (ctrl-lowering, adjoint-lowering) to a fixpoint: adjoint-lowering
// reduces the inner region to op-level gates, then this ctrl op lowers on a later
// iteration. A pre-scan avoids a partial rewrite (creating ops, then bailing out
// mid-region).
if (ctrl.getRegion()
.walk([](AdjointOp) { return WalkResult::interrupt(); })
.wasInterrupted()) {
return failure();
}

Block &block = ctrl.getRegion().front();

// Map the region's block arguments (the target qubits/registers) to the ctrl op operands.
Expand Down Expand Up @@ -224,11 +236,6 @@ struct CtrlLoweringRewritePattern : public OpRewritePattern<CtrlOp> {
mergedCtrlResults.end());
continue;
}
if (isa<AdjointOp>(op)) {
op.emitError("nested quantum.adjoint inside a quantum.ctrl region is not supported "
"by ctrl-lowering; run adjoint-lowering first");
return failure();
}
if (isa<scf::ForOp, scf::IfOp, scf::WhileOp, scf::IndexSwitchOp>(op)) {
op.emitError(
"control flow inside a quantum.ctrl region is not supported by ctrl-lowering");
Expand Down
126 changes: 126 additions & 0 deletions mlir/test/Quantum/CtrlAdjointNestedTest.mlir
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Copyright 2026 Xanadu Quantum Technologies Inc.

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Nested quantum.ctrl / quantum.adjoint regions are reduced innermost-out by running the two
// lowering passes as a fixpoint. Two rounds of (ctrl-lowering, adjoint-lowering) are enough for the
// nesting depths exercised here.
// RUN: quantum-opt --ctrl-lowering --adjoint-lowering --ctrl-lowering --adjoint-lowering \
// RUN: --split-input-file %s | FileCheck %s

// CHECK-LABEL: @nested_ctrl_of_ctrl
func.func @nested_ctrl_of_ctrl(%c1: !quantum.bit, %c2: !quantum.bit, %q: !quantum.bit)
-> (!quantum.bit, !quantum.bit, !quantum.bit) {
%true = arith.constant true
// CHECK-NOT: quantum.ctrl
// CHECK: quantum.custom "Hadamard"() %{{.*}} ctrls(%{{.*}}, %{{.*}}) ctrlvals(%{{.*}}, %{{.*}})
%oc, %or:2 = quantum.ctrl(%c1) ctrlvals(%true) (%c2, %q : !quantum.bit, !quantum.bit) : !quantum.bit -> !quantum.bit, !quantum.bit {
^bb0(%ac2: !quantum.bit, %aq: !quantum.bit):
%ic, %iq = quantum.ctrl(%ac2) ctrlvals(%true) (%aq : !quantum.bit) : !quantum.bit -> !quantum.bit {
^bb1(%iiq: !quantum.bit):
%h = quantum.custom "Hadamard"() %iiq : !quantum.bit
quantum.yield %h : !quantum.bit
}
quantum.yield %ic, %iq : !quantum.bit, !quantum.bit
}
return %oc, %or#0, %or#1 : !quantum.bit, !quantum.bit, !quantum.bit
}

// -----

// CHECK-LABEL: @nested_ctrl_of_adj
func.func @nested_ctrl_of_adj(%c: !quantum.bit, %q: !quantum.bit) -> (!quantum.bit, !quantum.bit) {
%true = arith.constant true
// CHECK-NOT: quantum.ctrl
// CHECK-NOT: quantum.adjoint
// CHECK: quantum.custom "S"() %{{.*}} adj ctrls(%{{.*}}) ctrlvals(%{{.*}})
%oc, %or = quantum.ctrl(%c) ctrlvals(%true) (%q : !quantum.bit) : !quantum.bit -> !quantum.bit {
^bb0(%aq: !quantum.bit):
%a = quantum.adjoint(%aq) : !quantum.bit {
^bb1(%iq: !quantum.bit):
%s = quantum.custom "S"() %iq : !quantum.bit
quantum.yield %s : !quantum.bit
}
quantum.yield %a : !quantum.bit
}
return %oc, %or : !quantum.bit, !quantum.bit
}

// -----

// CHECK-LABEL: @nested_adj_of_ctrl
func.func @nested_adj_of_ctrl(%c: !quantum.bit, %q: !quantum.bit) -> (!quantum.bit, !quantum.bit) {
%true = arith.constant true
// CHECK-NOT: quantum.ctrl
// CHECK-NOT: quantum.adjoint
// CHECK: quantum.custom "S"() %{{.*}} adj ctrls(%{{.*}}) ctrlvals(%{{.*}})
%a:2 = quantum.adjoint(%c, %q) : !quantum.bit, !quantum.bit {
^bb0(%ac: !quantum.bit, %aq: !quantum.bit):
%oc, %or = quantum.ctrl(%ac) ctrlvals(%true) (%aq : !quantum.bit) : !quantum.bit -> !quantum.bit {
^bb1(%iq: !quantum.bit):
%s = quantum.custom "S"() %iq : !quantum.bit
quantum.yield %s : !quantum.bit
}
quantum.yield %oc, %or : !quantum.bit, !quantum.bit
}
return %a#0, %a#1 : !quantum.bit, !quantum.bit
}

// -----

// CHECK-LABEL: @nested_ctrl_of_adj_of_ctrl
func.func @nested_ctrl_of_adj_of_ctrl(%c1: !quantum.bit, %c2: !quantum.bit, %q: !quantum.bit)
-> (!quantum.bit, !quantum.bit, !quantum.bit) {
%true = arith.constant true
// CHECK-NOT: quantum.ctrl
// CHECK-NOT: quantum.adjoint
// CHECK: quantum.custom "S"() %{{.*}} adj ctrls(%{{.*}}, %{{.*}}) ctrlvals(%{{.*}}, %{{.*}})
%oc, %or:2 = quantum.ctrl(%c1) ctrlvals(%true) (%c2, %q : !quantum.bit, !quantum.bit) : !quantum.bit -> !quantum.bit, !quantum.bit {
^bb0(%ac2: !quantum.bit, %aq: !quantum.bit):
%adj:2 = quantum.adjoint(%ac2, %aq) : !quantum.bit, !quantum.bit {
^bb1(%bc2: !quantum.bit, %bq: !quantum.bit):
%ic, %iq = quantum.ctrl(%bc2) ctrlvals(%true) (%bq : !quantum.bit) : !quantum.bit -> !quantum.bit {
^bb2(%iiq: !quantum.bit):
%s = quantum.custom "S"() %iiq : !quantum.bit
quantum.yield %s : !quantum.bit
}
quantum.yield %ic, %iq : !quantum.bit, !quantum.bit
}
quantum.yield %adj#0, %adj#1 : !quantum.bit, !quantum.bit
}
return %oc, %or#0, %or#1 : !quantum.bit, !quantum.bit, !quantum.bit
}

// -----

// CHECK-LABEL: @nested_adj_of_ctrl_of_adj
func.func @nested_adj_of_ctrl_of_adj(%c: !quantum.bit, %q: !quantum.bit) -> (!quantum.bit, !quantum.bit) {
%true = arith.constant true
// CHECK-NOT: quantum.ctrl
// CHECK-NOT: quantum.adjoint
// CHECK: quantum.custom "S"() %{{[^ ]+}} ctrls(%{{[^ ]+}}) ctrlvals(%{{[^ ]+}})
%oa:2 = quantum.adjoint(%c, %q) : !quantum.bit, !quantum.bit {
^bb0(%ac: !quantum.bit, %aq: !quantum.bit):
%mc, %mr = quantum.ctrl(%ac) ctrlvals(%true) (%aq : !quantum.bit) : !quantum.bit -> !quantum.bit {
^bb1(%iq: !quantum.bit):
%ia = quantum.adjoint(%iq) : !quantum.bit {
^bb2(%iiq: !quantum.bit):
%s = quantum.custom "S"() %iiq : !quantum.bit
quantum.yield %s : !quantum.bit
}
quantum.yield %ia : !quantum.bit
}
quantum.yield %mc, %mr : !quantum.bit, !quantum.bit
}
return %oa#0, %oa#1 : !quantum.bit, !quantum.bit
}
20 changes: 0 additions & 20 deletions mlir/test/Quantum/CtrlTest.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -159,23 +159,3 @@ func.func @ctrl_scf(%ctrl: !quantum.bit, %q: !quantum.bit, %cond: i1) -> !quantu
}
return %outc : !quantum.bit
}

// -----

// A nested quantum.adjoint inside a ctrl region is not supported; adjoint-lowering must run first.
func.func @ctrl_nested_adjoint(%ctrl: !quantum.bit, %reg: !quantum.reg) -> !quantum.bit {
%true = arith.constant true
%outc, %outr = quantum.ctrl(%ctrl) ctrlvals(%true) (%reg : !quantum.reg) : !quantum.bit -> !quantum.reg {
^bb0(%arg0: !quantum.reg):
// expected-error @+1 {{nested quantum.adjoint inside a quantum.ctrl region is not supported}}
%a = quantum.adjoint(%arg0) : !quantum.reg {
^bb1(%argr: !quantum.reg):
%q = quantum.extract %argr[ 0] : !quantum.reg -> !quantum.bit
%h = quantum.custom "Hadamard"() %q : !quantum.bit
%r = quantum.insert %argr[ 0], %h : !quantum.reg, !quantum.bit
quantum.yield %r : !quantum.reg
}
quantum.yield %a : !quantum.reg
}
return %outc : !quantum.bit
}
Loading