diff --git a/CHANGELOG.md b/CHANGELOG.md index c2b5e5bc5d..6dee39ba2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Added +- ✨ Add native support for `R(theta, phi)` gate ([#1283]) ([**@burgholzer**]) - ✨ Add A\*-search-based routing algorithm to MLIR transpilation routines ([#1237], [#1271], [#1279]) ([**@MatthiasReumann**]) ### Fixed @@ -220,6 +221,7 @@ _📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-tool +[#1283]: https://github.com/munich-quantum-toolkit/core/pull/1283 [#1279]: https://github.com/munich-quantum-toolkit/core/pull/1279 [#1276]: https://github.com/munich-quantum-toolkit/core/pull/1276 [#1271]: https://github.com/munich-quantum-toolkit/core/pull/1271 diff --git a/bindings/ir/operations/register_optype.cpp b/bindings/ir/operations/register_optype.cpp index d721bc95d4..3cc10f3e48 100644 --- a/bindings/ir/operations/register_optype.cpp +++ b/bindings/ir/operations/register_optype.cpp @@ -48,6 +48,7 @@ void registerOptype(const py::module& m) { .value("rx", qc::OpType::RX) .value("ry", qc::OpType::RY) .value("rz", qc::OpType::RZ) + .value("r", qc::OpType::R) .value("swap", qc::OpType::SWAP) .value("iswap", qc::OpType::iSWAP) .value("iswapdg", qc::OpType::iSWAPdg) diff --git a/bindings/ir/register_quantum_computation.cpp b/bindings/ir/register_quantum_computation.cpp index 4f40331e78..45a2c6e30d 100644 --- a/bindings/ir/register_quantum_computation.cpp +++ b/bindings/ir/register_quantum_computation.cpp @@ -345,6 +345,7 @@ void registerQuantumComputation(py::module& m) { py::arg(#param1), "controls"_a, "target"_a); DEFINE_SINGLE_TARGET_TWO_PARAMETER_OPERATION(u2, phi, lambda_) + DEFINE_SINGLE_TARGET_TWO_PARAMETER_OPERATION(r, theta, phi) #define DEFINE_SINGLE_TARGET_THREE_PARAMETER_OPERATION(op, param0, param1, \ param2) \ diff --git a/include/mqt-core/ir/QuantumComputation.hpp b/include/mqt-core/ir/QuantumComputation.hpp index 34eeccc583..3833beac7e 100644 --- a/include/mqt-core/ir/QuantumComputation.hpp +++ b/include/mqt-core/ir/QuantumComputation.hpp @@ -252,6 +252,7 @@ class QuantumComputation { const Controls& controls, const Qubit target); DECLARE_SINGLE_TARGET_TWO_PARAMETER_OPERATION(u2, phi, lambda) + DECLARE_SINGLE_TARGET_TWO_PARAMETER_OPERATION(r, theta, phi) #undef DECLARE_SINGLE_TARGET_TWO_PARAMETER_OPERATION diff --git a/include/mqt-core/ir/operations/OpType.hpp b/include/mqt-core/ir/operations/OpType.hpp index 30ff8f98ab..2bdeda0294 100644 --- a/include/mqt-core/ir/operations/OpType.hpp +++ b/include/mqt-core/ir/operations/OpType.hpp @@ -14,6 +14,7 @@ #include #include #include +#include namespace qc { @@ -90,6 +91,7 @@ std::string shortName(OpType opType); case RX: case RY: case RZ: + case R: return true; default: return false; @@ -100,7 +102,7 @@ inline std::ostream& operator<<(std::ostream& out, const OpType opType) { return out << toString(opType); } -[[nodiscard]] OpType opTypeFromString(const std::string& opType); +[[nodiscard]] OpType opTypeFromString(std::string_view opType); inline std::istream& operator>>(std::istream& in, OpType& opType) { std::string opTypeStr; diff --git a/include/mqt-core/ir/operations/OpType.inc b/include/mqt-core/ir/operations/OpType.inc index 2ed153d417..b06ed03e4f 100644 --- a/include/mqt-core/ir/operations/OpType.inc +++ b/include/mqt-core/ir/operations/OpType.inc @@ -32,8 +32,9 @@ HANDLE_OP_TYPE(14, SXdg, OpTypeInv, "sxdg") HANDLE_OP_TYPE(15, RX, OpTypeNone, "rx") HANDLE_OP_TYPE(16, RY, OpTypeNone, "ry") HANDLE_OP_TYPE(17, RZ, OpTypeDiag, "rz") +HANDLE_OP_TYPE(41, R, OpTypeNone, "r") HANDLE_OP_TYPE(18, SWAP, OpTypeNone, "swap") -HANDLE_OP_TYPE(19, iSWAP, OpTypeNone, "iswap") +HANDLE_OP_TYPE(19, iSWAP, OpTypeNone, "iswap") HANDLE_OP_TYPE(19, iSWAPdg, OpTypeInv, "iswapdg") HANDLE_OP_TYPE(20, Peres, OpTypeNone, "peres") HANDLE_OP_TYPE(20, Peresdg, OpTypeInv, "peresdg") @@ -68,7 +69,7 @@ HANDLE_OP_TYPE(38, AodActivate, 0, "aod_activate") HANDLE_OP_TYPE(39, AodDeactivate, 0, "aod_deactivate") HANDLE_OP_TYPE(40, AodMove, 0, "aod_move") -LAST_OP_TYPE(41) +LAST_OP_TYPE(42) #undef OpTypeInv diff --git a/include/mqt-core/qasm3/StdGates.hpp b/include/mqt-core/qasm3/StdGates.hpp index 211f16aeb9..92fecf0b7b 100644 --- a/include/mqt-core/qasm3/StdGates.hpp +++ b/include/mqt-core/qasm3/StdGates.hpp @@ -49,75 +49,184 @@ const std::string QE1LIB = "gate rccx a, b, c {\n" const std::map> STANDARD_GATES = { // gates from which all other gates can be constructed. {"gphase", - std::make_shared(StandardGate({0, 0, 1, qc::GPhase}))}, - {"U", std::make_shared(StandardGate({0, 1, 3, qc::U}))}, + std::make_shared(StandardGate({.nControls = 0, + .nTargets = 0, + .nParameters = 1, + .type = qc::GPhase}))}, + {"U", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 1, .nParameters = 3, .type = qc::U}))}, // natively supported gates - {"p", std::make_shared(StandardGate({0, 1, 1, qc::P}))}, - {"u1", std::make_shared(StandardGate({0, 1, 1, qc::P}))}, - {"phase", std::make_shared(StandardGate({0, 1, 1, qc::P}))}, - {"cphase", std::make_shared(StandardGate({1, 1, 1, qc::P}))}, - {"cp", std::make_shared(StandardGate({1, 1, 1, qc::P}))}, - - {"id", std::make_shared(StandardGate({0, 1, 0, qc::I}))}, - {"u2", std::make_shared(StandardGate({0, 1, 2, qc::U2}))}, - {"u3", std::make_shared(StandardGate({0, 1, 3, qc::U}))}, - {"u", std::make_shared(StandardGate({0, 1, 3, qc::U}))}, - - {"x", std::make_shared(StandardGate({0, 1, 0, qc::X}))}, - {"cx", std::make_shared(StandardGate({1, 1, 0, qc::X}))}, - {"CX", std::make_shared(StandardGate({1, 1, 0, qc::X}))}, - {"ccx", std::make_shared(StandardGate({2, 1, 0, qc::X}))}, - {"c3x", std::make_shared(StandardGate({3, 1, 0, qc::X}))}, - {"c4x", std::make_shared(StandardGate({4, 1, 0, qc::X}))}, - - {"rx", std::make_shared(StandardGate({0, 1, 1, qc::RX}))}, - {"crx", std::make_shared(StandardGate({1, 1, 1, qc::RX}))}, - - {"y", std::make_shared(StandardGate({0, 1, 0, qc::Y}))}, - {"cy", std::make_shared(StandardGate({1, 1, 0, qc::Y}))}, - - {"ry", std::make_shared(StandardGate({0, 1, 1, qc::RY}))}, - {"cry", std::make_shared(StandardGate({1, 1, 1, qc::RY}))}, - - {"z", std::make_shared(StandardGate({0, 1, 0, qc::Z}))}, - {"cz", std::make_shared(StandardGate({1, 1, 0, qc::Z}))}, - - {"rz", std::make_shared(StandardGate({0, 1, 1, qc::RZ}))}, - {"crz", std::make_shared(StandardGate({1, 1, 1, qc::RZ}))}, - - {"h", std::make_shared(StandardGate({0, 1, 0, qc::H}))}, - {"ch", std::make_shared(StandardGate({1, 1, 0, qc::H}))}, - - {"s", std::make_shared(StandardGate({0, 1, 0, qc::S}))}, - {"sdg", std::make_shared(StandardGate({0, 1, 0, qc::Sdg}))}, - - {"t", std::make_shared(StandardGate({0, 1, 0, qc::T}))}, - {"tdg", std::make_shared(StandardGate({0, 1, 0, qc::Tdg}))}, - - {"sx", std::make_shared(StandardGate({0, 1, 0, qc::SX}))}, - {"sxdg", std::make_shared(StandardGate({0, 1, 0, qc::SXdg}))}, + {"p", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 1, .nParameters = 1, .type = qc::P}))}, + {"u1", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 1, .nParameters = 1, .type = qc::P}))}, + {"phase", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 1, .nParameters = 1, .type = qc::P}))}, + {"cphase", + std::make_shared(StandardGate( + {.nControls = 1, .nTargets = 1, .nParameters = 1, .type = qc::P}))}, + {"cp", + std::make_shared(StandardGate( + {.nControls = 1, .nTargets = 1, .nParameters = 1, .type = qc::P}))}, + + {"id", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 1, .nParameters = 0, .type = qc::I}))}, + {"u2", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 1, .nParameters = 2, .type = qc::U2}))}, + {"u3", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 1, .nParameters = 3, .type = qc::U}))}, + {"u", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 1, .nParameters = 3, .type = qc::U}))}, + + {"x", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 1, .nParameters = 0, .type = qc::X}))}, + {"cx", + std::make_shared(StandardGate( + {.nControls = 1, .nTargets = 1, .nParameters = 0, .type = qc::X}))}, + {"CX", + std::make_shared(StandardGate( + {.nControls = 1, .nTargets = 1, .nParameters = 0, .type = qc::X}))}, + {"ccx", + std::make_shared(StandardGate( + {.nControls = 2, .nTargets = 1, .nParameters = 0, .type = qc::X}))}, + {"c3x", + std::make_shared(StandardGate( + {.nControls = 3, .nTargets = 1, .nParameters = 0, .type = qc::X}))}, + {"c4x", + std::make_shared(StandardGate( + {.nControls = 4, .nTargets = 1, .nParameters = 0, .type = qc::X}))}, + + {"rx", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 1, .nParameters = 1, .type = qc::RX}))}, + {"crx", + std::make_shared(StandardGate( + {.nControls = 1, .nTargets = 1, .nParameters = 1, .type = qc::RX}))}, + + {"y", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 1, .nParameters = 0, .type = qc::Y}))}, + {"cy", + std::make_shared(StandardGate( + {.nControls = 1, .nTargets = 1, .nParameters = 0, .type = qc::Y}))}, + + {"ry", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 1, .nParameters = 1, .type = qc::RY}))}, + {"cry", + std::make_shared(StandardGate( + {.nControls = 1, .nTargets = 1, .nParameters = 1, .type = qc::RY}))}, + + {"z", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 1, .nParameters = 0, .type = qc::Z}))}, + {"cz", + std::make_shared(StandardGate( + {.nControls = 1, .nTargets = 1, .nParameters = 0, .type = qc::Z}))}, + + {"rz", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 1, .nParameters = 1, .type = qc::RZ}))}, + {"crz", + std::make_shared(StandardGate( + {.nControls = 1, .nTargets = 1, .nParameters = 1, .type = qc::RZ}))}, + + {"r", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 1, .nParameters = 2, .type = qc::R}))}, + {"prx", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 1, .nParameters = 2, .type = qc::R}))}, + {"cr", + std::make_shared(StandardGate( + {.nControls = 1, .nTargets = 1, .nParameters = 2, .type = qc::R}))}, + + {"h", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 1, .nParameters = 0, .type = qc::H}))}, + {"ch", + std::make_shared(StandardGate( + {.nControls = 1, .nTargets = 1, .nParameters = 0, .type = qc::H}))}, + + {"s", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 1, .nParameters = 0, .type = qc::S}))}, + {"sdg", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 1, .nParameters = 0, .type = qc::Sdg}))}, + + {"t", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 1, .nParameters = 0, .type = qc::T}))}, + {"tdg", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 1, .nParameters = 0, .type = qc::Tdg}))}, + + {"sx", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 1, .nParameters = 0, .type = qc::SX}))}, + {"sxdg", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 1, .nParameters = 0, .type = qc::SXdg}))}, {"c3sqrtx", - std::make_shared(StandardGate({3, 1, 0, qc::SXdg}))}, + std::make_shared(StandardGate( + {.nControls = 3, .nTargets = 1, .nParameters = 0, .type = qc::SXdg}))}, - {"swap", std::make_shared(StandardGate({0, 2, 0, qc::SWAP}))}, + {"swap", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 2, .nParameters = 0, .type = qc::SWAP}))}, {"cswap", - std::make_shared(StandardGate({1, 2, 0, qc::SWAP}))}, + std::make_shared(StandardGate( + {.nControls = 1, .nTargets = 2, .nParameters = 0, .type = qc::SWAP}))}, {"iswap", - std::make_shared(StandardGate({0, 2, 0, qc::iSWAP}))}, + std::make_shared(StandardGate({.nControls = 0, + .nTargets = 2, + .nParameters = 0, + .type = qc::iSWAP}))}, {"iswapdg", - std::make_shared(StandardGate({0, 2, 0, qc::iSWAPdg}))}, - - {"rxx", std::make_shared(StandardGate({0, 2, 1, qc::RXX}))}, - {"ryy", std::make_shared(StandardGate({0, 2, 1, qc::RYY}))}, - {"rzz", std::make_shared(StandardGate({0, 2, 1, qc::RZZ}))}, - {"rzx", std::make_shared(StandardGate({0, 2, 1, qc::RZX}))}, - {"dcx", std::make_shared(StandardGate({0, 2, 0, qc::DCX}))}, - {"ecr", std::make_shared(StandardGate({0, 2, 0, qc::ECR}))}, + std::make_shared(StandardGate({.nControls = 0, + .nTargets = 2, + .nParameters = 0, + .type = qc::iSWAPdg}))}, + + {"rxx", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 2, .nParameters = 1, .type = qc::RXX}))}, + {"ryy", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 2, .nParameters = 1, .type = qc::RYY}))}, + {"rzz", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 2, .nParameters = 1, .type = qc::RZZ}))}, + {"rzx", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 2, .nParameters = 1, .type = qc::RZX}))}, + {"dcx", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 2, .nParameters = 0, .type = qc::DCX}))}, + {"ecr", + std::make_shared(StandardGate( + {.nControls = 0, .nTargets = 2, .nParameters = 0, .type = qc::ECR}))}, {"xx_minus_yy", - std::make_shared(StandardGate({0, 2, 2, qc::XXminusYY}))}, + std::make_shared(StandardGate({.nControls = 0, + .nTargets = 2, + .nParameters = 2, + .type = qc::XXminusYY}))}, {"xx_plus_yy", - std::make_shared(StandardGate({0, 2, 2, qc::XXplusYY}))}, + std::make_shared(StandardGate({.nControls = 0, + .nTargets = 2, + .nParameters = 2, + .type = qc::XXplusYY}))}, }; } // namespace qasm3 diff --git a/include/mqt-core/qir/runtime/QIR.h b/include/mqt-core/qir/runtime/QIR.h index f1ae383368..f67818d681 100644 --- a/include/mqt-core/qir/runtime/QIR.h +++ b/include/mqt-core/qir/runtime/QIR.h @@ -116,6 +116,8 @@ void __quantum__qis__sqrtx__body(Qubit*); void __quantum__qis__sqrtxdg__body(Qubit*); void __quantum__qis__t__body(Qubit*); void __quantum__qis__tdg__body(Qubit*); +void __quantum__qis__r__body(Qubit*, double, double); +void __quantum__qis__prx__body(Qubit*, double, double); void __quantum__qis__rx__body(Qubit*, double); void __quantum__qis__ry__body(Qubit*, double); void __quantum__qis__rz__body(Qubit*, double); diff --git a/mlir/include/mlir/Dialect/Common/IR/StdOps.td.inc b/mlir/include/mlir/Dialect/Common/IR/StdOps.td.inc index 675c4b9929..c1d9f4ac90 100644 --- a/mlir/include/mlir/Dialect/Common/IR/StdOps.td.inc +++ b/mlir/include/mlir/Dialect/Common/IR/StdOps.td.inc @@ -178,6 +178,16 @@ def SXdgOp : UnitaryOp<"sxdg", [OneTarget, NoParameter]> { }]; } +def ROp : UnitaryOp<"r", [OneTarget, TwoParameters]> { + let summary = "R operation"; + + let description = [{ + This class represents a R gate. It takes a qubit and a variadic + list of positive/negative controls as an input. Additionally, it accepts + two parameters indicating the degree of the rotation angles. + }]; +} + def RXOp : UnitaryOp<"rx", [OneTarget, OneParameter]> { let summary = "RX operation"; diff --git a/mlir/lib/Conversion/MQTOptToMQTRef/MQTOptToMQTRef.cpp b/mlir/lib/Conversion/MQTOptToMQTRef/MQTOptToMQTRef.cpp index f0845315c5..28a8ed910f 100644 --- a/mlir/lib/Conversion/MQTOptToMQTRef/MQTOptToMQTRef.cpp +++ b/mlir/lib/Conversion/MQTOptToMQTRef/MQTOptToMQTRef.cpp @@ -322,6 +322,7 @@ struct MQTOptToMQTRef final : impl::MQTOptToMQTRefBase { ADD_CONVERT_PATTERN(POp) ADD_CONVERT_PATTERN(SXOp) ADD_CONVERT_PATTERN(SXdgOp) + ADD_CONVERT_PATTERN(ROp) ADD_CONVERT_PATTERN(RXOp) ADD_CONVERT_PATTERN(RYOp) ADD_CONVERT_PATTERN(RZOp) diff --git a/mlir/lib/Conversion/MQTRefToMQTOpt/MQTRefToMQTOpt.cpp b/mlir/lib/Conversion/MQTRefToMQTOpt/MQTRefToMQTOpt.cpp index 4818e16b1f..e7903e7096 100644 --- a/mlir/lib/Conversion/MQTRefToMQTOpt/MQTRefToMQTOpt.cpp +++ b/mlir/lib/Conversion/MQTRefToMQTOpt/MQTRefToMQTOpt.cpp @@ -434,6 +434,7 @@ struct MQTRefToMQTOpt final : impl::MQTRefToMQTOptBase { ADD_CONVERT_PATTERN(POp) ADD_CONVERT_PATTERN(SXOp) ADD_CONVERT_PATTERN(SXdgOp) + ADD_CONVERT_PATTERN(ROp) ADD_CONVERT_PATTERN(RXOp) ADD_CONVERT_PATTERN(RYOp) ADD_CONVERT_PATTERN(RZOp) diff --git a/mlir/lib/Conversion/MQTRefToQIR/MQTRefToQIR.cpp b/mlir/lib/Conversion/MQTRefToQIR/MQTRefToQIR.cpp index 0973760ef8..81b9811e88 100644 --- a/mlir/lib/Conversion/MQTRefToQIR/MQTRefToQIR.cpp +++ b/mlir/lib/Conversion/MQTRefToQIR/MQTRefToQIR.cpp @@ -986,6 +986,7 @@ struct MQTRefToQIR final : impl::MQTRefToQIRBase { ADD_CONVERT_PATTERN(POp) ADD_CONVERT_PATTERN(SXOp) ADD_CONVERT_PATTERN(SXdgOp) + ADD_CONVERT_PATTERN(ROp) ADD_CONVERT_PATTERN(RXOp) ADD_CONVERT_PATTERN(RYOp) ADD_CONVERT_PATTERN(RZOp) diff --git a/mlir/lib/Conversion/QIRToMQTRef/QIRToMQTRef.cpp b/mlir/lib/Conversion/QIRToMQTRef/QIRToMQTRef.cpp index ddd74c28f5..2d224786f3 100644 --- a/mlir/lib/Conversion/QIRToMQTRef/QIRToMQTRef.cpp +++ b/mlir/lib/Conversion/QIRToMQTRef/QIRToMQTRef.cpp @@ -126,6 +126,7 @@ class StatefulOpConversionPattern : public OpConversionPattern { {"rzz", "RZZOp"}, {"rzx", "RZXOp"}, {"u1", "POp"}}; inline static const llvm::StringMap DOUBLE_ROTATION_GATES = { + {"r", "ROp"}, {"u2", "U2Op"}, {"xx_minus_yy", "XXminusYYOp"}, {"xx_plus_yy", "XXplusYYOp"}}; @@ -349,6 +350,7 @@ struct ConvertQIRCall final : StatefulOpConversionPattern { ADD_CONVERT_ROTATION_GATE(POp) ADD_CONVERT_ROTATION_GATE(UOp) ADD_CONVERT_ROTATION_GATE(U2Op) + ADD_CONVERT_ROTATION_GATE(ROp) ADD_CONVERT_ROTATION_GATE(RXOp) ADD_CONVERT_ROTATION_GATE(RYOp) ADD_CONVERT_ROTATION_GATE(RZOp) diff --git a/mlir/lib/Dialect/MQTRef/Translation/ImportQuantumComputation.cpp b/mlir/lib/Dialect/MQTRef/Translation/ImportQuantumComputation.cpp index ff058e4da6..5e6f16a982 100644 --- a/mlir/lib/Dialect/MQTRef/Translation/ImportQuantumComputation.cpp +++ b/mlir/lib/Dialect/MQTRef/Translation/ImportQuantumComputation.cpp @@ -513,6 +513,7 @@ llvm::LogicalResult addOperation(mlir::OpBuilder& builder, ADD_OP_CASE(P) ADD_OP_CASE(SX) ADD_OP_CASE(SXdg) + ADD_OP_CASE(R) ADD_OP_CASE(RX) ADD_OP_CASE(RY) ADD_OP_CASE(RZ) diff --git a/mlir/test/Conversion/mqtopt-to-mqtref.mlir b/mlir/test/Conversion/mqtopt-to-mqtref.mlir index c35507ad04..37b9b4bc9a 100644 --- a/mlir/test/Conversion/mqtopt-to-mqtref.mlir +++ b/mlir/test/Conversion/mqtopt-to-mqtref.mlir @@ -292,6 +292,7 @@ module { // CHECK: mqtref.rx(%[[c_0]]) %[[q_0]] // CHECK: mqtref.ry(%[[c_0]]) %[[q_0]] // CHECK: mqtref.rz(%[[c_0]]) %[[q_0]] + // CHECK: mqtref.r(%[[c_0]], %[[c_0]]) %[[q_0]] %i0 = arith.constant 0 : index %qreg = memref.alloc() : memref<1x!mqtopt.Qubit> @@ -304,8 +305,9 @@ module { %q4 = mqtopt.rx(%cst) %q3 : !mqtopt.Qubit %q5 = mqtopt.ry(%cst) %q4 : !mqtopt.Qubit %q6 = mqtopt.rz(%cst) %q5 : !mqtopt.Qubit + %q7 = mqtopt.r(%cst, %cst) %q6 : !mqtopt.Qubit - memref.store %q6, %qreg[%i0] : memref<1x!mqtopt.Qubit> + memref.store %q7, %qreg[%i0] : memref<1x!mqtopt.Qubit> memref.dealloc %qreg : memref<1x!mqtopt.Qubit> return diff --git a/mlir/test/Conversion/mqtref-to-mqtopt.mlir b/mlir/test/Conversion/mqtref-to-mqtopt.mlir index 6a08681c16..70007cbe7d 100644 --- a/mlir/test/Conversion/mqtref-to-mqtopt.mlir +++ b/mlir/test/Conversion/mqtref-to-mqtopt.mlir @@ -310,6 +310,7 @@ module { // CHECK: %[[q_3:.*]] = mqtopt.rx(%[[c_0]]) %[[q_2]] : !mqtopt.Qubit // CHECK: %[[q_4:.*]] = mqtopt.ry(%[[c_0]]) %[[q_3]] : !mqtopt.Qubit // CHECK: %[[q_5:.*]] = mqtopt.rz(%[[c_0]]) %[[q_4]] : !mqtopt.Qubit + // CHECK: %[[q_6:.*]] = mqtopt.r(%[[c_0]], %[[c_0]]) %[[q_5]] : !mqtopt.Qubit %cst = arith.constant 3.000000e-01 : f64 @@ -323,6 +324,7 @@ module { mqtref.rx(%cst) %q0 mqtref.ry(%cst) %q0 mqtref.rz(%cst) %q0 + mqtref.r(%cst, %cst) %q0 return } diff --git a/mlir/test/Conversion/mqtref-to-qir.mlir b/mlir/test/Conversion/mqtref-to-qir.mlir index bc095be493..57477e0bbc 100644 --- a/mlir/test/Conversion/mqtref-to-qir.mlir +++ b/mlir/test/Conversion/mqtref-to-qir.mlir @@ -457,6 +457,7 @@ module { // CHECK: llvm.call @__quantum__qis__rx__body(%[[q_0]], %[[c_0]]) : (!llvm.ptr, f64) -> () // CHECK: llvm.call @__quantum__qis__ry__body(%[[q_0]], %[[c_0]]) : (!llvm.ptr, f64) -> () // CHECK: llvm.call @__quantum__qis__rz__body(%[[q_0]], %[[c_0]]) : (!llvm.ptr, f64) -> () + // CHECK: llvm.call @__quantum__qis__r__body(%[[q_0:.*]], %[[c_0]], %[[c_1]]) : (!llvm.ptr, f64, f64) -> () %c0 = arith.constant 3.000000e-01 : f64 %c1 = arith.constant 1.000000e-01 : f64 @@ -468,6 +469,7 @@ module { mqtref.rx(%c0) %q0 mqtref.ry(%c0) %q0 mqtref.rz(%c0) %q0 + mqtref.r(%c0, %c1) %q0 return } } @@ -486,6 +488,7 @@ module { // CHECK: llvm.call @__quantum__qis__rx__body(%[[q_0]], %[[c_0]]) : (!llvm.ptr, f64) -> () // CHECK: llvm.call @__quantum__qis__ry__body(%[[q_0]], %[[c_0]]) : (!llvm.ptr, f64) -> () // CHECK: llvm.call @__quantum__qis__rz__body(%[[q_0]], %[[c_0]]) : (!llvm.ptr, f64) -> () + // CHECK: llvm.call @__quantum__qis__r__body(%[[q_0:.*]], %[[c_0]], %[[c_1]]) : (!llvm.ptr, f64, f64) -> () %c0 = arith.constant 3.000000e-01 : f64 %c1 = arith.constant 1.000000e-01 : f64 @@ -497,6 +500,7 @@ module { mqtref.rx(%c0) %q0 mqtref.ry(%c0) %q0 mqtref.rz(%c0) %q0 + mqtref.r(%c0, %c1) %q0 return } } diff --git a/mlir/test/Conversion/qir-to-mqtref.mlir b/mlir/test/Conversion/qir-to-mqtref.mlir index ba99d5df4c..eac76e4e9e 100644 --- a/mlir/test/Conversion/qir-to-mqtref.mlir +++ b/mlir/test/Conversion/qir-to-mqtref.mlir @@ -572,6 +572,7 @@ module { // CHECK: mqtref.rx(%[[c_0]]) %[[q_0]] // CHECK: mqtref.ry(%[[c_0]]) %[[q_0]] // CHECK: mqtref.rz(%[[c_0]]) %[[q_0]] + // CHECK: mqtref.r(%[[c_0]], %[[c_1]]) %[[q_0]] %0 = llvm.mlir.zero : !llvm.ptr %c2 = llvm.mlir.constant(1.000000e-01 : f64) : f64 @@ -588,6 +589,7 @@ module { llvm.call @__quantum__qis__rx__body(%q0, %c2) : (!llvm.ptr, f64) -> () llvm.call @__quantum__qis__ry__body(%q0, %c2) : (!llvm.ptr, f64) -> () llvm.call @__quantum__qis__rz__body(%q0, %c2) : (!llvm.ptr, f64) -> () + llvm.call @__quantum__qis__r__body(%q0, %c2, %c3) : (!llvm.ptr, f64, f64) -> () llvm.br ^bb2 ^bb2: llvm.call @__quantum__rt__qubit_release(%q0) : (!llvm.ptr) -> () @@ -602,6 +604,7 @@ module { llvm.func @__quantum__qis__rx__body(!llvm.ptr, f64) llvm.func @__quantum__qis__ry__body(!llvm.ptr, f64) llvm.func @__quantum__qis__rz__body(!llvm.ptr, f64) + llvm.func @__quantum__qis__r__body(!llvm.ptr, f64, f64) llvm.func @__quantum__rt__initialize(!llvm.ptr) llvm.func @__quantum__rt__qubit_allocate() -> !llvm.ptr llvm.func @__quantum__rt__qubit_release(!llvm.ptr) -> () @@ -623,6 +626,7 @@ module { // CHECK: mqtref.rx(%[[c_0]]) %[[q_0]] // CHECK: mqtref.ry(%[[c_0]]) %[[q_0]] // CHECK: mqtref.rz(%[[c_0]]) %[[q_0]] + // CHECK: mqtref.r(%[[c_0]], %[[c_1]]) %[[q_0]] %0 = llvm.mlir.zero : !llvm.ptr %c2 = llvm.mlir.constant(1.000000e-01 : f64) : f64 @@ -638,6 +642,7 @@ module { llvm.call @__quantum__qis__rx__body(%0, %c2) : (!llvm.ptr, f64) -> () llvm.call @__quantum__qis__ry__body(%0, %c2) : (!llvm.ptr, f64) -> () llvm.call @__quantum__qis__rz__body(%0, %c2) : (!llvm.ptr, f64) -> () + llvm.call @__quantum__qis__r__body(%0, %c2, %c3) : (!llvm.ptr, f64, f64) -> () llvm.br ^bb2 ^bb2: @@ -652,6 +657,7 @@ module { llvm.func @__quantum__qis__rx__body(!llvm.ptr, f64) llvm.func @__quantum__qis__ry__body(!llvm.ptr, f64) llvm.func @__quantum__qis__rz__body(!llvm.ptr, f64) + llvm.func @__quantum__qis__r__body(!llvm.ptr, f64, f64) llvm.func @__quantum__rt__initialize(!llvm.ptr) } diff --git a/mlir/test/Dialect/MQTOpt/IR/dialect_features.mlir b/mlir/test/Dialect/MQTOpt/IR/dialect_features.mlir index f5900991af..0010a38822 100644 --- a/mlir/test/Dialect/MQTOpt/IR/dialect_features.mlir +++ b/mlir/test/Dialect/MQTOpt/IR/dialect_features.mlir @@ -408,6 +408,7 @@ module { // CHECK: %[[Q_4:.*]] = mqtopt.rx(%[[C0_F64]]) %[[Q_3]] : !mqtopt.Qubit // CHECK: %[[Q_5:.*]] = mqtopt.ry(%[[C0_F64]]) %[[Q_4]] : !mqtopt.Qubit // CHECK: %[[Q_6:.*]] = mqtopt.rz(%[[C0_F64]]) %[[Q_5]] : !mqtopt.Qubit + // CHECK: %[[Q_7:.*]] = mqtopt.r(%[[C0_F64]], %[[C0_F64]] static [] mask [false, false]) %[[Q_6]] : !mqtopt.Qubit %i0 = arith.constant 0 : index %qreg = memref.alloc() : memref<1x!mqtopt.Qubit> @@ -420,8 +421,9 @@ module { %q_4 = mqtopt.rx(%c0_f64) %q_3 : !mqtopt.Qubit %q_5 = mqtopt.ry(%c0_f64) %q_4 : !mqtopt.Qubit %q_6 = mqtopt.rz(%c0_f64) %q_5 : !mqtopt.Qubit + %q_7 = mqtopt.r(%c0_f64, %c0_f64 static [] mask [false, false]) %q_6 : !mqtopt.Qubit - memref.store %q_6, %qreg[%i0] : memref<1x!mqtopt.Qubit> + memref.store %q_7, %qreg[%i0] : memref<1x!mqtopt.Qubit> memref.dealloc %qreg : memref<1x!mqtopt.Qubit> return @@ -440,6 +442,7 @@ module { // CHECK: %[[Q_4:.*]] = mqtopt.rx(%[[C0_F64]]) %[[Q_3]] : !mqtopt.Qubit // CHECK: %[[Q_5:.*]] = mqtopt.ry(%[[C0_F64]]) %[[Q_4]] : !mqtopt.Qubit // CHECK: %[[Q_6:.*]] = mqtopt.rz(%[[C0_F64]]) %[[Q_5]] : !mqtopt.Qubit + // CHECK: %[[Q_7:.*]] = mqtopt.r(%[[C0_F64]], %[[C0_F64]] static [] mask [false, false]) %[[Q_6]] : !mqtopt.Qubit %q_0 = mqtopt.qubit 0 @@ -450,6 +453,7 @@ module { %q_4 = mqtopt.rx(%c0_f64) %q_3 : !mqtopt.Qubit %q_5 = mqtopt.ry(%c0_f64) %q_4 : !mqtopt.Qubit %q_6 = mqtopt.rz(%c0_f64) %q_5 : !mqtopt.Qubit + %q_7 = mqtopt.r(%c0_f64, %c0_f64 static [] mask [false, false]) %q_6 : !mqtopt.Qubit return } @@ -468,6 +472,7 @@ module { // CHECK: %[[Q0_4:.*]], %[[Q1_4:.*]] = mqtopt.rx(%[[C0_F64]]) %[[Q0_3]] ctrl %[[Q1_3]] : !mqtopt.Qubit ctrl !mqtopt.Qubit // CHECK: %[[Q0_5:.*]], %[[Q1_5:.*]] = mqtopt.ry(%[[C0_F64]]) %[[Q0_4]] ctrl %[[Q1_4]] : !mqtopt.Qubit ctrl !mqtopt.Qubit // CHECK: %[[Q0_6:.*]], %[[Q1_6:.*]] = mqtopt.rz(%[[C0_F64]]) %[[Q0_5]] ctrl %[[Q1_5]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: %[[Q0_7:.*]], %[[Q1_7:.*]] = mqtopt.r(%[[C0_F64]], %[[C0_F64]]) %[[Q0_6]] ctrl %[[Q1_6]] : !mqtopt.Qubit ctrl !mqtopt.Qubit %i1 = arith.constant 1 : index %i0 = arith.constant 0 : index @@ -482,9 +487,10 @@ module { %q0_4, %q1_4 = mqtopt.rx(%c0_f64) %q0_3 ctrl %q1_3 : !mqtopt.Qubit ctrl !mqtopt.Qubit %q0_5, %q1_5 = mqtopt.ry(%c0_f64) %q0_4 ctrl %q1_4 : !mqtopt.Qubit ctrl !mqtopt.Qubit %q0_6, %q1_6 = mqtopt.rz(%c0_f64) %q0_5 ctrl %q1_5 : !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_7, %q1_7 = mqtopt.r(%c0_f64, %c0_f64) %q0_6 ctrl %q1_6 : !mqtopt.Qubit ctrl !mqtopt.Qubit - memref.store %q0_6, %qreg[%i0] : memref<2x!mqtopt.Qubit> - memref.store %q1_0, %qreg[%i1] : memref<2x!mqtopt.Qubit> + memref.store %q0_7, %qreg[%i0] : memref<2x!mqtopt.Qubit> + memref.store %q1_7, %qreg[%i1] : memref<2x!mqtopt.Qubit> memref.dealloc %qreg : memref<2x!mqtopt.Qubit> return @@ -503,6 +509,7 @@ module { // CHECK: %[[Q0_4:.*]], %[[Q1_4:.*]] = mqtopt.rx(%[[C0_F64]]) %[[Q0_3]] ctrl %[[Q1_3]] : !mqtopt.Qubit ctrl !mqtopt.Qubit // CHECK: %[[Q0_5:.*]], %[[Q1_5:.*]] = mqtopt.ry(%[[C0_F64]]) %[[Q0_4]] ctrl %[[Q1_4]] : !mqtopt.Qubit ctrl !mqtopt.Qubit // CHECK: %[[Q0_6:.*]], %[[Q1_6:.*]] = mqtopt.rz(%[[C0_F64]]) %[[Q0_5]] ctrl %[[Q1_5]] : !mqtopt.Qubit ctrl !mqtopt.Qubit + // CHECK: %[[Q0_7:.*]], %[[Q1_7:.*]] = mqtopt.r(%[[C0_F64]], %[[C0_F64]]) %[[Q0_6]] ctrl %[[Q1_6]] : !mqtopt.Qubit ctrl !mqtopt.Qubit %q0_0 = mqtopt.qubit 0 %q1_0 = mqtopt.qubit 1 @@ -514,6 +521,7 @@ module { %q0_4, %q1_4 = mqtopt.rx(%c0_f64) %q0_3 ctrl %q1_3 : !mqtopt.Qubit ctrl !mqtopt.Qubit %q0_5, %q1_5 = mqtopt.ry(%c0_f64) %q0_4 ctrl %q1_4 : !mqtopt.Qubit ctrl !mqtopt.Qubit %q0_6, %q1_6 = mqtopt.rz(%c0_f64) %q0_5 ctrl %q1_5 : !mqtopt.Qubit ctrl !mqtopt.Qubit + %q0_7, %q1_7 = mqtopt.r(%c0_f64, %c0_f64) %q0_6 ctrl %q1_6 : !mqtopt.Qubit ctrl !mqtopt.Qubit return } diff --git a/mlir/test/Dialect/MQTRef/IR/dialect_features.mlir b/mlir/test/Dialect/MQTRef/IR/dialect_features.mlir index 729b9d57f6..a8e50efc18 100644 --- a/mlir/test/Dialect/MQTRef/IR/dialect_features.mlir +++ b/mlir/test/Dialect/MQTRef/IR/dialect_features.mlir @@ -378,6 +378,7 @@ module { // CHECK: mqtref.u(%[[P0]], %[[P0]], %[[P0]]) %[[Q0:.*]] // CHECK: mqtref.u2(%[[P0]], %[[P0]] static [] mask [false, false]) %[[Q0]] // CHECK: mqtref.p(%[[P0]]) %[[Q0]] + // CHECK: mqtref.r(%[[P0]], %[[P0]] static [] mask [false, false]) %[[Q0]] // CHECK: mqtref.rx(%[[P0]]) %[[Q0]] // CHECK: mqtref.ry(%[[P0]]) %[[Q0]] // CHECK: mqtref.rz(%[[P0]]) %[[Q0]] @@ -390,6 +391,7 @@ module { mqtref.u(%p0, %p0, %p0) %q0 mqtref.u2(%p0, %p0 static [] mask [false, false]) %q0 mqtref.p(%p0) %q0 + mqtref.r(%p0, %p0 static [] mask [false, false]) %q0 mqtref.rx(%p0) %q0 mqtref.ry(%p0) %q0 mqtref.rz(%p0) %q0 @@ -408,6 +410,7 @@ module { // CHECK: mqtref.u(%[[P0]], %[[P0]], %[[P0]]) %[[Q0:.*]] // CHECK: mqtref.u2(%[[P0]], %[[P0]] static [] mask [false, false]) %[[Q0]] // CHECK: mqtref.p(%[[P0]]) %[[Q0]] + // CHECK: mqtref.r(%[[P0]], %[[P0]] static [] mask [false, false]) %[[Q0]] // CHECK: mqtref.rx(%[[P0]]) %[[Q0]] // CHECK: mqtref.ry(%[[P0]]) %[[Q0]] // CHECK: mqtref.rz(%[[P0]]) %[[Q0]] @@ -418,6 +421,7 @@ module { mqtref.u(%p0, %p0, %p0) %q0 mqtref.u2(%p0, %p0 static [] mask [false, false]) %q0 mqtref.p(%p0) %q0 + mqtref.r(%p0, %p0 static [] mask [false, false]) %q0 mqtref.rx(%p0) %q0 mqtref.ry(%p0) %q0 mqtref.rz(%p0) %q0 @@ -435,6 +439,7 @@ module { // CHECK: mqtref.u(%[[P0]], %[[P0]], %[[P0]]) %[[Q0:.*]] ctrl %[[Q1:.*]] // CHECK: mqtref.u2(%[[P0]], %[[P0]]) %[[Q0]] ctrl %[[Q1]] // CHECK: mqtref.p(%[[P0]]) %[[Q0]] ctrl %[[Q1]] + // CHECK: mqtref.r(%[[P0]], %[[P0]]) %[[Q0]] ctrl %[[Q1]] // CHECK: mqtref.rx(%[[P0]]) %[[Q0]] ctrl %[[Q1]] // CHECK: mqtref.ry(%[[P0]]) %[[Q0]] ctrl %[[Q1]] // CHECK: mqtref.rz(%[[P0]]) %[[Q0]] ctrl %[[Q1]] @@ -449,6 +454,7 @@ module { mqtref.u(%p0, %p0, %p0) %q0 ctrl %q1 mqtref.u2(%p0, %p0) %q0 ctrl %q1 mqtref.p(%p0) %q0 ctrl %q1 + mqtref.r(%p0, %p0) %q0 ctrl %q1 mqtref.rx(%p0) %q0 ctrl %q1 mqtref.ry(%p0) %q0 ctrl %q1 mqtref.rz(%p0) %q0 ctrl %q1 @@ -467,6 +473,7 @@ module { // CHECK: mqtref.u(%[[P0]], %[[P0]], %[[P0]]) %[[Q0:.*]] ctrl %[[Q1:.*]] // CHECK: mqtref.u2(%[[P0]], %[[P0]]) %[[Q0]] ctrl %[[Q1]] // CHECK: mqtref.p(%[[P0]]) %[[Q0]] ctrl %[[Q1]] + // CHECK: mqtref.r(%[[P0]], %[[P0]]) %[[Q0]] ctrl %[[Q1]] // CHECK: mqtref.rx(%[[P0]]) %[[Q0]] ctrl %[[Q1]] // CHECK: mqtref.ry(%[[P0]]) %[[Q0]] ctrl %[[Q1]] // CHECK: mqtref.rz(%[[P0]]) %[[Q0]] ctrl %[[Q1]] @@ -478,6 +485,7 @@ module { mqtref.u(%p0, %p0, %p0) %q0 ctrl %q1 mqtref.u2(%p0, %p0) %q0 ctrl %q1 mqtref.p(%p0) %q0 ctrl %q1 + mqtref.r(%p0, %p0) %q0 ctrl %q1 mqtref.rx(%p0) %q0 ctrl %q1 mqtref.ry(%p0) %q0 ctrl %q1 mqtref.rz(%p0) %q0 ctrl %q1 diff --git a/mlir/unittests/translation/test_translation.cpp b/mlir/unittests/translation/test_translation.cpp index 405b6a509f..793476f788 100644 --- a/mlir/unittests/translation/test_translation.cpp +++ b/mlir/unittests/translation/test_translation.cpp @@ -446,6 +446,12 @@ INSTANTIATE_TEST_SUITE_P( .build = [](QuantumComputation& qc) { qc.p(0.1, 0); }, .checkStringOperation = getCheckStringOperationParams("p", {0.1}, {0})}, + TestCaseUnitary{ + .name = "R", + .numQubits = 1, + .build = [](QuantumComputation& qc) { qc.r(0.1, 0.2, 0); }, + .checkStringOperation = + getCheckStringOperationParams("r", {0.1, 0.2}, {0})}, TestCaseUnitary{.name = "RX", .numQubits = 1, .build = [](QuantumComputation& qc) { qc.rx(0.1, 0); }, diff --git a/python/mqt/core/ir/__init__.pyi b/python/mqt/core/ir/__init__.pyi index b23dda45ff..a6c979d53c 100644 --- a/python/mqt/core/ir/__init__.pyi +++ b/python/mqt/core/ir/__init__.pyi @@ -1003,6 +1003,48 @@ class QuantumComputation(MutableSequence[Operation]): :meth:`sxdg` """ + def r(self, theta: float | Expression, phi: float | Expression, q: int) -> None: + r"""Apply an :math:`R(\theta, \phi)` gate. + + .. math:: + R(\theta, \phi) = e^{-i\frac{\theta}{2}(\cos(\phi)X+\sin(\phi)Y)} + = \begin{pmatrix} \cos(\theta/2) & -i e^{-i\phi} \sin(\theta/2) \\ + -i e^{i\phi} \sin(\theta/2) & \cos(\theta/2) \end{pmatrix} + + Args: + theta: The rotation angle :math:`\theta` + phi: The angle specifying the rotation axis given by :math:`\cos(\phi)X+\sin(\phi)Y` + q: The target qubit + """ + + def cr(self, theta: float | Expression, phi: float | Expression, control: Control | int, target: int) -> None: + r"""Apply a controlled :math:`R(\theta, \phi)` gate. + + Args: + theta: The rotation angle :math:`\theta` + phi: The angle specifying the rotation axis given by :math:`\cos(\phi)X+\sin(\phi)Y` + control: The control qubit + target: The target qubit + + See Also: + :meth:`r` + """ + + def mcr( + self, theta: float | Expression, phi: float | Expression, controls: set[Control | int], target: int + ) -> None: + r"""Apply a multi-controlled :math:`R(\theta, \phi)` gate. + + Args: + theta: The rotation angle :math:`\theta` + phi: The angle specifying the rotation axis given by :math:`\cos(\phi)X+\sin(\phi)Y` + controls: The control qubits + target: The target qubit + + See Also: + :meth:`r` + """ + def rx(self, theta: float | Expression, q: int) -> None: r"""Apply an :math:`R_x(\theta)` gate. diff --git a/python/mqt/core/ir/operations.pyi b/python/mqt/core/ir/operations.pyi index fd677d6b09..5c23ebf913 100644 --- a/python/mqt/core/ir/operations.pyi +++ b/python/mqt/core/ir/operations.pyi @@ -185,6 +185,13 @@ class OpType(Enum): See Also: :meth:`mqt.core.ir.QuantumComputation.reset` """ + r = ... + r""" + An :math:`R` gate. + + See Also: + :meth:`mqt.core.ir.QuantumComputation.r` + """ rx = ... r""" An :math:`R_x` gate. diff --git a/python/mqt/core/plugins/qiskit/mqt_to_qiskit.py b/python/mqt/core/plugins/qiskit/mqt_to_qiskit.py index fae12505fd..08a156dff6 100644 --- a/python/mqt/core/plugins/qiskit/mqt_to_qiskit.py +++ b/python/mqt/core/plugins/qiskit/mqt_to_qiskit.py @@ -26,6 +26,7 @@ HGate, IGate, PhaseGate, + RGate, RXGate, RXXGate, RYGate, @@ -178,6 +179,7 @@ def _add_standard_operation(circ: QuantumCircuit, op: StandardOperation, qubit_m gate_map_two_param: dict[OpType, type] = { OpType.u2: U2Gate, + OpType.r: RGate, OpType.xx_plus_yy: XXPlusYYGate, OpType.xx_minus_yy: XXMinusYYGate, } diff --git a/python/mqt/core/plugins/qiskit/qiskit_to_mqt.py b/python/mqt/core/plugins/qiskit/qiskit_to_mqt.py index ea31cc40e8..fce31f34a7 100644 --- a/python/mqt/core/plugins/qiskit/qiskit_to_mqt.py +++ b/python/mqt/core/plugins/qiskit/qiskit_to_mqt.py @@ -132,6 +132,7 @@ def qiskit_to_mqt(circ: QuantumCircuit) -> QuantumComputation: "tdg", "p", "u1", + "r", "rx", "ry", "rz", @@ -265,6 +266,9 @@ def _emplace_operation( qargs = qargs[: -num_controls + 2] return _add_operation(qc, OpType.x, qargs, params, qubit_map) + if name in {"r", "prx"}: + return _add_operation(qc, OpType.r, qargs, params, qubit_map) + if name in {"rx", "crx", "mcrx"}: return _add_operation(qc, OpType.rx, qargs, params, qubit_map) diff --git a/src/dd/GateMatrixDefinitions.cpp b/src/dd/GateMatrixDefinitions.cpp index 0de5c877a0..c62780e064 100644 --- a/src/dd/GateMatrixDefinitions.cpp +++ b/src/dd/GateMatrixDefinitions.cpp @@ -65,6 +65,29 @@ GateMatrix rzMat(const fp lambda) { {std::cos(lambda / 2.), std::sin(lambda / 2.)}}}; } +/** + * @brief Computes the matrix representation of the R(θ, φ) gate. + * @param theta The rotation angle θ. + * @param phi The rotation axis angle φ. + * @return The gate matrix for the R(θ, φ) rotation. + * + * @details The R(θ, φ) gate is defined as R(θ, φ) = exp(-i*θ/2*(cos(φ)X + + * sin(φ)Y)), which results in the matrix: + * [[cos(θ/2), -i*e^(-iφ)*sin(θ/2)], + * [-i*e^(iφ)*sin(θ/2), cos(θ/2)]] + */ +GateMatrix rMat(const fp theta, const fp phi) { + const auto cosTheta = std::cos(theta / 2.); + const auto sinTheta = std::sin(theta / 2.); + const auto sinPhi = std::sin(phi); + const auto cosPhi = std::cos(phi); + const std::complex diag = {cosTheta, 0.}; + const std::complex m01 = {-sinTheta * sinPhi, -sinTheta * cosPhi}; + const std::complex m10 = {sinTheta * sinPhi, -sinTheta * cosPhi}; + + return GateMatrix{diag, m01, m10, diag}; +} + TwoQubitGateMatrix rxxMat(const fp theta) { const auto cosTheta = std::cos(theta / 2.); const auto sinTheta = std::sin(theta / 2.); @@ -183,6 +206,8 @@ GateMatrix opToSingleQubitGateMatrix(const qc::OpType t, return ryMat(params.at(0)); case qc::RZ: return rzMat(params.at(0)); + case qc::R: + return rMat(params.at(0), params.at(1)); default: throw std::invalid_argument("Invalid single-qubit gate type"); } diff --git a/src/dd/Operations.cpp b/src/dd/Operations.cpp index 73c211d23e..af440b5091 100644 --- a/src/dd/Operations.cpp +++ b/src/dd/Operations.cpp @@ -109,6 +109,7 @@ MatrixDD getStandardOperationDD(const qc::StandardOperation& op, Package& dd, case qc::RX: case qc::RY: case qc::RZ: + case qc::R: case qc::P: case qc::XXminusYY: case qc::XXplusYY: diff --git a/src/ir/QuantumComputation.cpp b/src/ir/QuantumComputation.cpp index 4d430d7b37..52cf4ba512 100644 --- a/src/ir/QuantumComputation.cpp +++ b/src/ir/QuantumComputation.cpp @@ -1396,6 +1396,7 @@ DEFINE_SINGLE_TARGET_SINGLE_PARAMETER_OPERATION(p, theta) } DEFINE_SINGLE_TARGET_TWO_PARAMETER_OPERATION(u2, phi, lambda) +DEFINE_SINGLE_TARGET_TWO_PARAMETER_OPERATION(r, theta, phi) #undef DEFINE_SINGLE_TARGET_TWO_PARAMETER_OPERATION diff --git a/src/ir/operations/OpType.cpp b/src/ir/operations/OpType.cpp index 181198454a..b936ee3f55 100644 --- a/src/ir/operations/OpType.cpp +++ b/src/ir/operations/OpType.cpp @@ -10,6 +10,8 @@ #include "ir/operations/OpType.hpp" +#include +#include #include #include #include @@ -64,87 +66,106 @@ std::string shortName(const OpType opType) { return toString(opType); } } -OpType opTypeFromString(const std::string& opType) { - static const std::unordered_map OP_NAME_TO_TYPE = { - {"none", OpType::None}, - {"gphase", OpType::GPhase}, - {"i", OpType::I}, - {"id", OpType::I}, - {"h", OpType::H}, - {"ch", OpType::H}, - {"x", OpType::X}, - {"cnot", OpType::X}, - {"cx", OpType::X}, - {"mcx", OpType::X}, - {"y", OpType::Y}, - {"cy", OpType::Y}, - {"z", OpType::Z}, - {"cz", OpType::Z}, - {"s", OpType::S}, - {"cs", OpType::S}, - {"sdg", OpType::Sdg}, - {"csdg", OpType::Sdg}, - {"t", OpType::T}, - {"ct", OpType::T}, - {"tdg", OpType::Tdg}, - {"ctdg", OpType::Tdg}, - {"v", OpType::V}, - {"vdg", OpType::Vdg}, - {"u", OpType::U}, - {"cu", OpType::U}, - {"u3", OpType::U}, - {"cu3", OpType::U}, - {"u2", OpType::U2}, - {"cu2", OpType::U2}, - {"p", OpType::P}, - {"cp", OpType::P}, - {"mcp", OpType::P}, - {"phase", OpType::P}, - {"cphase", OpType::P}, - {"mcphase", OpType::P}, - {"u1", OpType::P}, - {"cu1", OpType::P}, - {"sx", OpType::SX}, - {"csx", OpType::SX}, - {"sxdg", OpType::SXdg}, - {"csxdg", OpType::SXdg}, - {"rx", OpType::RX}, - {"crx", OpType::RX}, - {"ry", OpType::RY}, - {"cry", OpType::RY}, - {"rz", OpType::RZ}, - {"crz", OpType::RZ}, - {"swap", OpType::SWAP}, - {"cswap", OpType::SWAP}, - {"iswap", OpType::iSWAP}, - {"iswapdg", OpType::iSWAPdg}, - {"peres", OpType::Peres}, - {"peresdg", OpType::Peresdg}, - {"dcx", OpType::DCX}, - {"ecr", OpType::ECR}, - {"rxx", OpType::RXX}, - {"ryy", OpType::RYY}, - {"rzz", OpType::RZZ}, - {"rzx", OpType::RZX}, - {"xx_minus_yy", OpType::XXminusYY}, - {"xx_plus_yy", OpType::XXplusYY}, - {"measure", OpType::Measure}, - {"reset", OpType::Reset}, - {"barrier", OpType::Barrier}, - {"if_else", OpType::IfElse}, - {"compound", OpType::Compound}, - {"move", OpType::Move}, - {"aod_activate", OpType::AodActivate}, - {"aod_deactivate", OpType::AodDeactivate}, - {"aod_move", OpType::AodMove}, - }; - // try to find the operation type in the map of known operation types and - // return it if found or throw an exception otherwise. - if (const auto it = OP_NAME_TO_TYPE.find(opType); - it != OP_NAME_TO_TYPE.end()) { - return OP_NAME_TO_TYPE.at(opType); +namespace { +struct NameToType { + std::string_view name; + OpType type; +}; + +// Sorted lexicographically by `name` +constexpr std::array OP_NAME_TO_TYPE{ + NameToType{.name = "aod_activate", .type = AodActivate}, + NameToType{.name = "aod_deactivate", .type = AodDeactivate}, + NameToType{.name = "aod_move", .type = AodMove}, + NameToType{.name = "barrier", .type = Barrier}, + NameToType{.name = "ch", .type = H}, + NameToType{.name = "cnot", .type = X}, + NameToType{.name = "compound", .type = Compound}, + NameToType{.name = "cp", .type = P}, + NameToType{.name = "cphase", .type = P}, + NameToType{.name = "cr", .type = R}, + NameToType{.name = "crx", .type = RX}, + NameToType{.name = "cry", .type = RY}, + NameToType{.name = "crz", .type = RZ}, + NameToType{.name = "cs", .type = S}, + NameToType{.name = "csdg", .type = Sdg}, + NameToType{.name = "cswap", .type = SWAP}, + NameToType{.name = "csx", .type = SX}, + NameToType{.name = "csxdg", .type = SXdg}, + NameToType{.name = "ct", .type = T}, + NameToType{.name = "ctdg", .type = Tdg}, + NameToType{.name = "cu", .type = U}, + NameToType{.name = "cu1", .type = P}, + NameToType{.name = "cu2", .type = U2}, + NameToType{.name = "cu3", .type = U}, + NameToType{.name = "cx", .type = X}, + NameToType{.name = "cy", .type = Y}, + NameToType{.name = "cz", .type = Z}, + NameToType{.name = "dcx", .type = DCX}, + NameToType{.name = "ecr", .type = ECR}, + NameToType{.name = "gphase", .type = GPhase}, + NameToType{.name = "h", .type = H}, + NameToType{.name = "i", .type = I}, + NameToType{.name = "id", .type = I}, + NameToType{.name = "if_else", .type = IfElse}, + NameToType{.name = "iswap", .type = iSWAP}, + NameToType{.name = "iswapdg", .type = iSWAPdg}, + NameToType{.name = "mcp", .type = P}, + NameToType{.name = "mcphase", .type = P}, + NameToType{.name = "mcx", .type = X}, + NameToType{.name = "measure", .type = Measure}, + NameToType{.name = "move", .type = Move}, + NameToType{.name = "none", .type = None}, + NameToType{.name = "p", .type = P}, + NameToType{.name = "peres", .type = Peres}, + NameToType{.name = "peresdg", .type = Peresdg}, + NameToType{.name = "phase", .type = P}, + NameToType{.name = "prx", .type = R}, + NameToType{.name = "r", .type = R}, + NameToType{.name = "reset", .type = Reset}, + NameToType{.name = "rx", .type = RX}, + NameToType{.name = "rxx", .type = RXX}, + NameToType{.name = "ry", .type = RY}, + NameToType{.name = "ryy", .type = RYY}, + NameToType{.name = "rz", .type = RZ}, + NameToType{.name = "rzx", .type = RZX}, + NameToType{.name = "rzz", .type = RZZ}, + NameToType{.name = "s", .type = S}, + NameToType{.name = "sdg", .type = Sdg}, + NameToType{.name = "swap", .type = SWAP}, + NameToType{.name = "sx", .type = SX}, + NameToType{.name = "sxdg", .type = SXdg}, + NameToType{.name = "t", .type = T}, + NameToType{.name = "tdg", .type = Tdg}, + NameToType{.name = "u", .type = U}, + NameToType{.name = "u1", .type = P}, + NameToType{.name = "u2", .type = U2}, + NameToType{.name = "u3", .type = U}, + NameToType{.name = "v", .type = V}, + NameToType{.name = "vdg", .type = Vdg}, + NameToType{.name = "x", .type = X}, + NameToType{.name = "xx_minus_yy", .type = XXminusYY}, + NameToType{.name = "xx_plus_yy", .type = XXplusYY}, + NameToType{.name = "y", .type = Y}, + NameToType{.name = "z", .type = Z}, +}; +static_assert(std::ranges::is_sorted(OP_NAME_TO_TYPE.cbegin(), + OP_NAME_TO_TYPE.cend(), + [](const auto& lhs, const auto& rhs) { + return lhs.name < rhs.name; + })); +} // namespace + +OpType opTypeFromString(const std::string_view opType) { + // clang-tidy produces a false-positive that produces a Windows compile error + // when accepted. NOLINTNEXTLINE(*-qualified-auto) + const auto it = + std::ranges::lower_bound(OP_NAME_TO_TYPE, opType, {}, &NameToType::name); + if (it != OP_NAME_TO_TYPE.end() && it->name == opType) { + return it->type; } - throw std::invalid_argument("Unsupported operation type: " + opType); + throw std::invalid_argument("Unsupported operation type: " + + std::string(opType)); } } // namespace qc diff --git a/src/ir/operations/StandardOperation.cpp b/src/ir/operations/StandardOperation.cpp index e9cddf4601..965bedb01a 100644 --- a/src/ir/operations/StandardOperation.cpp +++ b/src/ir/operations/StandardOperation.cpp @@ -421,6 +421,9 @@ void StandardOperation::dumpGateType( case RZ: op << "rz(" << parameter[0] << ")"; break; + case R: + op << "r(" << parameter[0] << "," << parameter[1] << ")"; + break; case DCX: op << "dcx"; break; @@ -597,6 +600,7 @@ void StandardOperation::invert() { case RX: case RY: case RZ: + case R: case RXX: case RYY: case RZZ: diff --git a/src/ir/operations/SymbolicOperation.cpp b/src/ir/operations/SymbolicOperation.cpp index 6a47a47b58..076fb9cf39 100644 --- a/src/ir/operations/SymbolicOperation.cpp +++ b/src/ir/operations/SymbolicOperation.cpp @@ -419,6 +419,7 @@ void SymbolicOperation::invert() { case RX: case RY: case RZ: + case R: case RXX: case RYY: case RZZ: diff --git a/src/qir/runner/Runner.cpp b/src/qir/runner/Runner.cpp index 523bc4472b..879758d480 100644 --- a/src/qir/runner/Runner.cpp +++ b/src/qir/runner/Runner.cpp @@ -304,6 +304,12 @@ auto main(int argc, char* argv[]) -> int { llvm::sys::DynamicLibrary::AddSymbol( "__quantum__qis__tdg__body", reinterpret_cast(&__quantum__qis__tdg__body)); + llvm::sys::DynamicLibrary::AddSymbol( + "__quantum__qis__r__body", + reinterpret_cast(&__quantum__qis__r__body)); + llvm::sys::DynamicLibrary::AddSymbol( + "__quantum__qis__prx__body", + reinterpret_cast(&__quantum__qis__prx__body)); llvm::sys::DynamicLibrary::AddSymbol( "__quantum__qis__rx__body", reinterpret_cast(&__quantum__qis__rx__body)); diff --git a/src/qir/runtime/QIR.cpp b/src/qir/runtime/QIR.cpp index 13a01d1ea9..04439c421d 100644 --- a/src/qir/runtime/QIR.cpp +++ b/src/qir/runtime/QIR.cpp @@ -187,6 +187,19 @@ void __quantum__qis__tdg__body(Qubit* qubit) { runtime.apply(qubit); } +void __quantum__qis__r__body(Qubit* qubit, const double theta, + const double phi) { + auto& runtime = qir::Runtime::getInstance(); + runtime.apply(theta, phi, qubit); +} + +// prx is an alias for the R gate +void __quantum__qis__prx__body(Qubit* qubit, const double theta, + const double phi) { + auto& runtime = qir::Runtime::getInstance(); + runtime.apply(theta, phi, qubit); +} + void __quantum__qis__rx__body(Qubit* qubit, const double phi) { auto& runtime = qir::Runtime::getInstance(); runtime.apply(phi, qubit); diff --git a/src/zx/FunctionalityConstruction.cpp b/src/zx/FunctionalityConstruction.cpp index f50a956490..e69dbef87a 100644 --- a/src/zx/FunctionalityConstruction.cpp +++ b/src/zx/FunctionalityConstruction.cpp @@ -380,6 +380,17 @@ FunctionalityConstruction::parseOp(ZXDiagram& diag, op_it it, op_it end, addZSpider(diag, target, qubits, parseParam(op.get(), 0) + PiRational(1, 2)); break; + case qc::OpType::R: + addZSpider(diag, target, qubits, + parseParam(op.get(), 1) - PiRational(1, 2)); + addXSpider(diag, target, qubits, PiExpression(PiRational(1, 2))); + addZSpider(diag, target, qubits, + parseParam(op.get(), 0) + PiRational(1, 1)); + addXSpider(diag, target, qubits, PiExpression(PiRational(1, 2))); + addZSpider(diag, target, qubits, + -(parseParam(op.get(), 1) - PiRational(1, 2)) + + PiRational(3, 1)); + break; case qc::OpType::U: addZSpider(diag, target, qubits, parseParam(op.get(), 2)); addXSpider(diag, target, qubits, PiExpression(PiRational(1, 2))); @@ -647,6 +658,7 @@ bool FunctionalityConstruction::transformableToZX(const qc::Operation* op) { if (!op->isControlled()) { switch (op->getType()) { + case qc::OpType::R: case qc::OpType::Z: case qc::OpType::RZ: case qc::OpType::P: diff --git a/test/circuits/test.qasm b/test/circuits/test.qasm index 59e84016b4..0f76e80127 100755 --- a/test/circuits/test.qasm +++ b/test/circuits/test.qasm @@ -14,6 +14,7 @@ x q; x q[0]; y q[0]; z q[0]; +r(1, 2) q[0]; rx(pi/2) q[0]; rx(pi/4) q[0]; ry(pi/4) q[0]; diff --git a/test/dd/test_dd_functionality.cpp b/test/dd/test_dd_functionality.cpp index d24dff61f4..2d2ff1222d 100644 --- a/test/dd/test_dd_functionality.cpp +++ b/test/dd/test_dd_functionality.cpp @@ -58,7 +58,7 @@ class DDFunctionality : public testing::TestWithParam { INSTANTIATE_TEST_SUITE_P( Parameters, DDFunctionality, testing::Values(GPhase, I, H, X, Y, Z, S, Sdg, T, Tdg, SX, SXdg, V, Vdg, U, - U2, P, RX, RY, RZ, Peres, Peresdg, SWAP, iSWAP, iSWAPdg, + U2, P, R, RX, RY, RZ, Peres, Peresdg, SWAP, iSWAP, iSWAPdg, DCX, ECR, RXX, RYY, RZZ, RZX, XXminusYY, XXplusYY), [](const testing::TestParamInfo& inf) { const auto gate = inf.param; @@ -82,6 +82,7 @@ TEST_P(DDFunctionality, StandardOpBuildInverseBuild) { op = StandardOperation(0, gate, std::vector{dist(mt), dist(mt), dist(mt)}); break; case U2: + case R: op = StandardOperation(0, gate, std::vector{dist(mt), dist(mt)}); break; case RX: @@ -138,6 +139,7 @@ TEST_P(DDFunctionality, ControlledStandardOpBuildInverseBuild) { std::vector{dist(mt), dist(mt), dist(mt)}); break; case U2: + case R: op = StandardOperation(0, 1, gate, std::vector{dist(mt), dist(mt)}); break; case RX: @@ -195,6 +197,7 @@ TEST_P(DDFunctionality, ControlledStandardNegOpBuildInverseBuild) { std::vector{dist(mt), dist(mt), dist(mt)}); break; case U2: + case R: op = StandardOperation(Controls{0_nc}, 1, gate, std::vector{dist(mt), dist(mt)}); break; @@ -273,8 +276,14 @@ TEST_F(DDFunctionality, BuildCircuit) { qc.cxx_minus_yy(theta, beta, 2, 0, 1); qc.xx_plus_yy(theta, beta, 0, 1); qc.cxx_plus_yy(theta, beta, 2, 0, 1); + qc.r(theta, beta, 0); + qc.cr(theta, beta, 2, 0); + qc.mcr(theta, beta, {2, 3}, 0); // invert the circuit above + qc.mcr(-theta, beta, {2, 3}, 0); + qc.cr(-theta, beta, 2, 0); + qc.r(-theta, beta, 0); qc.cxx_plus_yy(-theta, beta, 2, 0, 1); qc.xx_plus_yy(-theta, beta, 0, 1); qc.cxx_minus_yy(-theta, beta, 2, 0, 1); diff --git a/test/ir/test_qfr_functionality.cpp b/test/ir/test_qfr_functionality.cpp index abae0f4c01..2caa97973e 100644 --- a/test/ir/test_qfr_functionality.cpp +++ b/test/ir/test_qfr_functionality.cpp @@ -272,6 +272,9 @@ TEST_F(QFRFunctionality, gateShortCutsAndCloning) { qc.sxdg(0); qc.csxdg(1, 0); qc.mcsxdg({1, 2_nc}, 0); + qc.r(PI, PI, 0); + qc.cr(PI, PI, 1, 0); + qc.mcr(PI, PI, {1, 2_nc}, 0); qc.rx(PI, 0); qc.crx(PI, 1, 0); qc.mcrx(PI, {1, 2_nc}, 0); @@ -856,6 +859,7 @@ TEST_F(QFRFunctionality, invertStandardOpParamChange) { const auto cases = { std::tuple{OpType::GPhase, std::vector{1}, std::vector{-1}}, std::tuple{OpType::P, std::vector{1}, std::vector{-1}}, + std::tuple{OpType::R, std::vector{1, 2}, std::vector{-1, 2}}, std::tuple{OpType::RX, std::vector{1}, std::vector{-1}}, std::tuple{OpType::RY, std::vector{1}, std::vector{-1}}, std::tuple{OpType::RZ, std::vector{1}, std::vector{-1}}, @@ -909,6 +913,8 @@ TEST_F(QFRFunctionality, invertSymbolicOpParamChange) { std::vector{-1.0}}, std::tuple{OpType::U2, std::vector{Symbolic({x}), 1.0}, std::vector{-1.0 + PI, -Symbolic({x}) - PI}}, + std::tuple{OpType::R, std::vector{Symbolic({x}), 2.0}, + std::vector{-Symbolic({x}), 2.0}}, std::tuple{ OpType::U, std::vector{Symbolic({x}), 2.0, Symbolic({y})}, diff --git a/test/ir/test_symbolic.cpp b/test/ir/test_symbolic.cpp index 44313f0896..e9e30afb3e 100644 --- a/test/ir/test_symbolic.cpp +++ b/test/ir/test_symbolic.cpp @@ -54,6 +54,10 @@ TEST_F(SymbolicTest, Gates) { symQc.cp(xMonom, 1, 0); symQc.p(xMonom, 0); + symQc.mcr(xMonom, yMonom, {1, 2_nc}, 0); + symQc.cr(xMonom, yMonom, 1, 0); + symQc.r(xMonom, yMonom, 0); + symQc.mcrx(xMonom, {1, 2_nc}, 0); symQc.crx(xMonom, 1, 0); symQc.rx(xMonom, 0); @@ -108,6 +112,10 @@ TEST_F(SymbolicTest, Gates) { qc.cp(xVal, 1, 0); qc.p(xVal, 0); + qc.mcr(xVal, yVal, {1, 2_nc}, 0); + qc.cr(xVal, yVal, 1, 0); + qc.r(xVal, yVal, 0); + qc.mcrx(xVal, {1, 2_nc}, 0); qc.crx(xVal, 1, 0); qc.rx(xVal, 0); diff --git a/test/python/plugins/test_qiskit.py b/test/python/plugins/test_qiskit.py index 7735356bde..5984ccd7fe 100644 --- a/test/python/plugins/test_qiskit.py +++ b/test/python/plugins/test_qiskit.py @@ -378,6 +378,7 @@ def test_operations() -> None: qc.sx(0) qc.csx(0, 1) qc.sxdg(0) + qc.r(0.5, 0.5, 0) qc.rx(0.5, 0) qc.crx(0.5, 0, 1) qc.ry(0.5, 0) diff --git a/test/qir/runtime/test_qir_runtime.cpp b/test/qir/runtime/test_qir_runtime.cpp index 3df115b02e..34153deca3 100644 --- a/test/qir/runtime/test_qir_runtime.cpp +++ b/test/qir/runtime/test_qir_runtime.cpp @@ -109,6 +109,18 @@ TEST_F(QIRRuntimeTest, TdgGate) { __quantum__qis__tdg__body(q0); } +TEST_F(QIRRuntimeTest, RGate) { + auto* q0 = reinterpret_cast(0UL); + __quantum__rt__initialize(nullptr); + __quantum__qis__r__body(q0, qc::PI_2, 0); +} + +TEST_F(QIRRuntimeTest, PRXGate) { + auto* q0 = reinterpret_cast(0UL); + __quantum__rt__initialize(nullptr); + __quantum__qis__prx__body(q0, qc::PI_2, 0); +} + TEST_F(QIRRuntimeTest, RXGate) { auto* q0 = reinterpret_cast(0UL); __quantum__rt__initialize(nullptr); diff --git a/test/zx/test_zx_functionality.cpp b/test/zx/test_zx_functionality.cpp index 7728908893..60ecc9132a 100644 --- a/test/zx/test_zx_functionality.cpp +++ b/test/zx/test_zx_functionality.cpp @@ -124,6 +124,8 @@ TEST_F(ZXFunctionalityTest, complexCircuit) { << "rzx(pi/4) q[0], q[1];" << "ecr q[0], q[1];" << "dcx q[0], q[1];" + << "r(pi/8, pi/4) q[2];" + << "r(-pi/8, pi/4) q[2];" << "dcx q[1], q[0];" << "ecr q[0], q[1];" << "rzx(-pi/4) q[0], q[1];"