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 @@ -395,6 +395,7 @@
[(#2983)](https://github.com/PennyLaneAI/catalyst/pull/2983)
[(#3022)](https://github.com/PennyLaneAI/catalyst/pull/3022)
[(#3039)](https://github.com/PennyLaneAI/catalyst/pull/3039)
[(#3046)](https://github.com/PennyLaneAI/catalyst/pull/3046)

* The `graph-decomposition` pass eliminates three redundant IR manipulations:
the cloning, removal, and re-insertion of user rules. This optimization is particularly
Expand Down
31 changes: 31 additions & 0 deletions frontend/test/lit/GraphDecomposition/TestGraphOpId.mlir
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// 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.

// Test that graph-decomposition succeeds when using graphOpIds

// RUN: catalyst --tool=opt --split-input-file --pass-pipeline='builtin.module(graph-decomposition{gate-set=PauliX=1.0 alt-decomps=Hadamard=my_decomp})' %s | FileCheck %s

func.func @circuit(%q: !quantum.bit) -> !quantum.bit {
// CHECK-NOT: Hadamard
// CHECK: PauliX
// CHECK: PauliX
%out = quantum.custom "Hadamard"() %q: !quantum.bit
return %out: !quantum.bit
}

func.func private @my_decomp(%q: !quantum.bit) -> !quantum.bit attributes {target_gate="Hadamard[][1]{}"} {
%q0 = quantum.custom "PauliX"() %q : !quantum.bit
%q1 = quantum.custom "PauliX"() %q0 : !quantum.bit
return %q1 : !quantum.bit
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,23 @@

#include "DGBuilder.hpp"

#include <cstddef>
#include <cstdint>
#include <iostream>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <variant>
#include <vector>

#include "DGUtils.hpp"
#include "boost/graph/adjacency_list.hpp"
#include "boost/graph/detail/adjacency_list.hpp"
#include "boost/graph/graph_selectors.hpp"
#include "boost/graph/graph_traits.hpp"

#include <boost/graph/adjacency_list.hpp>
#include "DGTypes.hpp"
#include "DGUtils.hpp"

using namespace DecompGraph::Core;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

#pragma once

#include <cstddef>
#include <memory>
#include <vector>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@

#include "DGSolver.hpp"

#include <algorithm>
#include <optional>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>

#include "DGTypes.hpp"
#include "DGUtils.hpp"

using namespace DecompGraph::Core;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,12 @@

#pragma once

#include <optional>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include "DGBuilder.hpp"
#include "DGTypes.hpp"
#include "DGUtils.hpp"

namespace DecompGraph::Solver {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,38 +53,16 @@ namespace DecompGraph::Core {
* when adding support for operators with dynamic numbers of wires/params.
*/
struct OperatorNode {
std::string name;
int numWires{-1};
int numParams{-1};
std::string id;
bool adjoint{false};

// Optional static arguments for operators that require additional data.
// optional params, primarily for debug use
std::string name{""};
int numWires{-1};
int numParams{-1};
std::unordered_map<std::string, std::string> staticNamedArgs{};
std::string id{""};

bool operator==(const OperatorNode &other) const {
// id match
if (!id.empty() && !other.id.empty() && id == other.id) {
return true;
}
// legacy fallback if either op is missing ID

// For equality, we consider numWires and numParams conditionally equal
// if they are not set to -1 (which indicates a wildcard that can match any value).
const bool default_wires =
(numWires == -1 || other.numWires == -1 || numWires == other.numWires);
const bool default_params =
(numParams == -1 || other.numParams == -1 || numParams == other.numParams);

// Static arguments are optional: if either side has no static args, they
// are treated as matching (wildcard). When both sides provide entries, the maps must
// be equal element-wise for the operators to be considered equivalent.
const bool static_args_match = staticNamedArgs.empty() || other.staticNamedArgs.empty() ||
staticNamedArgs == other.staticNamedArgs;

return name == other.name && default_wires && default_params && adjoint == other.adjoint &&
static_args_match;
}

bool operator==(const OperatorNode &other) const { return id == other.id; }
bool operator!=(const OperatorNode &other) const { return !(*this == other); }
};

Expand All @@ -105,43 +83,23 @@ struct OperatorNode {
*/
struct OperatorNodeHash {
std::size_t operator()(const OperatorNode &node) const {
// prefer id if available
if (!node.id.empty()) {
return std::hash<std::string>{}(node.id);
}
return std::hash<std::string>{}(node.name);
return std::hash<std::string>{}(node.id);
}
};

/**
* @brief This represents the weighted target gateset for the graph decomposition problem.
*/
struct WeightedGateset {
// TODO: using ID here mandates that gatesets specify all legal IDs, rather than generic class
// like "PauliRot". This should be updated to work on generic names
std::unordered_map<OperatorNode, double, OperatorNodeHash> ops;

[[nodiscard]] bool contains(const OperatorNode &op) const {
// hash match
if (ops.find(op) != ops.end()) {
return true;
}

// use op-matching if hashes failed (could be id vs name)
for (auto [gatesetOp, cost] : ops) {
if (gatesetOp == op) {
return true;
}
}

return false;
}
[[nodiscard]] bool contains(const OperatorNode &op) const { return ops.find(op) != ops.end(); }

[[nodiscard]] double getCost(const OperatorNode &op) const {
auto it = ops.find(op);
if (it != ops.end()) {
return it->second;
}

return std::numeric_limits<double>::infinity();
return it != ops.end() ? it->second : std::numeric_limits<double>::infinity();
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <sstream>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>

#include "DGTypes.hpp"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -413,27 +413,16 @@ struct GraphDecompositionPass : public impl::GraphDecompositionPassBase<GraphDec
// dialect.
// The interface will provide one unified way of generating operator nodes from operations,
// with consistent getter methods for all relevant data fields.
getOperation().walk([&](quantum::QuantumGate op) {
getOperation().walk([&](DecomposableGate op) {
if (DecompUtils::isInDecompRule(op)) {
return;
}
OperatorNode node;
node.numWires = op.getNonCtrlQubitOperands().size();
node.adjoint = op.getAdjointFlag();

if (auto customOp = llvm::dyn_cast<quantum::CustomOp>(op.getOperation())) {
node.name = customOp.getGateName().str();
}
// Name handling for non-custom ops
else {
std::string name = op->getName().stripDialect().str();
if (name == "gphase") {
name = "GlobalPhase";
} else if (name == "paulirot") {
name = cast<DecomposableGate>(op.getOperation()).getGraphOpId();
}
node.name = name;
}
node.name = op.getOperatorName();
node.id = op.getGraphOpId();

if (auto paramOp =
llvm::dyn_cast<catalyst::quantum::ParametrizedGate>(op.getOperation())) {
Expand Down Expand Up @@ -463,6 +452,9 @@ struct GraphDecompositionPass : public impl::GraphDecompositionPassBase<GraphDec
}
node.name = raw.take_front(closeIdx).trim().str();
raw = raw.drop_front(closeIdx + 1); // leftover: "(w,p)" or ""
} else if (raw.contains('[') || raw.contains('{')) {
node.id = raw.str();
node.name = raw.take_until([](char c) { return c == '[' || c == '{'; });
} else {
auto openIdx = raw.find('(');
if (openIdx == llvm::StringRef::npos) {
Expand Down
Loading