diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 380f2cae90..b7439cf829 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -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 diff --git a/frontend/test/lit/GraphDecomposition/TestGraphOpId.mlir b/frontend/test/lit/GraphDecomposition/TestGraphOpId.mlir new file mode 100644 index 0000000000..8536a5fa85 --- /dev/null +++ b/frontend/test/lit/GraphDecomposition/TestGraphOpId.mlir @@ -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 +} diff --git a/mlir/lib/Quantum/Transforms/GraphDecomposition/DecompGraphSolver/DGBuilder.cpp b/mlir/lib/Quantum/Transforms/GraphDecomposition/DecompGraphSolver/DGBuilder.cpp index 6d08d20761..f3ba519770 100644 --- a/mlir/lib/Quantum/Transforms/GraphDecomposition/DecompGraphSolver/DGBuilder.cpp +++ b/mlir/lib/Quantum/Transforms/GraphDecomposition/DecompGraphSolver/DGBuilder.cpp @@ -18,13 +18,23 @@ #include "DGBuilder.hpp" +#include #include #include +#include +#include +#include +#include #include +#include -#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 +#include "DGTypes.hpp" +#include "DGUtils.hpp" using namespace DecompGraph::Core; diff --git a/mlir/lib/Quantum/Transforms/GraphDecomposition/DecompGraphSolver/DGBuilder.hpp b/mlir/lib/Quantum/Transforms/GraphDecomposition/DecompGraphSolver/DGBuilder.hpp index 984dd6a970..56fae1952b 100644 --- a/mlir/lib/Quantum/Transforms/GraphDecomposition/DecompGraphSolver/DGBuilder.hpp +++ b/mlir/lib/Quantum/Transforms/GraphDecomposition/DecompGraphSolver/DGBuilder.hpp @@ -24,6 +24,7 @@ #pragma once +#include #include #include diff --git a/mlir/lib/Quantum/Transforms/GraphDecomposition/DecompGraphSolver/DGSolver.cpp b/mlir/lib/Quantum/Transforms/GraphDecomposition/DecompGraphSolver/DGSolver.cpp index 4198fdd7e8..864449a65f 100644 --- a/mlir/lib/Quantum/Transforms/GraphDecomposition/DecompGraphSolver/DGSolver.cpp +++ b/mlir/lib/Quantum/Transforms/GraphDecomposition/DecompGraphSolver/DGSolver.cpp @@ -18,12 +18,14 @@ #include "DGSolver.hpp" -#include #include +#include #include +#include #include #include "DGTypes.hpp" +#include "DGUtils.hpp" using namespace DecompGraph::Core; diff --git a/mlir/lib/Quantum/Transforms/GraphDecomposition/DecompGraphSolver/DGSolver.hpp b/mlir/lib/Quantum/Transforms/GraphDecomposition/DecompGraphSolver/DGSolver.hpp index 65c92f7911..6f0e7c5896 100644 --- a/mlir/lib/Quantum/Transforms/GraphDecomposition/DecompGraphSolver/DGSolver.hpp +++ b/mlir/lib/Quantum/Transforms/GraphDecomposition/DecompGraphSolver/DGSolver.hpp @@ -25,14 +25,12 @@ #pragma once -#include #include #include #include #include "DGBuilder.hpp" #include "DGTypes.hpp" -#include "DGUtils.hpp" namespace DecompGraph::Solver { diff --git a/mlir/lib/Quantum/Transforms/GraphDecomposition/DecompGraphSolver/DGTypes.hpp b/mlir/lib/Quantum/Transforms/GraphDecomposition/DecompGraphSolver/DGTypes.hpp index c2738e7921..a7ac7e9080 100644 --- a/mlir/lib/Quantum/Transforms/GraphDecomposition/DecompGraphSolver/DGTypes.hpp +++ b/mlir/lib/Quantum/Transforms/GraphDecomposition/DecompGraphSolver/DGTypes.hpp @@ -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 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); } }; @@ -105,11 +83,7 @@ struct OperatorNode { */ struct OperatorNodeHash { std::size_t operator()(const OperatorNode &node) const { - // prefer id if available - if (!node.id.empty()) { - return std::hash{}(node.id); - } - return std::hash{}(node.name); + return std::hash{}(node.id); } }; @@ -117,31 +91,15 @@ struct OperatorNodeHash { * @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 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::infinity(); + return it != ops.end() ? it->second : std::numeric_limits::infinity(); } }; diff --git a/mlir/lib/Quantum/Transforms/GraphDecomposition/DecompGraphSolver/DGUtils.hpp b/mlir/lib/Quantum/Transforms/GraphDecomposition/DecompGraphSolver/DGUtils.hpp index 4bbf4bc938..272a38e4b4 100644 --- a/mlir/lib/Quantum/Transforms/GraphDecomposition/DecompGraphSolver/DGUtils.hpp +++ b/mlir/lib/Quantum/Transforms/GraphDecomposition/DecompGraphSolver/DGUtils.hpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include "DGTypes.hpp" diff --git a/mlir/lib/Quantum/Transforms/GraphDecomposition/graph_decomposition.cpp b/mlir/lib/Quantum/Transforms/GraphDecomposition/graph_decomposition.cpp index b837a94b0a..d6bb63a5aa 100644 --- a/mlir/lib/Quantum/Transforms/GraphDecomposition/graph_decomposition.cpp +++ b/mlir/lib/Quantum/Transforms/GraphDecomposition/graph_decomposition.cpp @@ -413,7 +413,7 @@ struct GraphDecompositionPass : public impl::GraphDecompositionPassBase(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(op.getOperation()).getGraphOpId(); - } - node.name = name; - } + node.name = op.getOperatorName(); + node.id = op.getGraphOpId(); if (auto paramOp = llvm::dyn_cast(op.getOperation())) { @@ -463,6 +452,9 @@ struct GraphDecompositionPass : public impl::GraphDecompositionPassBase + #include "DGTypes.hpp" #include @@ -23,73 +25,56 @@ using namespace Catch::Matchers; using namespace DecompGraph::Core; TEST_CASE("Test OperatorNode construction", "[DecompGraph::Core]") { - const OperatorNode op1{"H", 1, 0, false}; - const OperatorNode op2{"CNOT", 2, 0, false}; - const OperatorNode op3{"RX", 1, 1, false}; - const OperatorNode op4{"RZ", 1, 1, true}; - - REQUIRE(op1.name == "H"); - REQUIRE(op1.numWires == 1); - REQUIRE(op1.numParams == 0); - REQUIRE(op1.adjoint == false); - - REQUIRE(op2.name == "CNOT"); - REQUIRE(op2.numWires == 2); - REQUIRE(op2.numParams == 0); - REQUIRE(op2.adjoint == false); - - REQUIRE(op3.name == "RX"); - REQUIRE(op3.numWires == 1); - REQUIRE(op3.numParams == 1); - REQUIRE(op3.adjoint == false); - - REQUIRE(op4.name == "RZ"); - REQUIRE(op4.numWires == 1); - REQUIRE(op4.numParams == 1); - REQUIRE(op4.adjoint == true); + const OperatorNode h{"Hadamard[][1]{}"}; + const OperatorNode cnot{"CNOT[][2]{}"}; + const OperatorNode rx{"RX[f64][1]{}"}; + const OperatorNode rz{"RZ[f64][1]{}"}; + + REQUIRE(h.id == "Hadamard[][1]{}"); + REQUIRE(cnot.id == "CNOT[][2]{}"); + REQUIRE(rx.id == "RX[f64][1]{}"); + REQUIRE(rz.id == "RZ[f64][1]{}"); } TEST_CASE("Test OperatorNode equality operator", "[DecompGraph::Core]") { - const OperatorNode op1{"H", 1, 0, false}; - const OperatorNode op2{"H", 1, 0, false}; - const OperatorNode op3{"H", 1, 0, true}; - const OperatorNode op4{"CNOT", 2, 0, false}; - - REQUIRE(op1 == op2); - REQUIRE_FALSE(op1 == op3); - REQUIRE_FALSE(op1 == op4); + const OperatorNode h1{"Hadamard[][1]{}"}; + const OperatorNode h2{"Hadamard[][1]{}"}; + const OperatorNode cnot{"CNOT[][2]{}"}; + + REQUIRE(h1 == h2); + REQUIRE(h1 != cnot); } TEST_CASE("Test OperatorNodeHash", "[DecompGraph::Core]") { - const OperatorNode op1{"H", 1, 0, false}; - const OperatorNode op2{"H", 1, 0, false}; - const OperatorNode op3{"H", 1, 0, true}; - const OperatorNode op4{"CNOT", 2, 0, false}; + const OperatorNode h1{"Hadamard[][1]{}"}; + const OperatorNode h2{"Hadamard[][1]{}"}; + const OperatorNode h3{"Hadamard[][1]{}"}; + const OperatorNode cnot{"CNOT[][2]{}"}; const OperatorNodeHash hashFunc; - REQUIRE(hashFunc(op1) == hashFunc(op2)); - REQUIRE(hashFunc(op1) == hashFunc(op3)); - REQUIRE(hashFunc(op1) != hashFunc(op4)); + REQUIRE(hashFunc(h1) == hashFunc(h2)); + REQUIRE(hashFunc(h1) == hashFunc(h3)); + REQUIRE(hashFunc(h1) != hashFunc(cnot)); } TEST_CASE("Test OperatorNode in unordered_map", "[DecompGraph::Core]") { std::unordered_map opMap; - const OperatorNode op1{"H", 1, 0, false}; - const OperatorNode op2{"CNOT", 2, 0, false}; - const OperatorNode op3{"RX", 1, 1, false}; + const OperatorNode h{"Hadamard[][1]{}"}; + const OperatorNode cnot{"CNOT[][2]{}"}; + const OperatorNode rx{"RX[f64][1]{}"}; - opMap[op1] = 1.0; - opMap[op2] = 2.0; + opMap[h] = 1.0; + opMap[cnot] = 2.0; - REQUIRE(opMap[op1] == 1.0); - REQUIRE(opMap[op2] == 2.0); - REQUIRE(opMap.find(op3) == opMap.end()); + REQUIRE(opMap[h] == 1.0); + REQUIRE(opMap[cnot] == 2.0); + REQUIRE(opMap.find(rx) == opMap.end()); } TEST_CASE("Test RuleNode construction", "[DecompGraph::Core]") { - const auto h = OperatorNode{"H"}; - const auto rz = OperatorNode{"RZ"}; - const auto rx = OperatorNode{"RX"}; + const OperatorNode h{"Hadamard[][1]{}"}; + const OperatorNode rx{"RX[f64][1]{}"}; + const OperatorNode rz{"RZ[f64][1]{}"}; const RuleNode h_to_rz_rx_rz{"h_to_rz_rx_rz", h, {{rz, 2}, {rx, 1}}}; REQUIRE(h_to_rz_rx_rz.name == "h_to_rz_rx_rz"); @@ -102,9 +87,9 @@ TEST_CASE("Test RuleNode construction", "[DecompGraph::Core]") { } TEST_CASE("Test WeightedGateset construction and contains", "[DecompGraph::Core]") { - const OperatorNode h{"H"}; - const OperatorNode cnot{"CNOT"}; - const OperatorNode rx{"RX"}; + const OperatorNode h{"Hadamard[][1]{}"}; + const OperatorNode cnot{"CNOT[][2]{}"}; + const OperatorNode rx{"RX[f64][1]{}"}; const WeightedGateset gateset{{{h, 1.0}, {cnot, 2.0}}}; @@ -117,9 +102,9 @@ TEST_CASE("Test WeightedGateset construction and contains", "[DecompGraph::Core] } TEST_CASE("Test ChosenDecompRule construction", "[DecompGraph::Core]") { - const OperatorNode h{"H"}; - const OperatorNode rz{"RZ"}; - const OperatorNode rx{"RX"}; + const OperatorNode h{"Hadamard[][1]{}"}; + const OperatorNode rx{"RX[f64][1]{}"}; + const OperatorNode rz{"RZ[f64][1]{}"}; const RuleTerm term1{rz, 2}; const RuleTerm term2{rx, 1}; @@ -139,44 +124,3 @@ TEST_CASE("Test ChosenDecompRule construction", "[DecompGraph::Core]") { REQUIRE(chosenRule.basisCounts[rz] == 2); REQUIRE(chosenRule.basisCounts[rx] == 1); } - -TEST_CASE("Test graphOpId Support", "[DecompGraph::Core]") { - // comparing an op without an ID should fallback to legacy match - const OperatorNode h{"H"}; - const OperatorNode hId{"H", -1, -1, false, {}, "H[][1]{}"}; - - REQUIRE(h == hId); - - // id + legacy match with non-wildcard params - const OperatorNode x{"X", 1, 0}; - const OperatorNode xId{"X", -1, -1, false, {}, "X[][1]{}"}; - - REQUIRE(x == xId); - - // id nodes should fail legacy match if params differ - const OperatorNode op1{"op", 1, 1}; - const OperatorNode op2{"op", 2, 2, false, {}, "op[f64,f64][2]{}"}; - - REQUIRE_FALSE(op1 == op2); - - // nodes with same ids should match - const OperatorNode pr1{"PauliRot", -1, -1, false, {}, "PauliRot[f64][2]{pauli_word:XX}"}; - const OperatorNode pr2{"PauliRot", -1, -1, false, {}, "PauliRot[f64][2]{pauli_word:XX}"}; - - REQUIRE(pr1 == pr2); - - // nodes with matching ids should ignore other parameters (id is source of truth) - const OperatorNode id1{"name1", 1, 1, false, {}, "sameID"}; - const OperatorNode id2{"name2", 2, 2, true, {}, "sameID"}; - - REQUIRE(id1 == id2); - - // Unit test for `OperatorNodeHash`. Check that the hash function prefers ID over name - const OperatorNode hash1{"name", -1, -1, false, {}, "id"}; - const OperatorNode hash2{"name2", 1, 1, true, {}, "id"}; - const OperatorNode hash3{"name2", 1, 1, true, {}}; - - const OperatorNodeHash hashFunc; - REQUIRE(hashFunc(hash1) == hashFunc(hash2)); - REQUIRE(hashFunc(hash2) != hashFunc(hash3)); -} diff --git a/mlir/unittests/DecompGraphSolver/Test_DecompGraphSolver.cpp b/mlir/unittests/DecompGraphSolver/Test_DecompGraphSolver.cpp index 32ca27474c..975d2532ed 100644 --- a/mlir/unittests/DecompGraphSolver/Test_DecompGraphSolver.cpp +++ b/mlir/unittests/DecompGraphSolver/Test_DecompGraphSolver.cpp @@ -13,7 +13,9 @@ // limitations under the License. #include -#include +#include +#include +#include #include "DGBuilder.hpp" #include "DGSolver.hpp" @@ -30,9 +32,9 @@ using namespace DecompGraph::Core; using namespace DecompGraph::Solver; TEST_CASE("Test DecompositionGraph construction", "[DecompGraph::Solver]") { - const auto h = OperatorNode{"H", 1, 0, false}; - const auto rz = OperatorNode{"RZ", 1, 1, false}; - const auto rx = OperatorNode{"RX", 1, 1, false}; + const auto h = OperatorNode{"H[][1]{}"}; + const auto rz = OperatorNode{"RZ[f64][1]{}"}; + const auto rx = OperatorNode{"RX[f64][1]{}"}; const WeightedGateset gateset{{{rz, 1.0}, {rx, 2.0}}}; @@ -64,8 +66,8 @@ TEST_CASE("Test DecompositionGraph construction", "[DecompGraph::Solver]") { TEST_CASE("Test DecompositionSolver solve method with incomplete gates in Gateset", "[DecompGraph::Solver]") { - const auto h = OperatorNode{"H", 1, 0, false}; - const auto h_gateset = OperatorNode{"H"}; + const auto h = OperatorNode{"H[][1]{}"}; + const auto h_gateset = OperatorNode{"H[][1]{}"}; const WeightedGateset gateset{{{h_gateset, 1.0}}}; const std::vector rules{ {"h_to_h", h, {{h, 1}}}, @@ -88,8 +90,8 @@ TEST_CASE("Test DecompositionSolver solve method with incomplete gates in Gatese } TEST_CASE("Do not solve for target gates", "[DecompGraph::Solver]") { - const auto h = OperatorNode{"H", 1, 0, false}; - const auto rz = OperatorNode{"RZ", 1, 1, false}; + const auto h = OperatorNode{"H[][1]{}"}; + const auto rz = OperatorNode{"RZ[f64][1]{}"}; const WeightedGateset gateset{{{h, 2.0}, {rz, 1.0}}}; @@ -108,9 +110,9 @@ TEST_CASE("Do not solve for target gates", "[DecompGraph::Solver]") { } TEST_CASE("Test DecompositionGraph copy and move semantics", "[DecompGraph::Solver]") { - const auto h = OperatorNode{"H", 1, 0, false}; - const auto rz = OperatorNode{"RZ", 1, 1, false}; - const auto rx = OperatorNode{"RX", 1, 1, false}; + const auto h = OperatorNode{"H[][1]{}"}; + const auto rz = OperatorNode{"RZ[f64][1]{}"}; + const auto rx = OperatorNode{"RX[f64][1]{}"}; const WeightedGateset gateset{{{rz, 1.0}, {rx, 2.0}}}; @@ -150,10 +152,10 @@ TEST_CASE("Test DecompositionGraph copy and move semantics", "[DecompGraph::Solv } TEST_CASE("Test DecompositionGraph lookup and counting", "[DecompGraph::Solver]") { - const OperatorNode h{"H", 1, 0, false}; - const OperatorNode rz{"RZ", 1, 1, false}; - const OperatorNode rx{"RX", 1, 1, false}; - const OperatorNode ry{"RY", 1, 1, false}; + const OperatorNode h{"H[][1]{}"}; + const OperatorNode rz{"RZ[f64][1]{}"}; + const OperatorNode rx{"RX[f64][1]{}"}; + const OperatorNode ry{"RY[f64][1]{}"}; const WeightedGateset gateset{{{rz, 1.0}, {ry, 2.0}, {rx, 3.0}}}; @@ -186,13 +188,13 @@ TEST_CASE("Test DecompositionGraph lookup and counting", "[DecompGraph::Solver]" TEST_CASE("Test the graph construction with realistic ops and multiple rules from PennyLane", "[DecompGraph::Solver]") { - const OperatorNode h{"H", 1, 0, false}; - const OperatorNode rz{"RZ", 1, 1, false}; - const OperatorNode rx{"RX", 1, 1, false}; - const OperatorNode ry{"RY", 1, 1, false}; - const OperatorNode cnot{"CNOT", 2, 0, false}; - const OperatorNode swap{"SWAP", 2, 0, false}; - const OperatorNode customBellOp{"BellOp", 2, 0, false}; + const OperatorNode h{"H[][1]{}"}; + const OperatorNode rz{"RZ[f64][1]{}"}; + const OperatorNode rx{"RX[f64][1]{}"}; + const OperatorNode ry{"RY[f64][1]{}"}; + const OperatorNode cnot{"CNOT[][2]{}"}; + const OperatorNode swap{"SWAP[][2]{}"}; + const OperatorNode customBellOp{"BellOp[][2]{}"}; const WeightedGateset gateset{{{rz, 1.0}, {rx, 3.0}, {cnot, 5.0}}}; @@ -211,10 +213,10 @@ TEST_CASE("Test the graph construction with realistic ops and multiple rules fro } TEST_CASE("Test DecompositionSolver with one single operator", "[DecompGraph::Solver]") { - const OperatorNode h{"H", 1, 0, false}; - const OperatorNode rz{"RZ", 1, 1, false}; - const OperatorNode rx{"RX", 1, 1, false}; - const OperatorNode ry{"RY", 1, 1, false}; + const OperatorNode h{"H[][1]{}"}; + const OperatorNode rz{"RZ[f64][1]{}"}; + const OperatorNode rx{"RX[f64][1]{}"}; + const OperatorNode ry{"RY[f64][1]{}"}; const WeightedGateset gateset{{{rz, 1.0}, {ry, 2.0}, {rx, 3.0}}}; @@ -250,13 +252,13 @@ TEST_CASE("Test DecompositionSolver with one single operator", "[DecompGraph::So TEST_CASE("Test the graph solver with intermediate ops and multiple rules", "[DecompGraph::Solver]") { - const OperatorNode h{"H", 1, 0, false}; - const OperatorNode rz{"RZ", 1, 1, false}; - const OperatorNode rx{"RX", 1, 1, false}; - const OperatorNode ry{"RY", 1, 1, false}; - const OperatorNode cnot{"CNOT", 2, 0, false}; - const OperatorNode swap{"SWAP", 2, 0, false}; - const OperatorNode customBellOp{"BellOp", 2, 0, false}; + const OperatorNode h{"H[][1]{}"}; + const OperatorNode rz{"RZ[f64][1]{}"}; + const OperatorNode rx{"RX[f64][1]{}"}; + const OperatorNode ry{"RY[f64][1]{}"}; + const OperatorNode cnot{"CNOT[][2]{}"}; + const OperatorNode swap{"SWAP[][2]{}"}; + const OperatorNode customBellOp{"BellOp[][2]{}"}; const WeightedGateset gateset{{{rz, 1.0}, {rx, 3.0}, {cnot, 5.0}}}; @@ -303,8 +305,8 @@ TEST_CASE("Test the graph solver with intermediate ops and multiple rules", } TEST_CASE("Test GraphSolveError for unsolvable operator", "[DecompGraph::Solver]") { - const OperatorNode h{"H", 1, 0, false}; - const OperatorNode rz{"RZ", 1, 1, false}; + const OperatorNode h{"H[][1]{}"}; + const OperatorNode rz{"RZ[f64][1]{}"}; const WeightedGateset gateset{{{rz, 1.0}}}; @@ -319,7 +321,7 @@ TEST_CASE("Test GraphSolveError for unsolvable operator", "[DecompGraph::Solver] } TEST_CASE("Test GraphSolveError for cyclic decomposition", "[DecompGraph::Solver]") { - const OperatorNode h{"H", 1, 0, false}; + const OperatorNode h{"H[][1]{}"}; const WeightedGateset gateset{}; @@ -334,9 +336,9 @@ TEST_CASE("Test GraphSolveError for cyclic decomposition", "[DecompGraph::Solver } TEST_CASE("Test PauliX -> GlobalPhase(1), RX(1) decomposition", "[DecompGraph::Solver]") { - const OperatorNode x{"X"}; - const OperatorNode globalPhase{"GlobalPhase"}; - const OperatorNode rx{"RX"}; + const OperatorNode x{"X[][1]{}"}; + const OperatorNode globalPhase{"GlobalPhase[][]{}"}; + const OperatorNode rx{"RX[f64][1]{}"}; const WeightedGateset gateset{{{globalPhase, 1.0}, {rx, 1.0}}}; @@ -363,14 +365,14 @@ TEST_CASE("Test PauliX -> GlobalPhase(1), RX(1) decomposition", "[DecompGraph::S TEST_CASE("Test cyclic decomposition with multiple rules for the same operator", "[DecompGraph::Solver]") { - const OperatorNode hadamard{"Hadamard"}; - const OperatorNode globalPhase{"GlobalPhase"}; - const OperatorNode rx{"RX"}; - const OperatorNode rz{"RZ"}; - const OperatorNode ry{"RY"}; - const OperatorNode changeOpBasis{"ChangeOpBasis"}; - const OperatorNode pauliRot{"PauliRot"}; - const OperatorNode rot{"Rot"}; + const OperatorNode hadamard{"Hadamard[][1]{}"}; + const OperatorNode globalPhase{"GlobalPhase[][]{}"}; + const OperatorNode rx{"RX[f64][1]{}"}; + const OperatorNode rz{"RZ[f64][1]{}"}; + const OperatorNode ry{"RY[f64][1]{}"}; + const OperatorNode changeOpBasis{"ChangeOpBasis[][2]{}"}; + const OperatorNode pauliRot{"PauliRot[f64][2]{pauli_word:XY}"}; + const OperatorNode rot{"Rot[f64,f64,f64][3]{}"}; const std::vector rules{ {"__builtin__ry_to_rz_cliff", ry, {{changeOpBasis, 1}}}, @@ -400,9 +402,9 @@ TEST_CASE("Test cyclic decomposition with multiple rules for the same operator", } TEST_CASE("Test GraphBuilder with fixed decomposition", "[DecompGraph::Solver]") { - const OperatorNode h{"H"}; - const OperatorNode rz{"RZ"}; - const OperatorNode rx{"RX"}; + const OperatorNode h{"H[][1]{}"}; + const OperatorNode rz{"RZ[f64][1]{}"}; + const OperatorNode rx{"RX[f64][1]{}"}; const WeightedGateset gateset{{{rz, 1.0}, {rx, 3.0}}}; @@ -421,9 +423,9 @@ TEST_CASE("Test GraphBuilder with fixed decomposition", "[DecompGraph::Solver]") } TEST_CASE("Test GraphBuilder with alternative decomposition", "[DecompGraph::Solver]") { - const OperatorNode h{"H"}; - const OperatorNode rz{"RZ"}; - const OperatorNode rx{"RX"}; + const OperatorNode h{"H[][1]{}"}; + const OperatorNode rz{"RZ[f64][1]{}"}; + const OperatorNode rx{"RX[f64][1]{}"}; const WeightedGateset gateset{{{rz, 1.0}, {rx, 3.0}}}; @@ -440,9 +442,9 @@ TEST_CASE("Test GraphBuilder with alternative decomposition", "[DecompGraph::Sol } TEST_CASE("Test GraphSolver with fixed decomposition", "[DecompGraph::Solver]") { - const OperatorNode h{"H"}; - const OperatorNode rz{"RZ"}; - const OperatorNode rx{"RX"}; + const OperatorNode h{"H[][1]{}"}; + const OperatorNode rz{"RZ[f64][1]{}"}; + const OperatorNode rx{"RX[f64][1]{}"}; const WeightedGateset gateset{{{rz, 3.0}, {rx, 1.0}}}; @@ -465,9 +467,9 @@ TEST_CASE("Test GraphSolver with fixed decomposition", "[DecompGraph::Solver]") } TEST_CASE("Test GraphSolver with alternative decomposition", "[DecompGraph::Solver]") { - const OperatorNode h{"H"}; - const OperatorNode rz{"RZ"}; - const OperatorNode rx{"RX"}; + const OperatorNode h{"H[][1]{}"}; + const OperatorNode rz{"RZ[f64][1]{}"}; + const OperatorNode rx{"RX[f64][1]{}"}; const WeightedGateset gateset{{{rz, 1.0}, {rx, 3.0}}}; @@ -489,9 +491,9 @@ TEST_CASE("Test GraphSolver with alternative decomposition", "[DecompGraph::Solv } TEST_CASE("Test GraphSolver with MultiRZ decompositions", "[DecompGraph::Solver]") { - const OperatorNode multiRZ3{"MultiRZ3"}; - const OperatorNode multiRZ5{"MultiRZ5"}; - const OperatorNode rz{"RZ"}; + const OperatorNode multiRZ3{"MultiRZ[f64][3]{}"}; + const OperatorNode multiRZ5{"MultiRZ[f64][5]{}"}; + const OperatorNode rz{"RZ[f64][1]{}"}; const WeightedGateset gateset{{{rz, 1.0}}}; @@ -515,8 +517,8 @@ TEST_CASE("Test GraphSolver with MultiRZ decompositions", "[DecompGraph::Solver] } TEST_CASE("Test GraphSolver with empty decomposition rules", "[DecompGraph::Solver]") { - const OperatorNode hadamard{"Hadamard"}; - const OperatorNode globalPhase{"GlobalPhase"}; + const OperatorNode hadamard{"Hadamard[][1]{}"}; + const OperatorNode globalPhase{"GlobalPhase[][]{}"}; const WeightedGateset gateset{{{globalPhase, 1.0}}}; @@ -535,45 +537,11 @@ TEST_CASE("Test GraphSolver with empty decomposition rules", "[DecompGraph::Solv REQUIRE(chosen_rule.totalCost == 0.0); } -TEST_CASE("Test GraphSolver with PauliRot specialized by static argument pauli_word", - "[DecompGraph::Solver]") { - // Query: PauliRot[w:1][p:1][pauli_word:X] should match a rule whose output is - // PauliRot[w:-1][p:-1][pauli_word:X] (wildcards on wires/params, exact match on pauli_word). - const OperatorNode pauliRotQuery{"PauliRot", 1, 1, false, {{"pauli_word", "X"}}}; - const OperatorNode pauliRotRuleOutput{"PauliRot", -1, -1, false, {{"pauli_word", "X"}}}; - const OperatorNode hadamard{"Hadamard", 1, 0, false}; - const OperatorNode multiRZ{"MultiRZ", 1, 1, false}; - - const WeightedGateset gateset{{{hadamard, 1.0}, {multiRZ, 1.0}}}; - - const std::vector rules{ - {"_pauli_rot_decomposition_X", pauliRotRuleOutput, {{hadamard, 2}, {multiRZ, 1}}}, - }; - - const DecompositionGraph graph({pauliRotQuery}, gateset, rules); - DecompositionSolver solver(graph); - const auto result = solver.solve(); - - REQUIRE(result.find(pauliRotQuery) != result.end()); - const auto &chosen = result.at(pauliRotQuery); - REQUIRE_FALSE(chosen.isBasis); - REQUIRE(chosen.ruleName == "_pauli_rot_decomposition_X"); - REQUIRE(chosen.totalCost == 1.0 * 2 + 1.0 * 1); - REQUIRE(chosen.basisCounts.at(hadamard) == 2); - REQUIRE(chosen.basisCounts.at(multiRZ) == 1); - - const OperatorNode pauliRotQueryY{"PauliRot", 1, 1, false, {{"pauli_word", "Y"}}}; - REQUIRE_FALSE(pauliRotQuery == pauliRotQueryY); - REQUIRE(pauliRotQuery == pauliRotRuleOutput); -} - TEST_CASE("Test OperatorNode equality with staticNamedArgs", "[DecompGraph::Core]") { - const OperatorNode pauliRotX{"PauliRot", 1, 1, false, {{"pauli_word", "X"}}}; - const OperatorNode pauliRotXWildcard{"PauliRot", -1, -1, false, {{"pauli_word", "X"}}}; - const OperatorNode pauliRotY{"PauliRot", 1, 1, false, {{"pauli_word", "Y"}}}; - const OperatorNode pauliRotNoArgs{"PauliRot", 1, 1, false}; + const OperatorNode pauliRotX{"PauliRot[f64][1]{pauli_word:X}"}; + const OperatorNode pauliRotX2{"PauliRot[f64][1]{pauli_word:X}"}; + const OperatorNode pauliRotY{"PauliRot[f64][1]{pauli_word:Y}"}; - REQUIRE(pauliRotX == pauliRotXWildcard); + REQUIRE(pauliRotX == pauliRotX2); REQUIRE_FALSE(pauliRotX == pauliRotY); - REQUIRE(pauliRotX == pauliRotNoArgs); }