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
163 changes: 163 additions & 0 deletions cudaq/include/cudaq/Optimizer/Analysis/CommutationAnalysis.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*******************************************************************************
* Copyright (c) 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. *
******************************************************************************/

#pragma once

#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/StringRef.h"
#include <memory>
#include <utility>

namespace mlir {
class Block;
class Operation;
} // namespace mlir

namespace cudaq::quake::detail {

class QubitIdentityAnalysis;

/// The outcome of a commutation query.
enum class CommutationStatus { Commutes, DoesNotCommute, Indeterminate };

/// The rule or limitation that produced a commutation status.
enum class CommutationReason {
// Reasons paired with CommutationStatus::Commutes.
/// The operations have disjoint block-local quantum support.
DisjointSupport,
/// The recognized operations have the same structural action and placement,
/// optionally with opposite adjoint states.
SameOperation,
/// Both operations are diagonal in the computational basis.
ComputationalDiagonal,
/// Both operations rotate about the same axis. Rotation angles may differ;
/// `PhasedRx` rotation angles may differ, but their axis-defining phase
/// parameters must be the same SSA value or equal constants.
SameAxis,
/// The unitary channel is diagonal in the recognized measurement
/// instrument's basis.
MeasurementInstrumentBasis,
/// The unitary channel preserves the reset channel's output state.
PreservedResetState,
/// Pauli products have even anti-commutation parity on shared targets.
EvenPauliParity,
/// A diagonal operation overlaps the other operation only on controls.
DiagonalOnControls,
/// Controlled operations have commuting target actions and no target-control
/// crossover.
CompatibleControlledTargets,
/// Opposite polarity on a shared control makes the control predicates
/// mutually exclusive.
MutuallyExclusiveControls,

// Reasons paired with CommutationStatus::DoesNotCommute.
/// Exact Pauli operators have odd anti-commutation parity on shared targets.
OddPauliParity,

// Reasons paired with CommutationStatus::Indeterminate.
/// At least one query operation is null.
NullOperation,
/// At least one operation is outside the analyzed block.
DifferentBlocks,
/// At least one operation has no supported analysis operation view.
UnsupportedOperationKind,
/// A quantum operand is not a supported scalar wire value.
UnsupportedQuantumOperandType,
/// A quantum operand has no analysis-local qubit identifier.
UnmappedQubitId,
/// An operation uses the same virtual qubit in more than one control or
/// target position.
DuplicateQubitOperand,
/// An `ExpPauli` word is dynamic.
UnsupportedPauliWord,
/// Supported operations did not satisfy an available structural rule.
NoApplicableRule
};

/// Return the stable textual identifier for a commutation reason.
llvm::StringRef getCommutationReasonId(CommutationReason reason);

/// A structural commutation outcome and its classification.
struct CommutationResult {
CommutationStatus status;
CommutationReason reason;

/// True only when structural analysis proved exact commutation.
explicit operator bool() const {
return status == CommutationStatus::Commutes;
}
};

/// Exact block-local commutation analysis for supported Quake quantum
/// operations.
///
/// A query asks whether two operations have the same induced action when
/// composed in either order. Gates are treated as unitary channels. For
/// supported measurement instruments, equality is required outcome by outcome,
/// preserving each classical outcome and its conditional output state.
///
/// The block must contain valid Quake value-form IR. Candidate operations must
/// implement Quake `OperatorInterface` or be single-target scalar-wire
/// measurement instruments, reset channels, or sinks. Operations on disjoint
/// qubits commute regardless of their supported operation kind. For
/// overlapping qubits, the analysis applies structural rules for recognized
/// built-in Quake operators, matching-basis measurement instruments, and
/// unitary channels that preserve the reset output state. Custom unitaries with
/// the same defining symbol, exact parameters, controls, and targets are also
/// recognized as the same operation. The analysis does not inspect
/// custom-unitary matrices, analyze arbitrary quantum-channel or measurement-
/// instrument representations, or infer overlapping-support semantics from
/// different custom definitions or dynamic Pauli words.
/// Pass pipelines can establish the supported form by running
/// `linear-ctrl-form` after `memtoreg`.
///
/// `DoesNotCommute` is returned only for the limited cases where an available
/// rule proves that the operations do not commute. `Indeterminate` means that
/// the available rules established neither result. It does not imply either
/// commutation or a failure to commute.
///
/// Compiler transformations must treat both `DoesNotCommute` and
/// `Indeterminate` as not safe to reorder. The separate statuses preserve the
/// distinction between a proven failure to commute and the absence of a proof.
///
/// Qubit identity is followed through supported scalar wire operators,
/// measurement instruments, and reset channels, including controls represented
/// as `!quake.wire`. Shared-support non-unitary rules cover only single-target
/// matching-basis measurement instruments and reset-channel relations with
/// unitary channels; sinks and pairs of non-unitary operations remain
/// indeterminate. The analysis does not follow identity through reusable
/// `!quake.control`, `quake.to_ctrl`, `quake.from_ctrl`, calls, references,
/// aggregates, or unsupported non-unitary quantum operations. Each wire block
/// argument establishes a local identity that is not correlated with values on
/// predecessor edges.
///
/// Any mutation of the block invalidates the analysis instance. The caller
/// must discard it before querying the changed block.
class CommutationAnalysis {
public:
explicit CommutationAnalysis(mlir::Block &block);
~CommutationAnalysis();

CommutationAnalysis(const CommutationAnalysis &) = delete;
CommutationAnalysis &operator=(const CommutationAnalysis &) = delete;

/// Return the detailed symmetric relation between two operations.
CommutationResult getResult(mlir::Operation *lhs, mlir::Operation *rhs);

/// Return true only when exact commutation has been proven.
bool canCommute(mlir::Operation *lhs, mlir::Operation *rhs);

private:
mlir::Block *block;
std::unique_ptr<QubitIdentityAnalysis> qubitIdentity;
llvm::DenseMap<std::pair<mlir::Operation *, mlir::Operation *>,
CommutationResult>
cache;
};

} // namespace cudaq::quake::detail
35 changes: 31 additions & 4 deletions cudaq/include/cudaq/Optimizer/Dialect/Quake/QuakeOps.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@ inline bool isQuakeOperation(mlir::Operation *op) {
}

namespace cudaq::quake {
namespace detail {
/// Ordered scalar-wire inputs and the results that carry the same qubits.
struct ScalarWireFlow {
mlir::SmallVector<mlir::Value> inputs;
mlir::SmallVector<mlir::Value> results;
};

/// Return the one-to-one scalar-wire flow for an effect-free operator,
/// measurement, or reset without regions. Unsupported forms and mismatched
/// input and result shapes return no value.
std::optional<ScalarWireFlow> getScalarWireFlow(mlir::Operation *operation);
} // namespace detail

/// Returns true if and only if any quantum operand has type `!quake.ref` or
/// `!quake.veq`.
inline bool hasReference(mlir::Operation *op) {
Expand All @@ -94,14 +107,28 @@ inline std::optional<std::size_t> getVeqSize(mlir::Value v) {
return veqTy.getSize();
if (auto relaxOp = v.getDefiningOp<cudaq::quake::RelaxSizeOp>()) {
// RelaxSizeOp verifier guarantees input is VeqType when result is VeqType.
auto innerTy =
mlir::cast<cudaq::quake::VeqType>(relaxOp.getInputVec().getType());
if (innerTy.hasSpecifiedSize())
return innerTy.getSize();
return getVeqSize(relaxOp.getInputVec());
}
return std::nullopt;
}

/// Returns an operator's wire operands: its wire controls, then its wire
/// targets. In value form an operator returns one wire result per wire operand
/// in this same order, so `getWireOperands(op)[i]` and `op.getWires()[i]` are
/// the same qubit before and after \p op. An operator in memory form names its
/// qubits by reference instead, so both lists come back empty.
inline mlir::SmallVector<mlir::Value>
getWireOperands(cudaq::quake::OperatorInterface op) {
mlir::SmallVector<mlir::Value> wires;
for (mlir::Value control : op.getControls())
if (isa<cudaq::quake::WireType>(control.getType()))
wires.push_back(control);
for (mlir::Value target : op.getTargets())
if (isa<cudaq::quake::WireType>(target.getType()))
wires.push_back(target);
return wires;
}

/// Returns true if and only if any quantum operand has type `!quake.ref`.
inline bool hasNonVectorReference(mlir::Operation *op) {
for (mlir::Value opnd : op->getOperands())
Expand Down
22 changes: 17 additions & 5 deletions cudaq/include/cudaq/Optimizer/Dialect/Quake/QuakeTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@

#pragma once

#include "cudaq/Optimizer/Dialect/Traits.h"
#include "cudaq/Support/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "mlir/IR/BuiltinAttributes.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/Types.h"
#include <optional>

//===----------------------------------------------------------------------===//
// Generated logic
Expand All @@ -21,6 +24,15 @@
#include "cudaq/Optimizer/Dialect/Quake/QuakeTypes.h.inc"

namespace cudaq::quake {
/// A single factor in a Pauli tensor product.
enum class Pauli { I, X, Y, Z };

/// An ordered Pauli tensor-product word.
using PauliWord = llvm::SmallVector<Pauli>;

/// Convert a word containing only `I`, `X`, `Y`, and `Z` to Pauli symbols.
std::optional<PauliWord> symbolizePauliWord(llvm::StringRef value);

/// \returns true if \p `ty` is a quantum value or reference.
inline bool isQuantumType(mlir::Type ty) {
// NB: this intentionally excludes MeasureType.
Expand Down Expand Up @@ -59,14 +71,14 @@ inline bool isQuantumValueType(mlir::Type ty) {
/// number of quantum references.
bool isConstantQuantumRefType(mlir::Type ty);

/// Get the number of qubits represented by \p ty when it is statically known.
/// \p ty must be a quantum type.
std::optional<std::size_t> getQubitCount(mlir::Type ty);

/// Get the number of references in \p ty. \p ty must be a reference type.
std::size_t getAllocationSize(mlir::Type ty);

/// Get the number of wires in \p ty. \p ty must be a value type.
inline std::size_t getWireCount(mlir::Type ty) {
if (isa<cudaq::quake::WireType, cudaq::quake::ControlType>(ty))
return 1;
return cast<cudaq::quake::CableType>(ty).getSize();
}
std::size_t getWireCount(mlir::Type ty);

} // namespace cudaq::quake
2 changes: 2 additions & 0 deletions cudaq/lib/Optimizer/Analysis/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
# ============================================================================ #

add_cudaq_library(OptAnalysis
CommutationAnalysis.cpp
QubitIdentityAnalysis.cpp
UnitaryOpGrouping.cpp

DEPENDS
Expand Down
Loading
Loading