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
2 changes: 1 addition & 1 deletion mlir/include/mqt/Compiler/TargetCompilation.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class OpPassManager;
/// Populate the canonical compiler-target pipeline.
///
/// Inlines reusable functions, decomposes supported multi-controlled gates,
/// performs target-independent optimization, maps to the target topology,
/// maps to the target topology, fuses blocks in the target basis,
/// synthesizes native operations, performs a final local cleanup, and verifies
/// target conformance. The context that runs this low-level pipeline must
/// register inliner extensions for its callable dialects.
Expand Down
10 changes: 6 additions & 4 deletions mlir/include/mqt/Dialect/QCO/Transforms/Passes.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

#pragma once

#include "mqt/Compiler/Target.h"

#include "mlir/Interfaces/FunctionInterfaces.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Pass/PassRegistry.h"
Expand All @@ -19,15 +21,15 @@
#include <cstdint>
#include <memory>

namespace mlir {
class CompilerTarget;
} // namespace mlir

namespace mlir::qco {

#define GEN_PASS_DECL
#include "mqt/Dialect/QCO/Transforms/Passes.h.inc" // IWYU pragma: export

/// Fuse single-qubit runs directly in the selected compiler-target basis.
[[nodiscard]] std::unique_ptr<Pass>
createFuseSingleQubitUnitaryRuns(CompilerTarget::SingleQubitBasis basis);

//===----------------------------------------------------------------------===//
// Registration
//===----------------------------------------------------------------------===//
Expand Down
14 changes: 11 additions & 3 deletions mlir/include/mqt/Dialect/QCO/Transforms/Passes.td
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,10 @@ def MergeSingleQubitRotationGates

When every angle in the chain is a compile-time constant, the same algorithm
runs on host scalars and emits constant `U` / `gphase` results directly.
Otherwise it emits `arith` / `math` operations that compute the merged
parameters at run time.
Symbolic RZ-RX-RZ and RZ-RY-RZ chains reuse their Euler angles directly;
either outer RZ may be absent. Their angles remain unwrapped, without
inverse trigonometry or conditional expressions. Other dynamic chains emit
`arith` / `math` operations that compute the merged parameters at run time.

The emitted `UOp` is defined by $U = \exp [i (\phi + \lambda) / 2] R_z (\phi) R_y (\theta) R_z (\lambda)$.
Normalizing either extracted Z angle into $[-\pi, \pi)$ by $\pm 2\pi$ flips
Expand All @@ -56,7 +58,7 @@ def MergeSingleQubitRotationGates
restores exact matrix equality (including global phase). This applies even
to chains of purely $\mathrm{SU}(2)$ gates (`rx`, `ry`, `rz`, `r`).
On the host (static) path, a near-zero correction may be omitted; on the
SSA (dynamic) path a `GPhaseOp` is always emitted. After greedy merging,
SSA (dynamic) path a `GPhaseOp` carries any remaining correction. After greedy merging,
the implementation directly invokes the shared global-phase normalization
utility to combine, normalize, and remove trivial corrections in their
respective scopes.
Expand Down Expand Up @@ -86,6 +88,12 @@ def FuseSingleQubitUnitaryRuns
The pass also composes supported named gates with dynamic `f64` parameters
and emits conservative runtime sequences in the requested basis. Dynamic
`pow` and arbitrary dynamic unitaries remain unchanged.

Symbolic ZXZ/ZYZ chains reuse their angles directly in the `u`, `zyz`,
`zxz`, and `zsxx` bases; XZX/XYX chains do so in `xzx`, `xyx`, and `r`.
Either outer rotation may be absent. These conversions need no inverse
trigonometry or conditional expressions. Other dynamic chains use runtime
Euler-angle extraction.
}];
let options = [Option<
"basis", "basis", "std::string", "\"zyz\"",
Expand Down
20 changes: 16 additions & 4 deletions mlir/lib/Compiler/TargetCompilation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,18 @@ class PrepareTargetCompilationPass

} /* namespace */

static void populatePostPlacementPipeline(OpPassManager& pm) {
static void populatePostPlacementPipeline(OpPassManager& pm,
const CompilerTarget& target) {
/// Placement consumes allocations; native synthesis normalizes phases.
pm.addPass(createCanonicalizerPass(
GreedyRewriteConfig{}.setMaxIterations(GreedyRewriteConfig::kNoLimit)));
/// Reuse unchanged classical reads before native synthesis splits their uses.
pm.addPass(createCSEPass());
pm.addPass(createRemoveDeadValuesPass());
if (const auto basis = target.synthesisBasis();
basis && basis->singleQubit != CompilerTarget::SingleQubitBasis::U) {
pm.addPass(qco::createFuseSingleQubitUnitaryRuns(basis->singleQubit));
}
pm.addPass(qco::createTargetNativeSynthesis());
pm.addPass(createCSEPass());
pm.addPass(qco::createVerifyTargetConformance());
Expand All @@ -105,7 +110,14 @@ void populateTargetCompilationPipeline(OpPassManager& pm,
pm.addPass(qco::createLegalizeControlFlow());
pm.addPass(qco::createDecomposeMultiControlled(target));
pm.addPass(qco::createFuseTwoQubitGates(target));
populateDefaultQCOOptimizationPipeline(pm);
// Non-U targets fuse directly in their basis after placement, avoiding an
// intermediate U representation and its symbolic phase correction.
if (const auto basis = target.synthesisBasis();
!basis || basis->singleQubit == CompilerTarget::SingleQubitBasis::U) {
// Retain the U optimizer's treatment of isolated gates on U-based and
// unrestricted targets.
populateDefaultQCOOptimizationPipeline(pm);
}
switch (target.connectivityKind()) {
case CompilerTarget::Connectivity::Kind::Explicit:
pm.addPass(qco::createMappingPass(qco::MappingPassOptions{}));
Expand All @@ -114,7 +126,7 @@ void populateTargetCompilationPipeline(OpPassManager& pm,
pm.addPass(qco::createPlacementPass(target));
break;
}
populatePostPlacementPipeline(pm);
populatePostPlacementPipeline(pm, target);
}

void populateTargetSynthesisPipeline(OpPassManager& pm,
Expand All @@ -128,7 +140,7 @@ void populateTargetSynthesisPipeline(OpPassManager& pm,
pm.addPass(qco::createDecomposeMultiControlled(target));
pm.addPass(qco::createFuseTwoQubitGates(target));
pm.addPass(qco::createPlacementPass(target));
populatePostPlacementPipeline(pm);
populatePostPlacementPipeline(pm, target);
}

} // namespace mlir
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "mlir/Transforms/GreedyPatternRewriteDriver.h"

#include <cstddef>
#include <memory>
#include <optional>
#include <utility>

Expand Down Expand Up @@ -207,6 +208,38 @@ struct FuseSingleQubitUnitaryRunsPass final

} // namespace

std::unique_ptr<Pass>
createFuseSingleQubitUnitaryRuns(CompilerTarget::SingleQubitBasis basis) {
// Populate the existing option so textual pipelines and reproducers retain
// the basis selected by target compilation.
FuseSingleQubitUnitaryRunsOptions options;
using Basis = CompilerTarget::SingleQubitBasis;
switch (basis) {
case Basis::U:
options.basis = "u";
break;
case Basis::ZSXX:
options.basis = "zsxx";
break;
case Basis::R:
options.basis = "r";
break;
case Basis::XZX:
options.basis = "xzx";
break;
case Basis::XYX:
options.basis = "xyx";
break;
case Basis::ZYZ:
options.basis = "zyz";
break;
case Basis::ZXZ:
options.basis = "zxz";
break;
}
return createFuseSingleQubitUnitaryRuns(options);
}

} // namespace mlir::qco

namespace mlir::qco::decomposition {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1066,6 +1066,73 @@ struct MergeSingleQubitRotationGatesPattern final
return success();
}

/// Reuse Euler angles when the chain and output share their outer axis.
/// Either outer rotation may be absent. Do not add independent, unbounded
/// dynamic angles.
static LogicalResult
tryMergeDirectChain(MutableArrayRef<UnitaryOpInterface> chain,
RewriterBase& rewriter,
decomposition::SingleQubitBasis basis) {
const bool outerX = basis == decomposition::SingleQubitBasis::XZX ||
basis == decomposition::SingleQubitBasis::XYX ||
basis == decomposition::SingleQubitBasis::R;
const auto isOuter = [outerX](UnitaryOpInterface op) {
return outerX ? isa<RXOp>(op.getOperation())
: isa<RZOp>(op.getOperation());
};

const size_t middle = chain.size() > 1 && isOuter(chain.front()) ? 1 : 0;
if (chain.size() <= middle || chain.size() > middle + 2 ||
!isa<RXOp, RYOp, RZOp>(chain[middle].getOperation()) ||
(chain.size() > 1 && isOuter(chain[middle])) ||
(chain.size() == middle + 2 && !isOuter(chain.back()))) {
return failure();
}

// Check the complete run before creating or replacing any operations.
const Location loc = chain.front()->getLoc();
const auto consts = makeConsts<Value>(rewriter, loc);
const auto angle = [&](UnitaryOpInterface op) {
return Val<Value>{
.v = op.getParameter(0),
.rewriter = &rewriter,
.loc = loc,
};
};
RuntimeEulerAngles angles{
.theta = angle(chain[middle]),
.phi = consts.zero,
.lambda = consts.zero,
.phase = consts.zero,
};
if (!outerX) {
angles = directZYZAnglesFromGate(chain[middle], rewriter, consts);
} else if (isOuter(chain[middle])) {
angles.lambda = angles.theta;
angles.theta = consts.zero;
} else if (const bool middleZ = isa<RZOp>(chain[middle].getOperation());
middleZ != (basis == decomposition::SingleQubitBasis::XZX)) {
// RX conjugation exchanges Y and Z, with opposite quarter-turns.
const auto halfPi = consts.pi / consts.two;
angles.phi = middleZ ? halfPi : -halfPi;
angles.lambda = -angles.phi;
}
if (middle == 1) {
angles.lambda = sumAngles(angles.lambda, angle(chain.front()));
}
if (chain.size() == middle + 2) {
angles.phi = sumAngles(angles.phi, angle(chain.back()));
}

for (auto op : llvm::drop_begin(chain)) {
rewriter.replaceOp(op, op.getInputQubit(0));
}
Value qubit = emitRuntimeEulerAngles(
rewriter, loc, chain.front().getInputQubit(0), angles, basis, consts);
rewriter.replaceOp(chain.front(), qubit);
return success();
}

// Merges a dynamic or mixed-angle chain through `Val<Value>` SSA.
//
// Fusion mode emits the requested basis directly. Regular merge mode emits
Expand All @@ -1079,6 +1146,10 @@ struct MergeSingleQubitRotationGatesPattern final
RewriterBase& rewriter,
std::optional<decomposition::SingleQubitBasis> fusionBasis =
std::nullopt) {
const auto basis = fusionBasis.value_or(decomposition::SingleQubitBasis::U);
if (succeeded(tryMergeDirectChain(chain, rewriter, basis))) {
return success();
}
const Location loc = chain.front()->getLoc();
const auto consts = makeConsts<Value>(rewriter, loc);

Expand All @@ -1101,7 +1172,6 @@ struct MergeSingleQubitRotationGatesPattern final
rewriter.replaceOp(chainOp, chainOp.getInputQubit(0));
}

const auto basis = fusionBasis.value_or(decomposition::SingleQubitBasis::U);
const bool transformed = basis == decomposition::SingleQubitBasis::XZX ||
basis == decomposition::SingleQubitBasis::XYX ||
basis == decomposition::SingleQubitBasis::R;
Expand Down
Loading
Loading