diff --git a/extern/LogicBlocks b/extern/LogicBlocks index a4287fcc2..4bb726be7 160000 --- a/extern/LogicBlocks +++ b/extern/LogicBlocks @@ -1 +1 @@ -Subproject commit a4287fcc2d1e9ab91efc132d9f63c35fc830f39b +Subproject commit 4bb726be710e39c5571a0eb7874fd8e897be09d6 diff --git a/extern/mqt-core b/extern/mqt-core index 0e8afc486..ca49adc1c 160000 --- a/extern/mqt-core +++ b/extern/mqt-core @@ -1 +1 @@ -Subproject commit 0e8afc486515df7258f1dd47800803b0154f0bbb +Subproject commit ca49adc1c3669b44cf19756e01d2055e94403864 diff --git a/include/cliffordsynthesis/CliffordSynthesizer.hpp b/include/cliffordsynthesis/CliffordSynthesizer.hpp index faa03d8f1..eb1c4b1ef 100644 --- a/include/cliffordsynthesis/CliffordSynthesizer.hpp +++ b/include/cliffordsynthesis/CliffordSynthesizer.hpp @@ -81,6 +81,10 @@ class CliffordSynthesizer { return metric == TargetMetric::Depth; } + static bool requiresTwoQubitEncoding(const TargetMetric metric) { + return metric == TargetMetric::TwoQubitDepth; + } + void determineInitialTimestepLimit(EncoderConfig& config); std::pair determineUpperBound(EncoderConfig config); void runMaxSAT(const EncoderConfig& config); @@ -92,6 +96,8 @@ class CliffordSynthesizer { std::size_t upper); void depthOptimalSynthesis(EncoderConfig config, std::size_t lower, std::size_t upper); + void twoQubitDepthOptimalSynthesis(EncoderConfig config, std::size_t lower, + std::size_t upper); void depthHeuristicSynthesis(); void twoQubitGateOptimalSynthesis(EncoderConfig config, std::size_t lower, std::size_t upper); diff --git a/include/cliffordsynthesis/Configuration.hpp b/include/cliffordsynthesis/Configuration.hpp index 47bdbd829..8a3850ac6 100644 --- a/include/cliffordsynthesis/Configuration.hpp +++ b/include/cliffordsynthesis/Configuration.hpp @@ -38,6 +38,9 @@ struct Configuration { /// Settings for depth-optimal synthesis bool minimizeGatesAfterDepthOptimization = false; + /// Settings for TwoQubitDepth-optimal synthesis + bool useTwoQubitEncoding = false; + /// Settings for two-qubit gate-optimal synthesis bool tryHigherGateLimitForTwoQubitGateOptimization = false; double gateLimitFactor = 1.1; @@ -63,9 +66,10 @@ struct Configuration { j["gate_limit_factor"] = gateLimitFactor; j["minimize_gates_after_two_qubit_gate_optimization"] = minimizeGatesAfterTwoQubitGateOptimization; - j["heuristic"] = heuristic; - j["split_size"] = splitSize; - j["n_threads_heuristic"] = nThreadsHeuristic; + j["use_two_qubit_encoding"] = useTwoQubitEncoding; + j["heuristic"] = heuristic; + j["split_size"] = splitSize; + j["n_threads_heuristic"] = nThreadsHeuristic; if (!solverParameters.empty()) { nlohmann::json solverParametersJson; for (const auto& entry : solverParameters) { @@ -77,6 +81,7 @@ struct Configuration { } j["solver_parameters"] = solverParametersJson; } + return j; } diff --git a/include/cliffordsynthesis/Results.hpp b/include/cliffordsynthesis/Results.hpp index 53f88f979..089febf4e 100644 --- a/include/cliffordsynthesis/Results.hpp +++ b/include/cliffordsynthesis/Results.hpp @@ -25,6 +25,7 @@ class Results { setResultCircuit(qc); setResultTableau(tableau); setDepth(qc.getDepth()); + setTwoQubitDepth(qc.getTwoQubitDepth()); setSingleQubitGates(qc.getNsingleQubitOps()); setTwoQubitGates(qc.getNindividualOps() - singleQubitGates); setSolverResult(logicbase::Result::SAT); @@ -39,8 +40,9 @@ class Results { [[nodiscard]] std::size_t getSingleQubitGates() const { return singleQubitGates; } - [[nodiscard]] std::size_t getDepth() const { return depth; } - [[nodiscard]] double getRuntime() const { return runtime; } + [[nodiscard]] std::size_t getDepth() const { return depth; } + [[nodiscard]] std::size_t getTwoQubitDepth() const { return twoQubitDepth; } + [[nodiscard]] double getRuntime() const { return runtime; } [[nodiscard]] logicbase::Result getSolverResult() const { return solverResult; } @@ -52,6 +54,7 @@ class Results { void setSingleQubitGates(const std::size_t g) { singleQubitGates = g; } void setTwoQubitGates(const std::size_t g) { twoQubitGates = g; } void setDepth(const std::size_t d) { depth = d; } + void setTwoQubitDepth(const std::size_t d) { twoQubitDepth = d; } void setRuntime(const double t) { runtime = t; } void setSolverResult(const logicbase::Result r) { solverResult = r; } void setSolverCalls(const std::size_t c) { solverCalls = c; } @@ -80,6 +83,7 @@ class Results { resultJSON["single_qubit_gates"] = singleQubitGates; resultJSON["two_qubit_gates"] = twoQubitGates; resultJSON["depth"] = depth; + resultJSON["twoQubitDepth"] = twoQubitDepth; resultJSON["runtime"] = runtime; resultJSON["solver_calls"] = solverCalls; @@ -96,8 +100,10 @@ class Results { std::size_t singleQubitGates = std::numeric_limits::max(); std::size_t twoQubitGates = std::numeric_limits::max(); std::size_t depth = std::numeric_limits::max(); - double runtime = 0.0; - std::size_t solverCalls = 0U; + std::size_t twoQubitDepth = std::numeric_limits::max(); + + double runtime = 0.0; + std::size_t solverCalls = 0U; std::string resultTableau{}; std::string resultCircuit{}; diff --git a/include/cliffordsynthesis/TargetMetric.hpp b/include/cliffordsynthesis/TargetMetric.hpp index bb0cb36ef..51500c244 100644 --- a/include/cliffordsynthesis/TargetMetric.hpp +++ b/include/cliffordsynthesis/TargetMetric.hpp @@ -9,7 +9,7 @@ #include namespace cs { -enum class TargetMetric { Gates, TwoQubitGates, Depth }; +enum class TargetMetric { Gates, TwoQubitGates, Depth, TwoQubitDepth }; [[maybe_unused]] static inline std::string toString(const TargetMetric target) { switch (target) { @@ -19,6 +19,8 @@ enum class TargetMetric { Gates, TwoQubitGates, Depth }; return "two_qubit_gates"; case TargetMetric::Depth: return "depth"; + case TargetMetric::TwoQubitDepth: + return "two_qubit_depth"; } return "Error"; } @@ -34,6 +36,9 @@ targetMetricFromString(const std::string& target) { if (target == "depth") { return TargetMetric::Depth; } + if (target == "two_qubit_depth") { + return TargetMetric::TwoQubitDepth; + } return TargetMetric::Gates; } } // namespace cs diff --git a/include/cliffordsynthesis/encoding/GateEncoder.hpp b/include/cliffordsynthesis/encoding/GateEncoder.hpp index ac5f62348..f1fd490d7 100644 --- a/include/cliffordsynthesis/encoding/GateEncoder.hpp +++ b/include/cliffordsynthesis/encoding/GateEncoder.hpp @@ -40,8 +40,8 @@ class GateEncoder { }; // variable creation - void createSingleQubitGateVariables(); - void createTwoQubitGateVariables(); + virtual void createSingleQubitGateVariables(); + virtual void createTwoQubitGateVariables(); // encode the relation between the tableaus and the gates virtual void encodeGates() { @@ -52,13 +52,17 @@ class GateEncoder { virtual void encodeSymmetryBreakingConstraints(); // extracting the circuit - void extractCircuitFromModel(Results& res, logicbase::Model& model); + virtual void extractCircuitFromModel(Results& res, logicbase::Model& model); [[nodiscard]] auto* getVariables() { return &vars; } - static constexpr std::array SINGLE_QUBIT_GATES = { - qc::OpType::None, qc::OpType::X, qc::OpType::Y, qc::OpType::Z, - qc::OpType::H, qc::OpType::S, qc::OpType::Sdag}; + // TODO: set back + static constexpr std::array SINGLE_QUBIT_GATES = { + qc::OpType::None, qc::OpType::H, qc::OpType::S, qc::OpType::SX}; + + // static constexpr std::array SINGLE_QUBIT_GATES = { + // qc::OpType::None, qc::OpType::X, qc::OpType::Y, qc::OpType::Z, + // qc::OpType::H, qc::OpType::S, qc::OpType::Sdag}; [[nodiscard]] static constexpr std::size_t gateToIndex(const qc::OpType type) { @@ -130,9 +134,9 @@ class GateEncoder { virtual void assertSingleQubitGateConstraints(std::size_t pos) = 0; virtual void assertTwoQubitGateConstraints(std::size_t pos) = 0; [[nodiscard]] static std::vector - collectGateTransformations(std::size_t pos, std::size_t qubit, - const GateToTransformation& gateToTransformation); - void assertGatesImplyTransform( + collectGateTransformations(std::size_t pos, std::size_t qubit, + const GateToTransformation& gateToTransformation); + virtual void assertGatesImplyTransform( std::size_t pos, std::size_t qubit, const std::vector& transformations); virtual void assertZConstraints(std::size_t pos, std::size_t qubit); @@ -142,10 +146,10 @@ class GateEncoder { createTwoQubitGateConstraint(std::size_t pos, std::size_t ctrl, std::size_t trgt) = 0; - void extractSingleQubitGatesFromModel(std::size_t pos, - logicbase::Model& model, - qc::QuantumComputation& qc, - std::size_t& nSingleQubitGates); + virtual void extractSingleQubitGatesFromModel(std::size_t pos, + logicbase::Model& model, + qc::QuantumComputation& qc, + std::size_t& nSingleQubitGates); void extractTwoQubitGatesFromModel(std::size_t pos, logicbase::Model& model, qc::QuantumComputation& qc, std::size_t& nTwoQubitGates); diff --git a/include/cliffordsynthesis/encoding/SATEncoder.hpp b/include/cliffordsynthesis/encoding/SATEncoder.hpp index e017fa69a..f216fbba7 100644 --- a/include/cliffordsynthesis/encoding/SATEncoder.hpp +++ b/include/cliffordsynthesis/encoding/SATEncoder.hpp @@ -50,6 +50,12 @@ class SATEncoder { // whether to use symmetry breaking bool useSymmetryBreaking = false; + // whether to use two qubit encoding + bool useTwoQubitEncoding = false; + + // the number of threads to pass to the SAT solver + std::size_t nThreads = 1U; + // an optional limit on the total number of gates std::optional gateLimit = std::nullopt; diff --git a/include/cliffordsynthesis/encoding/TwoQubitEncoder.hpp b/include/cliffordsynthesis/encoding/TwoQubitEncoder.hpp new file mode 100644 index 000000000..9fd077f74 --- /dev/null +++ b/include/cliffordsynthesis/encoding/TwoQubitEncoder.hpp @@ -0,0 +1,77 @@ +// +// Created by Velsh Aleksei on 16.06.23. +// + +#pragma once + +#include "cliffordsynthesis/encoding/GateEncoder.hpp" + +#include +#include + +namespace cs::encoding { + +class TwoQubitEncoder : public GateEncoder { +public: + using GateEncoder::GateEncoder; + +protected: + // define variables + logicbase::LogicTerm rChanges{}; + logicbase::LogicMatrix xorHelpers{}; + logicbase::LogicMatrix gP{}; + + static constexpr std::array PAULI_GATES = { + qc::OpType::None, qc::OpType::X, qc::OpType::Y, qc::OpType::Z}; + + [[nodiscard]] constexpr std::size_t + paulieGateToIndex(const qc::OpType type) { + for (std::size_t i = 0; i < PAULI_GATES.size(); ++i) { + if (PAULI_GATES.at(i) == type) { + return i; + } + } + return 0; + } + + // variable creation + virtual void createSingleQubitGateVariables() override; + virtual void createTwoQubitGateVariables() override; + void createPauliGateVariables(); + + // create constrains + [[nodiscard]] logicbase::LogicTerm + createTwoQubitGateConstraint(std::size_t pos, std::size_t ctrl, + std::size_t trgt) override; + + // assert constrains + void assertConsistency() const override; + void assertGateConstraints() override; + void assertRConstraints(std::size_t pos, std::size_t qubit) override; + void assertSingleQubitGateConstraints(std::size_t pos) override; + void assertPauliGateConstraints(std::size_t pos); + void assertTwoQubitGateConstraints(std::size_t pos) override; + void assertSingleQubitGateOrderConstraints(std::size_t pos, + std::size_t qubit) override; + void assertTwoQubitGateOrderConstraints(std::size_t pos, std::size_t ctrl, + std::size_t trgt) override; + + // collect TQG variables + void collectPauliGateVariables(std::size_t qubit, + logicbase::LogicVector& variables) const; + + // extracting + void extractCircuitFromModel(Results& res, logicbase::Model& model) override; + void extractPauliGatesFromModel(logicbase::Model& model, + qc::QuantumComputation& qc, + std::size_t& nSingleQubitGates); + + // helpers + void splitXorR(const logicbase::LogicTerm& changes, std::size_t pos); + virtual void assertGatesImplyTransform( + std::size_t pos, std::size_t qubit, + const std::vector& transformations) override; + virtual void encodeSymmetryBreakingConstraints() override; +}; + +} // namespace cs::encoding diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 65b087cb2..16139b5ce 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -60,6 +60,7 @@ macro(ADD_SYNTHESIS_LIBRARY libname srcfile) ${PROJECT_SOURCE_DIR}/include/cliffordsynthesis/Configuration.hpp ${PROJECT_SOURCE_DIR}/include/cliffordsynthesis/encoding/GateEncoder.hpp ${PROJECT_SOURCE_DIR}/include/cliffordsynthesis/encoding/MultiGateEncoder.hpp + ${PROJECT_SOURCE_DIR}/include/cliffordsynthesis/encoding/TwoQubitEncoder.hpp ${PROJECT_SOURCE_DIR}/include/cliffordsynthesis/encoding/ObjectiveEncoder.hpp ${PROJECT_SOURCE_DIR}/include/cliffordsynthesis/encoding/SATEncoder.hpp ${PROJECT_SOURCE_DIR}/include/cliffordsynthesis/encoding/SingleGateEncoder.hpp @@ -70,6 +71,7 @@ macro(ADD_SYNTHESIS_LIBRARY libname srcfile) ${PROJECT_SOURCE_DIR}/include/utils.hpp cliffordsynthesis/encoding/GateEncoder.cpp cliffordsynthesis/encoding/MultiGateEncoder.cpp + cliffordsynthesis/encoding/TwoQubitEncoder.cpp cliffordsynthesis/encoding/ObjectiveEncoder.cpp cliffordsynthesis/encoding/SATEncoder.cpp cliffordsynthesis/encoding/SingleGateEncoder.cpp diff --git a/src/cliffordsynthesis/CliffordSynthesizer.cpp b/src/cliffordsynthesis/CliffordSynthesizer.cpp index e8f3a6880..3fb02e32c 100644 --- a/src/cliffordsynthesis/CliffordSynthesizer.cpp +++ b/src/cliffordsynthesis/CliffordSynthesizer.cpp @@ -5,7 +5,6 @@ #include "cliffordsynthesis/CliffordSynthesizer.hpp" -#include "LogicTerm/Logic.hpp" #include "QuantumComputation.hpp" #include "cliffordsynthesis/Tableau.hpp" #include "utils/logging.hpp" @@ -13,6 +12,7 @@ #include #include #include +#include #include #include @@ -44,6 +44,8 @@ void CliffordSynthesizer::synthesize(const Configuration& config) { encoderConfig.solverParameters = configuration.solverParameters; encoderConfig.useMultiGateEncoding = requiresMultiGateEncoding(encoderConfig.targetMetric); + encoderConfig.useTwoQubitEncoding = + requiresTwoQubitEncoding(encoderConfig.targetMetric); if (configuration.heuristic) { if (initialCircuit->empty() && !targetTableau.isIdentityTableau()) { @@ -97,6 +99,9 @@ void CliffordSynthesizer::synthesize(const Configuration& config) { case TargetMetric::TwoQubitGates: twoQubitGateOptimalSynthesis(encoderConfig, 0U, results.getTwoQubitGates()); break; + case TargetMetric::TwoQubitDepth: + twoQubitDepthOptimalSynthesis(encoderConfig, lower, upper); + break; } results.setSolverCalls(solverCalls); @@ -130,6 +135,11 @@ void CliffordSynthesizer::determineInitialTimestepLimit(EncoderConfig& config) { config.timestepLimit = results.getDepth(); INFO() << "Using initial circuit's depth as initial timestep limit: " << config.timestepLimit; + } else if (requiresTwoQubitEncoding(config.targetMetric)) { + config.timestepLimit = results.getTwoQubitDepth(); + INFO() + << "Using initial circuit's two qubit depth as initial timestep limit: " + << config.timestepLimit; } else { config.timestepLimit = results.getGates(); INFO() << "Using initial circuit's gate count as initial timestep limit: " @@ -170,6 +180,8 @@ CliffordSynthesizer::determineUpperBound(EncoderConfig config) { upperBound = std::min(upperBound, results.getGates()); } else if (config.targetMetric == TargetMetric::Depth) { upperBound = std::min(upperBound, results.getDepth()); + } else if (config.targetMetric == TargetMetric::TwoQubitDepth) { + upperBound = std::min(upperBound, results.getTwoQubitDepth()); } INFO() << "Found upper bound for the number of timesteps: " << upperBound; @@ -237,6 +249,28 @@ void CliffordSynthesizer::depthOptimalSynthesis( } } +void CliffordSynthesizer::twoQubitDepthOptimalSynthesis( + CliffordSynthesizer::EncoderConfig config, const std::size_t lower, + const std::size_t upper) { + // TwoQubitDepth-optimal synthesis is achieved by determining a timestep limit + // T such that there exists a solution with twoQubitDepth T, but no solution + // with twoQubitDepth T-1. This procedure uses an TwoQubitEncoding (SQG - TQG) + // where SQG layer consists only of single qubit gates in the time step t and + // TQG can consist only of two qubit gates in the time step t+1. This + // procedure is guaranteed to produce a TwoQubitdepth-optimal circuit. + // However, the number of gates in the resulting circuit is not necessarily + // minimal, i.e., there may be a solution with fewer gates and the same depth. + + if (configuration.linearSearch) { + runLinearSearch(config.timestepLimit, lower, upper, config); + } else { + // The binary search approach calls the SAT solver repeatedly with varying + // timestep (=TwoQubitDepth) limits T until a solution with twoQubitDepth T + // is found, but no solution with twoQubitDepth T-1 could be determined. + runBinarySearch(config.timestepLimit, lower, upper, config); + } +} + void CliffordSynthesizer::minimizeGatesFixedDepth(EncoderConfig config) { if (results.getDepth() == 0U) { return; @@ -427,6 +461,13 @@ void CliffordSynthesizer::updateResults(const Configuration& config, currentResults = newResults; } break; + case TargetMetric::TwoQubitDepth: + if ((newResults.getTwoQubitDepth() < currentResults.getTwoQubitDepth()) || + ((newResults.getTwoQubitDepth() == currentResults.getTwoQubitDepth()) && + (newResults.getGates() < currentResults.getGates()))) { + currentResults = newResults; + } + break; } } diff --git a/src/cliffordsynthesis/encoding/GateEncoder.cpp b/src/cliffordsynthesis/encoding/GateEncoder.cpp index 05adcfcd8..6a555e88d 100644 --- a/src/cliffordsynthesis/encoding/GateEncoder.cpp +++ b/src/cliffordsynthesis/encoding/GateEncoder.cpp @@ -6,6 +6,7 @@ #include "cliffordsynthesis/encoding/GateEncoder.hpp" #include "Encodings/Encodings.hpp" +#include "operations/OpType.hpp" #include "utils/logging.hpp" namespace cs::encoding { @@ -34,7 +35,7 @@ void GateEncoder::createSingleQubitGateVariables() { void GateEncoder::createTwoQubitGateVariables() { DEBUG() << "Creating two-qubit gate variables."; vars.gC.reserve(T); - for (std::size_t t = 0U; t < T; ++t) { + for (std::size_t t = 0; t < T; ++t) { auto& timeStep = vars.gC.emplace_back(); timeStep.reserve(N); for (std::size_t ctrl = 0U; ctrl < N; ++ctrl) { @@ -173,12 +174,14 @@ void GateEncoder::extractCircuitFromModel(Results& res, Model& model) { res.setSingleQubitGates(nSingleQubitGates); res.setTwoQubitGates(nTwoQubitGates); res.setDepth(qc.getDepth()); + res.setTwoQubitDepth(qc.getTwoQubitDepth()); res.setResultCircuit(qc); } void GateEncoder::extractSingleQubitGatesFromModel( const std::size_t pos, Model& model, qc::QuantumComputation& qc, std::size_t& nSingleQubitGates) { + DEBUG() << "Extract single qubit gates from model"; const auto& singleQubitGates = vars.gS[pos]; for (std::size_t q = 0U; q < N; ++q) { for (const auto gate : SINGLE_QUBIT_GATES) { @@ -199,6 +202,7 @@ void GateEncoder::extractTwoQubitGatesFromModel(const std::size_t pos, Model& model, qc::QuantumComputation& qc, size_t& nTwoQubitGates) { + DEBUG() << "Extract two qubit gates from model"; const auto& twoQubitGates = vars.gC[pos]; for (std::size_t ctrl = 0U; ctrl < N; ++ctrl) { for (std::size_t trgt = 0U; trgt < N; ++trgt) { diff --git a/src/cliffordsynthesis/encoding/SATEncoder.cpp b/src/cliffordsynthesis/encoding/SATEncoder.cpp index 4fcbf4aaf..02502f0d4 100644 --- a/src/cliffordsynthesis/encoding/SATEncoder.cpp +++ b/src/cliffordsynthesis/encoding/SATEncoder.cpp @@ -8,6 +8,7 @@ #include "LogicUtil/util_logicblock.hpp" #include "cliffordsynthesis/encoding/MultiGateEncoder.hpp" #include "cliffordsynthesis/encoding/SingleGateEncoder.hpp" +#include "cliffordsynthesis/encoding/TwoQubitEncoder.hpp" #include "utils/logging.hpp" #include @@ -15,7 +16,6 @@ namespace cs::encoding { using namespace logicbase; - void SATEncoder::initializeSolver() { DEBUG() << "Initializing solver engine."; bool success = false; @@ -55,6 +55,10 @@ void SATEncoder::createFormulation() { ? 2U * N : N; + if (config.useTwoQubitEncoding) { + T = T * 2U + 2U; + } + tableauEncoder = std::make_shared(N, s, T, lb); tableauEncoder->createTableauVariables(); tableauEncoder->assertTableau(*config.initialTableau, 0U); @@ -63,12 +67,18 @@ void SATEncoder::createFormulation() { if (config.useMultiGateEncoding) { gateEncoder = std::make_shared( N, s, T, tableauEncoder->getVariables(), lb); + } else if (config.useTwoQubitEncoding) { + T -= 1; + gateEncoder = std::make_shared( + N, s, T, tableauEncoder->getVariables(), lb); } else { gateEncoder = std::make_shared( N, s, T, tableauEncoder->getVariables(), lb); } + gateEncoder->createSingleQubitGateVariables(); gateEncoder->createTwoQubitGateVariables(); + gateEncoder->encodeGates(); if (config.useSymmetryBreaking) { @@ -122,6 +132,7 @@ void SATEncoder::cleanup() const { lb->reset(); } } + Results SATEncoder::run() { const auto start = std::chrono::high_resolution_clock::now(); diff --git a/src/cliffordsynthesis/encoding/TableauEncoder.cpp b/src/cliffordsynthesis/encoding/TableauEncoder.cpp index b16a49b5e..05aa8af5f 100644 --- a/src/cliffordsynthesis/encoding/TableauEncoder.cpp +++ b/src/cliffordsynthesis/encoding/TableauEncoder.cpp @@ -87,6 +87,8 @@ TableauEncoder::Variables::singleQubitXChange(const std::size_t pos, return x[pos][qubit]; case qc::OpType::H: return z[pos][qubit]; + case qc::OpType::SX: + return z[pos][qubit] ^ x[pos][qubit]; default: FATAL() << "Unsupported single-qubit gate: " << toString(gate); return LogicTerm::noneTerm(); @@ -102,12 +104,13 @@ TableauEncoder::Variables::singleQubitZChange(const std::size_t pos, case qc::OpType::X: case qc::OpType::Y: case qc::OpType::Z: + case qc::OpType::SX: return z[pos][qubit]; case qc::OpType::H: return x[pos][qubit]; case qc::OpType::S: case qc::OpType::Sdag: - return (z[pos][qubit] ^ x[pos][qubit]); + return z[pos][qubit] ^ x[pos][qubit]; default: FATAL() << "Unsupported single-qubit gate: " << toString(gate); return LogicTerm::noneTerm(); @@ -134,6 +137,8 @@ TableauEncoder::Variables::singleQubitRChange(const std::size_t pos, return x[pos][qubit] ^ z[pos][qubit]; case qc::OpType::Z: return x[pos][qubit]; + case qc::OpType::SX: + return z[pos][qubit] & (x[pos][qubit] ^ z[pos][qubit]); default: FATAL() << "Unsupported single-qubit gate: " << toString(gate); return LogicTerm::noneTerm(); diff --git a/src/cliffordsynthesis/encoding/TwoQubitEncoder.cpp b/src/cliffordsynthesis/encoding/TwoQubitEncoder.cpp new file mode 100644 index 000000000..1e236f439 --- /dev/null +++ b/src/cliffordsynthesis/encoding/TwoQubitEncoder.cpp @@ -0,0 +1,333 @@ +// +// Created by Velsh Aleksei on 16.06.23. +// + +#include "cliffordsynthesis/encoding/TwoQubitEncoder.hpp" + +#include "LogicTerm/LogicTerm.hpp" +#include "operations/OpType.hpp" +#include "utils/logging.hpp" + +namespace cs::encoding { + +using namespace logicbase; + +void TwoQubitEncoder::createSingleQubitGateVariables() { + DEBUG() << "Creating single-qubit gate variables."; + vars.gS.reserve(T/2 + 1); + for (std::size_t t = 0U; t < T; t += 2U) { + auto& timeStep = vars.gS.emplace_back(); + timeStep.reserve(SINGLE_QUBIT_GATES.size()); + for (const auto gate : SINGLE_QUBIT_GATES) { + auto& g = timeStep.emplace_back(); + g.reserve(N); + for (std::size_t q = 0U; q < N; ++q) { + const std::string gName = "g_" + std::to_string(t) + "_" + + toString(gate) + "_" + std::to_string(q); + TRACE() << "Creating variable " << gName; + g.emplace_back(lb->makeVariable(gName)); + } + } + } + createPauliGateVariables(); +} + +void TwoQubitEncoder::createTwoQubitGateVariables() { + DEBUG() << "Creating two-qubit gate variables."; + vars.gC.reserve(T/2); + for (std::size_t t = 1U; t < T; t += 2U) { + auto& timeStep = vars.gC.emplace_back(); + timeStep.reserve(N); + for (std::size_t ctrl = 0U; ctrl < N; ++ctrl) { + auto& control = timeStep.emplace_back(); + control.reserve(N); + for (std::size_t trgt = 0U; trgt < N; ++trgt) { + const std::string gName = "g_" + std::to_string(t) + "_cx_" + + std::to_string(ctrl) + "_" + + std::to_string(trgt); + TRACE() << "Creating variable " << gName; + control.emplace_back(lb->makeVariable(gName)); + } + } + } +} + +void TwoQubitEncoder::createPauliGateVariables() { + DEBUG() << "Creating pauli-qubit gate variables."; + gP.reserve(PAULI_GATES.size()); + for (const auto gate : PAULI_GATES) { + auto& g = gP.emplace_back(); + g.reserve(N); + for (std::size_t q = 0U; q < N; ++q) { + const std::string gName = "g_" + std::to_string(T) + "_" + + toString(gate) + "_" + std::to_string(q); + TRACE() << "Creating variable " << gName; + g.emplace_back(lb->makeVariable(gName)); + } + } +} + +void encoding::TwoQubitEncoder::assertConsistency() const { + DEBUG() << "Asserting gate consistency"; + for (std::size_t t = 0U; t < T - 1; t += 2U) { + // asserting only a single gate is applied on each qubit. + for (std::size_t q = 0U; q < N; ++q) { + LogicVector singleQubitGateVariables{}; + LogicVector twoQubitGateVariables{}; + + vars.collectSingleQubitGateVariables(t/2, q, singleQubitGateVariables); + assertExactlyOne(singleQubitGateVariables); + IF_PLOG(plog::verbose) { + TRACE() << "Single Qubit Gate variables at time " << t + << " and qubit " << q; + for (const auto& var : singleQubitGateVariables) { + TRACE() << var.getName(); + } + } + + vars.collectTwoQubitGateVariables(t/2, q, true, twoQubitGateVariables); + vars.collectTwoQubitGateVariables(t/2, q, false, twoQubitGateVariables); + + // add a variable that will be treated as identity + twoQubitGateVariables.emplace_back(vars.gC[t/2][q][q]); + + assertExactlyOne(twoQubitGateVariables); + IF_PLOG(plog::verbose) { + std::cout << twoQubitGateVariables.size() << std::endl; + + TRACE() << "Two Qubit Gate variables at time " << t + 1 << " and qubit " + << q; + for (const auto& var : twoQubitGateVariables) { + TRACE() << var.getName(); + } + } + } + } + + for (std::size_t q = 0U; q < N; ++q) { + LogicVector pauliGateVariables{}; + LogicVector singleQubitGateVariables{}; + vars.collectSingleQubitGateVariables(T/2, q, singleQubitGateVariables); + assertExactlyOne(singleQubitGateVariables); + IF_PLOG(plog::verbose) { + TRACE() << "Single Qubit Gate variables at time " << T - 1 + << " and qubit " << q; + for (const auto& var : singleQubitGateVariables) { + TRACE() << var.getName(); + } + } + + collectPauliGateVariables(q, pauliGateVariables); + assertExactlyOne(pauliGateVariables); + IF_PLOG(plog::verbose) { + TRACE() << "Pauli Qubit Gate variables at time " << T + << " and qubit " << q; + for (const auto& var : pauliGateVariables) { + TRACE() << var.getName(); + } + } + } +} + +void encoding::TwoQubitEncoder::assertGateConstraints() { + DEBUG() << "Asserting gate constraints"; + xorHelpers = logicbase::LogicMatrix{T + 1}; + for (std::size_t t = 0U; t < T; ++t) { + TRACE() << "Asserting gate constraints at time " << t; + splitXorR(tvars->r[t], t); + if (t % 2 == 0) { + assertSingleQubitGateConstraints(t); + } else { + assertTwoQubitGateConstraints(t); + } + TRACE() << "Asserting r changes at time " << t; + lb->assertFormula(tvars->r[t + 1] == xorHelpers[t].back()); + } + + TRACE() << "Asserting pauli gate constraints at time " << T; + splitXorR(tvars->r[T], T); + assertPauliGateConstraints(T); + TRACE() << "Asserting r changes at time " << T; + lb->assertFormula(tvars->r[T + 1] == xorHelpers[T].back()); +} + +void encoding::TwoQubitEncoder::assertSingleQubitGateConstraints( + const std::size_t pos) { + for (std::size_t q = 0U; q < N; ++q) { + assertZConstraints(pos, q); + assertXConstraints(pos, q); + assertRConstraints(pos, q); + } +} + +void TwoQubitEncoder::assertRConstraints(const std::size_t pos, + const std::size_t qubit) { + for (const auto gate : SINGLE_QUBIT_GATES) { + const auto& change = + LogicTerm::ite(vars.gS[pos/2][gateToIndex(gate)][qubit], + tvars->singleQubitRChange(pos, qubit, gate), + LogicTerm(0, static_cast(S))); + splitXorR(change, pos); + } +} + +void encoding::TwoQubitEncoder::assertTwoQubitGateConstraints( + const std::size_t pos) { + const auto& twoQubitGates = vars.gC[pos/2]; + for (std::size_t ctrl = 0U; ctrl < N; ++ctrl) { + for (std::size_t trgt = 0U; trgt < N; ++trgt) { + if (ctrl == trgt) { + // create identity constraint on TQG + auto changes = tvars->x[pos + 1][ctrl] == tvars->x[pos][ctrl]; + changes = changes && (tvars->z[pos + 1][ctrl] == + tvars->z[pos][ctrl]); // && here is overloaded + + lb->assertFormula( + LogicTerm::implies(twoQubitGates[ctrl][ctrl], changes)); + splitXorR(tvars->singleQubitRChange(pos, ctrl, qc::OpType::None), pos); + } else { + const auto changes = createTwoQubitGateConstraint(pos, ctrl, trgt); + lb->assertFormula( + LogicTerm::implies(twoQubitGates[ctrl][trgt], changes)); + DEBUG() << "Asserting CNOT on " << ctrl << " and " << trgt; + } + } + } +} + +void encoding::TwoQubitEncoder::assertPauliGateConstraints( + const std::size_t pos) { + for (std::size_t q = 0U; q < N; ++q) { + lb->assertFormula(tvars->x[pos][q] == tvars->x[pos+1][q]); + lb->assertFormula(tvars->z[pos][q] == tvars->z[pos+1][q]); + for (const auto gate : PAULI_GATES) { + const auto& change = + LogicTerm::ite(gP[paulieGateToIndex(gate)][q], + tvars->singleQubitRChange(pos, q, gate), + LogicTerm(0, static_cast(S))); + + splitXorR(change, pos); + } + } +} + +void TwoQubitEncoder::assertGatesImplyTransform( + const std::size_t pos, const std::size_t qubit, + const std::vector& transformations) { + const auto& singleQubitGates = vars.gS[pos/2]; + for (const auto& [transformation, gates] : transformations) { + auto gateOr = LogicTerm(false); + for (const auto& gate : gates) { + gateOr = gateOr || singleQubitGates[gateToIndex(gate)][qubit]; + } + lb->assertFormula(LogicTerm::implies(gateOr, transformation)); + } +} + +void TwoQubitEncoder::splitXorR(const logicbase::LogicTerm& changes, + std::size_t pos) { + auto& xorHelper = xorHelpers[pos]; + const std::string hName = + "h_" + std::to_string(pos) + "_" + std::to_string(xorHelper.size()); + DEBUG() << "Creating helper variable for RChange XOR " << hName; + const auto n = static_cast(S); + xorHelper.emplace_back(lb->makeVariable(hName, CType::BITVECTOR, n)); + if (xorHelper.size() == 1) { + lb->assertFormula(xorHelper.back() == changes); + } else { + lb->assertFormula(xorHelper.back() == + (xorHelper[xorHelpers[pos].size() - 2] ^ changes)); + } +} + +void encoding::TwoQubitEncoder::collectPauliGateVariables( + const std::size_t qubit, LogicVector& variables) const { + for (const auto& gate : gP) { + variables.emplace_back(gate[qubit]); + } +} + +LogicTerm encoding::TwoQubitEncoder::createTwoQubitGateConstraint( + std::size_t pos, std::size_t ctrl, std::size_t trgt) { + auto changes = LogicTerm(true); + const auto [xCtrl, xTrgt] = tvars->twoQubitXChange(pos, ctrl, trgt); + const auto [zCtrl, zTrgt] = tvars->twoQubitZChange(pos, ctrl, trgt); + + changes = changes && (tvars->x[pos + 1][ctrl] == xCtrl); + changes = changes && (tvars->x[pos + 1][trgt] == xTrgt); + changes = changes && (tvars->z[pos + 1][ctrl] == zCtrl); + changes = changes && (tvars->z[pos + 1][trgt] == zTrgt); + + const auto& newRChanges = LogicTerm::ite( + vars.gC[pos/2][ctrl][trgt], tvars->twoQubitRChange(pos, ctrl, trgt), + LogicTerm(0, static_cast(S))); + splitXorR(newRChanges, pos); + + return changes; +} + +void TwoQubitEncoder::extractCircuitFromModel(Results& res, Model& model) { + std::size_t nSingleQubitGates = 0U; + std::size_t nTwoQubitGates = 0U; + + qc::QuantumComputation qc(N); + for (std::size_t t = 0; t < T/2; ++t) { + extractSingleQubitGatesFromModel(t, model, qc, nSingleQubitGates); + extractTwoQubitGatesFromModel(t, model, qc, nTwoQubitGates); + } + //extract the last extra added SQG layer and the pauli layer + extractSingleQubitGatesFromModel(T/2, model, qc, nSingleQubitGates); + extractPauliGatesFromModel(model, qc, nSingleQubitGates); + + res.setSingleQubitGates(nSingleQubitGates); + res.setTwoQubitGates(nTwoQubitGates); + res.setDepth(qc.getDepth()); + res.setTwoQubitDepth(qc.getTwoQubitDepth()); + res.setResultCircuit(qc); +} + +void TwoQubitEncoder::extractPauliGatesFromModel( + Model& model, qc::QuantumComputation& qc, + std::size_t& nSingleQubitGates) { + DEBUG() << "Extract pauli gates from model"; + for (std::size_t q = 0U; q < N; ++q) { + for (const auto gate : PAULI_GATES) { + if (gate == qc::OpType::None) { + continue; + } + if (model.getBoolValue(gP[paulieGateToIndex(gate)][q], + lb.get())) { + qc.emplace_back(N, q, gate); + ++nSingleQubitGates; + DEBUG() << toString(gate) << "(" << q << ")"; + } + } + } +} + +// mock-functions +void TwoQubitEncoder::encodeSymmetryBreakingConstraints() { + DEBUG() << "Encoding symmetry breaking constraints IS NOT supported for the two qubit encoding."; +} + +void TwoQubitEncoder::assertSingleQubitGateOrderConstraints( + const std::size_t pos, const std::size_t qubit) { + std::ostringstream posToStr; + std::ostringstream qubitToStr; + + posToStr << pos; + qubitToStr << qubit; +} + +// mock-functions +void TwoQubitEncoder::assertTwoQubitGateOrderConstraints( + const std::size_t pos, const std::size_t ctrl, const std::size_t trgt) { + std::ostringstream posToStr; + std::ostringstream ctrlToStr; + std::ostringstream trgtToStr; + + posToStr << pos; + ctrlToStr << ctrl; + trgtToStr << trgt; +} +} // namespace cs::encoding diff --git a/src/mqt/qmap/pyqmap.pyi b/src/mqt/qmap/pyqmap.pyi index b472c0962..cfbb0fff8 100644 --- a/src/mqt/qmap/pyqmap.pyi +++ b/src/mqt/qmap/pyqmap.pyi @@ -272,6 +272,7 @@ def map(circ: str | QuantumCircuit, arch: Architecture, config: Configuration) - class TargetMetric: __members__: ClassVar[dict[TargetMetric, int]] = ... # read-only depth: ClassVar[TargetMetric] = ... + two_qubit_depth: ClassVar[TargetMetric] = ... gates: ClassVar[TargetMetric] = ... two_qubit_gates: ClassVar[TargetMetric] = ... @overload @@ -347,6 +348,8 @@ class SynthesisResults: @property def depth(self) -> int: ... @property + def two_qubit_depth(self) -> int: ... + @property def gates(self) -> int: ... @property def runtime(self) -> float: ... diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp index c3719f6d3..208a3c21c 100644 --- a/src/python/bindings.cpp +++ b/src/python/bindings.cpp @@ -400,6 +400,8 @@ PYBIND11_MODULE(pyqmap, m) { .value("two_qubit_gates", cs::TargetMetric::TwoQubitGates, "Optimize two-qubit gate count.") .value("depth", cs::TargetMetric::Depth, "Optimize circuit depth.") + .value("two_qubit_depth", cs::TargetMetric::TwoQubitDepth, + "Optimize circuit twoQubitDepth.") .export_values() .def(py::init([](const std::string& name) { return cs::targetMetricFromString(name); @@ -528,6 +530,9 @@ PYBIND11_MODULE(pyqmap, m) { "synthesized circuit.") .def_property_readonly("depth", &cs::Results::getDepth, "Returns the depth of the synthesized circuit.") + .def_property_readonly( + "two_qubit_depth", &cs::Results::getTwoQubitDepth, + "Returns the TwoQubitDepth of the synthesized circuit.") .def_property_readonly("runtime", &cs::Results::getRuntime, "Returns the runtime of the synthesis in seconds.") .def_property_readonly("solver_calls", &cs::Results::getSolverCalls, diff --git a/test/cliffordsynthesis/circuits.json b/test/cliffordsynthesis/circuits.json index 00fb48e24..a3cb2c945 100644 --- a/test/cliffordsynthesis/circuits.json +++ b/test/cliffordsynthesis/circuits.json @@ -4,6 +4,7 @@ "initial_circuit": "OPENQASM 2.0;include \"qelib1.inc\";qreg q[1];\n", "expected_minimal_gates": 0, "expected_minimal_depth": 0, + "expected_minimal_two_qubit_depth": 0, "expected_minimal_gates_at_minimal_depth": 0, "expected_minimal_two_qubit_gates": 0, "expected_minimal_gates_at_minimal_two_qubit_gates": 0 @@ -13,6 +14,7 @@ "initial_circuit": "OPENQASM 2.0;include \"qelib1.inc\";qreg q[2];h q; cx q[1], q[0];h q;\n", "expected_minimal_gates": 1, "expected_minimal_depth": 1, + "expected_minimal_two_qubit_depth": 1, "expected_minimal_gates_at_minimal_depth": 1, "expected_minimal_two_qubit_gates": 1, "expected_minimal_gates_at_minimal_two_qubit_gates": 1 @@ -22,6 +24,7 @@ "initial_circuit": "OPENQASM 2.0;include \"qelib1.inc\";qreg q[1];h q;h q;h q;h q;\n", "expected_minimal_gates": 0, "expected_minimal_depth": 0, + "expected_minimal_two_qubit_depth": 0, "expected_minimal_gates_at_minimal_depth": 0, "expected_minimal_two_qubit_gates": 0, "expected_minimal_gates_at_minimal_two_qubit_gates": 0 @@ -31,6 +34,7 @@ "initial_circuit": "OPENQASM 2.0;include \"qelib1.inc\";qreg q[2];swap q[0], q[1]; cx q[0], q[1];\n", "expected_minimal_gates": 2, "expected_minimal_depth": 2, + "expected_minimal_two_qubit_depth": 2, "expected_minimal_gates_at_minimal_depth": 2, "expected_minimal_two_qubit_gates": 2, "expected_minimal_gates_at_minimal_two_qubit_gates": 2 @@ -40,8 +44,29 @@ "initial_circuit": "OPENQASM 2.0;include \"qelib1.inc\";qreg q[2];x q; z q[0];cz q[0], q[1]; z q[0];\n", "expected_minimal_gates": 2, "expected_minimal_depth": 1, + "expected_minimal_two_qubit_depth": 0, "expected_minimal_gates_at_minimal_depth": 2, "expected_minimal_two_qubit_gates": 0, "expected_minimal_gates_at_minimal_two_qubit_gates": 2 + }, + { + "description": "TwoQubitEncoding", + "initial_circuit": "OPENQASM 2.0;include \"qelib1.inc\";qreg q[2];z q[0]; s q[0]; cx q[0], q[1]; s q[0];\n", + "expected_minimal_gates": 1, + "expected_minimal_depth": 1, + "expected_minimal_gates_at_minimal_depth": 1, + "expected_minimal_two_qubit_gates": 2, + "expected_minimal_gates_at_minimal_two_qubit_gates": 1, + "expected_minimal_two_qubit_depth": 1 + }, + { + "description": "TwoQubitEncodingWithPaulis", + "initial_circuit": "OPENQASM 2.0;include \"qelib1.inc\";qreg q[2];z q[0]; s q[0]; cx q[0], q[1]; s q[0]; x q[0];\n", + "expected_minimal_gates": 1, + "expected_minimal_depth": 1, + "expected_minimal_gates_at_minimal_depth": 1, + "expected_minimal_two_qubit_gates": 2, + "expected_minimal_gates_at_minimal_two_qubit_gates": 1, + "expected_minimal_two_qubit_depth": 1 } ] diff --git a/test/cliffordsynthesis/tableaus.json b/test/cliffordsynthesis/tableaus.json index 9c07e4046..815d404b3 100644 --- a/test/cliffordsynthesis/tableaus.json +++ b/test/cliffordsynthesis/tableaus.json @@ -5,6 +5,7 @@ "target_tableau": "0;1;0;", "expected_minimal_gates": 0, "expected_minimal_depth": 0, + "expected_minimal_two_qubit_depth": 0, "expected_minimal_gates_at_minimal_depth": 0, "expected_minimal_two_qubit_gates": 0, "expected_minimal_gates_at_minimal_two_qubit_gates": 0 @@ -15,6 +16,7 @@ "target_tableau": "0;1;1;", "expected_minimal_gates": 1, "expected_minimal_depth": 1, + "expected_minimal_two_qubit_depth": 0, "expected_minimal_gates_at_minimal_depth": 1, "expected_minimal_two_qubit_gates": 0, "expected_minimal_gates_at_minimal_two_qubit_gates": 1 @@ -25,6 +27,7 @@ "target_tableau": "1;0;0;", "expected_minimal_gates": 1, "expected_minimal_depth": 1, + "expected_minimal_two_qubit_depth": 0, "expected_minimal_gates_at_minimal_depth": 1, "expected_minimal_two_qubit_gates": 0, "expected_minimal_gates_at_minimal_two_qubit_gates": 1 @@ -35,6 +38,7 @@ "target_tableau": "1;0;1;", "expected_minimal_gates": 2, "expected_minimal_depth": 2, + "expected_minimal_two_qubit_depth": 0, "expected_minimal_gates_at_minimal_depth": 2, "expected_minimal_two_qubit_gates": 0, "expected_minimal_gates_at_minimal_two_qubit_gates": 2 @@ -45,6 +49,7 @@ "target_tableau": "1;1;0;", "expected_minimal_gates": 2, "expected_minimal_depth": 2, + "expected_minimal_two_qubit_depth": 0, "expected_minimal_gates_at_minimal_depth": 2, "expected_minimal_two_qubit_gates": 0, "expected_minimal_gates_at_minimal_two_qubit_gates": 2 @@ -55,6 +60,7 @@ "target_tableau": "1;1;1;", "expected_minimal_gates": 2, "expected_minimal_depth": 2, + "expected_minimal_two_qubit_depth": 0, "expected_minimal_gates_at_minimal_depth": 2, "expected_minimal_two_qubit_gates": 0, "expected_minimal_gates_at_minimal_two_qubit_gates": 2 @@ -65,6 +71,7 @@ "target_tableau": "1;1;0;", "expected_minimal_gates": 0, "expected_minimal_depth": 0, + "expected_minimal_two_qubit_depth": 0, "expected_minimal_gates_at_minimal_depth": 0, "expected_minimal_two_qubit_gates": 0, "expected_minimal_gates_at_minimal_two_qubit_gates": 0 @@ -76,6 +83,7 @@ "target_tableau": "0;0;1;0;0\n0;0;0;1;0", "expected_minimal_gates": 0, "expected_minimal_depth": 0, + "expected_minimal_two_qubit_depth": 0, "expected_minimal_gates_at_minimal_depth": 0, "expected_minimal_two_qubit_gates": 0, "expected_minimal_gates_at_minimal_two_qubit_gates": 0 @@ -86,6 +94,7 @@ "target_tableau": "0;0;1;0;0\n0;0;0;1;1", "expected_minimal_gates": 1, "expected_minimal_depth": 1, + "expected_minimal_two_qubit_depth": 0, "expected_minimal_gates_at_minimal_depth": 1, "expected_minimal_two_qubit_gates": 0, "expected_minimal_gates_at_minimal_two_qubit_gates": 1 @@ -96,6 +105,7 @@ "target_tableau": "0;0;1;0;1\n0;0;0;1;0", "expected_minimal_gates": 1, "expected_minimal_depth": 1, + "expected_minimal_two_qubit_depth": 0, "expected_minimal_gates_at_minimal_depth": 1, "expected_minimal_two_qubit_gates": 0, "expected_minimal_gates_at_minimal_two_qubit_gates": 1 @@ -106,6 +116,7 @@ "target_tableau": "0;0;1;0;1\n0;0;0;1;1", "expected_minimal_gates": 2, "expected_minimal_depth": 1, + "expected_minimal_two_qubit_depth": 0, "expected_minimal_gates_at_minimal_depth": 2, "expected_minimal_two_qubit_gates": 0, "expected_minimal_gates_at_minimal_two_qubit_gates": 2 @@ -117,6 +128,7 @@ "target_tableau": "1;0;0;0;0\n0;1;0;0;0", "expected_minimal_gates": 2, "expected_minimal_depth": 1, + "expected_minimal_two_qubit_depth": 0, "expected_minimal_gates_at_minimal_depth": 2, "expected_minimal_two_qubit_gates": 0, "expected_minimal_gates_at_minimal_two_qubit_gates": 2 @@ -127,6 +139,7 @@ "target_tableau": "1;0;0;0;0\n0;1;0;0;1", "expected_minimal_gates": 3, "expected_minimal_depth": 2, + "expected_minimal_two_qubit_depth": 2, "expected_minimal_gates_at_minimal_depth": 3, "expected_minimal_two_qubit_gates": 0, "expected_minimal_gates_at_minimal_two_qubit_gates": 3 @@ -137,6 +150,7 @@ "target_tableau": "1;0;0;0;1\n0;1;0;0;0", "expected_minimal_gates": 3, "expected_minimal_depth": 2, + "expected_minimal_two_qubit_depth": 2, "expected_minimal_gates_at_minimal_depth": 3, "expected_minimal_two_qubit_gates": 0, "expected_minimal_gates_at_minimal_two_qubit_gates": 3 @@ -147,6 +161,7 @@ "target_tableau": "1;0;0;0;1\n0;1;0;0;1", "expected_minimal_gates": 4, "expected_minimal_depth": 2, + "expected_minimal_two_qubit_depth": 2, "expected_minimal_gates_at_minimal_depth": 4, "expected_minimal_two_qubit_gates": 0, "expected_minimal_gates_at_minimal_two_qubit_gates": 4 @@ -158,6 +173,7 @@ "target_tableau": "1;0;1;0;0\n0;1;0;1;0", "expected_minimal_gates": 4, "expected_minimal_depth": 2, + "expected_minimal_two_qubit_depth": 0, "expected_minimal_gates_at_minimal_depth": 4, "expected_minimal_two_qubit_gates": 0, "expected_minimal_gates_at_minimal_two_qubit_gates": 4 @@ -168,6 +184,7 @@ "target_tableau": "1;0;1;0;0\n0;1;0;1;1", "expected_minimal_gates": 4, "expected_minimal_depth": 2, + "expected_minimal_two_qubit_depth": 0, "expected_minimal_gates_at_minimal_depth": 4, "expected_minimal_two_qubit_gates": 0, "expected_minimal_gates_at_minimal_two_qubit_gates": 4 @@ -178,6 +195,7 @@ "target_tableau": "1;0;1;0;1\n0;1;0;1;0", "expected_minimal_gates": 4, "expected_minimal_depth": 2, + "expected_minimal_two_qubit_depth": 0, "expected_minimal_gates_at_minimal_depth": 4, "expected_minimal_two_qubit_gates": 0, "expected_minimal_gates_at_minimal_two_qubit_gates": 4 @@ -188,6 +206,7 @@ "target_tableau": "1;0;1;0;1\n0;1;0;1;1", "expected_minimal_gates": 4, "expected_minimal_depth": 2, + "expected_minimal_two_qubit_depth": 0, "expected_minimal_gates_at_minimal_depth": 4, "expected_minimal_two_qubit_gates": 0, "expected_minimal_gates_at_minimal_two_qubit_gates": 4 @@ -199,6 +218,7 @@ "target_tableau": "1;1;0;0;0\n0;0;1;1;0", "expected_minimal_gates": 2, "expected_minimal_depth": 2, + "expected_minimal_two_qubit_depth": 1, "expected_minimal_gates_at_minimal_depth": 2, "expected_minimal_two_qubit_gates": 1, "expected_minimal_gates_at_minimal_two_qubit_gates": 2 @@ -209,6 +229,7 @@ "target_tableau": "1;1;0;0;0\n0;0;1;1;1", "expected_minimal_gates": 3, "expected_minimal_depth": 2, + "expected_minimal_two_qubit_depth": 1, "expected_minimal_gates_at_minimal_depth": 3, "expected_minimal_two_qubit_gates": 1, "expected_minimal_gates_at_minimal_two_qubit_gates": 3 @@ -219,6 +240,7 @@ "target_tableau": "1;1;0;0;1\n0;0;1;1;0", "expected_minimal_gates": 3, "expected_minimal_depth": 3, + "expected_minimal_two_qubit_depth": 1, "expected_minimal_gates_at_minimal_depth": 3, "expected_minimal_two_qubit_gates": 1, "expected_minimal_gates_at_minimal_two_qubit_gates": 3 @@ -229,6 +251,7 @@ "target_tableau": "1;1;0;0;1\n0;0;1;1;1", "expected_minimal_gates": 3, "expected_minimal_depth": 3, + "expected_minimal_two_qubit_depth": 1, "expected_minimal_gates_at_minimal_depth": 3, "expected_minimal_two_qubit_gates": 1, "expected_minimal_gates_at_minimal_two_qubit_gates": 3 @@ -239,6 +262,7 @@ "target_tableau": "0;0;0;1;0\n0;0;1;0;0", "expected_minimal_gates": 3, "expected_minimal_depth": 3, + "expected_minimal_two_qubit_depth": 2, "expected_minimal_gates_at_minimal_depth": 3, "expected_minimal_two_qubit_gates": 2, "expected_minimal_gates_at_minimal_two_qubit_gates": 4 @@ -249,6 +273,7 @@ "target_tableau": "1;1;1;0;0;0;0\n0;0;0;1;1;0;0\n0;0;0;0;1;1;0", "expected_minimal_gates": 3, "expected_minimal_depth": 3, + "expected_minimal_two_qubit_depth": 2, "expected_minimal_gates_at_minimal_depth": 3, "expected_minimal_two_qubit_gates": 2, "expected_minimal_gates_at_minimal_two_qubit_gates": 3 @@ -259,6 +284,7 @@ "target_tableau": "1;1;1;1;0;0;0;0;0\n0;0;0;0;1;1;0;0;0\n0;0;0;0;0;1;1;0;0\n0;0;0;0;0;0;1;1;0", "expected_minimal_gates": 4, "expected_minimal_depth": 3, + "expected_minimal_two_qubit_depth": 3, "expected_minimal_gates_at_minimal_depth": 5, "expected_minimal_two_qubit_gates": 3, "expected_minimal_gates_at_minimal_two_qubit_gates": 4 @@ -269,6 +295,7 @@ "target_tableau": "1;0;0;0;0;\n0;1;0;0;0;\n0;0;1;0;0\n0;0;0;1;0", "expected_minimal_gates": 0, "expected_minimal_depth": 0, + "expected_minimal_two_qubit_depth": 0, "expected_minimal_gates_at_minimal_depth": 0, "expected_minimal_two_qubit_gates": 0, "expected_minimal_gates_at_minimal_two_qubit_gates": 0 @@ -279,6 +306,7 @@ "target_tableau": "0;1;0;0;0;\n1;0;0;0;0;\n0;0;0;1;0\n0;0;1;0;0", "expected_minimal_gates": 3, "expected_minimal_depth": 3, + "expected_minimal_two_qubit_depth": 3, "expected_minimal_gates_at_minimal_depth": 3, "expected_minimal_two_qubit_gates": 3, "expected_minimal_gates_at_minimal_two_qubit_gates": 3 @@ -288,6 +316,7 @@ "target_tableau": "[+IX, +ZI]", "expected_minimal_gates": 3, "expected_minimal_depth": 3, + "expected_minimal_two_qubit_depth": 2, "expected_minimal_gates_at_minimal_depth": 3, "expected_minimal_two_qubit_gates": 2, "expected_minimal_gates_at_minimal_two_qubit_gates": 3 @@ -298,6 +327,7 @@ "target_tableau": "[+IX, +ZI, +ZZ, -XX]", "expected_minimal_gates": 4, "expected_minimal_depth": 3, + "expected_minimal_two_qubit_depth": 2, "expected_minimal_gates_at_minimal_depth": 4, "expected_minimal_two_qubit_gates": 2, "expected_minimal_gates_at_minimal_two_qubit_gates": 4 @@ -308,6 +338,7 @@ "target_tableau": "[+_X, +Z_, +ZZ, -XX]", "expected_minimal_gates": 4, "expected_minimal_depth": 3, + "expected_minimal_two_qubit_depth": 2, "expected_minimal_gates_at_minimal_depth": 4, "expected_minimal_two_qubit_gates": 2, "expected_minimal_gates_at_minimal_two_qubit_gates": 4 diff --git a/test/cliffordsynthesis/test_synthesis.cpp b/test/cliffordsynthesis/test_synthesis.cpp index 71b17ed09..b2dbfd881 100644 --- a/test/cliffordsynthesis/test_synthesis.cpp +++ b/test/cliffordsynthesis/test_synthesis.cpp @@ -24,6 +24,7 @@ struct TestConfiguration { std::size_t expectedMinimalGatesAtMinimalDepth{}; std::size_t expectedMinimalTwoQubitGates{}; std::size_t expectedMinimalGatesAtMinimalTwoQubitGates{}; + std::size_t expectedMinimalTQDepth{}; }; // NOLINTNEXTLINE (readability-identifier-naming) @@ -38,6 +39,10 @@ inline void from_json(const nlohmann::json& j, TestConfiguration& test) { if (j.contains("initial_circuit")) { test.initialCircuit = j.at("initial_circuit").get(); } + if (j.contains("expected_minimal_two_qubit_depth")) { + test.expectedMinimalTQDepth = + j.at("expected_minimal_two_qubit_depth").get(); + } test.expectedMinimalGates = j.at("expected_minimal_gates").get(); test.expectedMinimalDepth = j.at("expected_minimal_depth").get(); @@ -351,6 +356,25 @@ TEST_P(SynthesisTest, TestDestabilizerTwoQubitGates) { } } +TEST_P(SynthesisTest, TwoQubitDepthLinearSearch) { + config.target = TargetMetric::TwoQubitDepth; + config.verbosity = plog::Severity::none; + config.linearSearch = true; + synthesizer.synthesize(config); + results = synthesizer.getResults(); + + EXPECT_EQ(results.getTwoQubitDepth(), test.expectedMinimalTQDepth); +} + +TEST_P(SynthesisTest, TwoQubitDepthBinarySearch) { + config.target = TargetMetric::TwoQubitDepth; + config.verbosity = plog::Severity::none; + synthesizer.synthesize(config); + results = synthesizer.getResults(); + + EXPECT_EQ(results.getTwoQubitDepth(), test.expectedMinimalTQDepth); +} + TEST(HeuristicTest, basic) { auto config = Configuration(); auto qc = qc::QuantumComputation(2); diff --git a/test/python/test_cliffordsynthesis.py b/test/python/test_cliffordsynthesis.py index fbb1a15cb..160109b58 100644 --- a/test/python/test_cliffordsynthesis.py +++ b/test/python/test_cliffordsynthesis.py @@ -7,10 +7,8 @@ from pathlib import Path import pytest -from qiskit import QuantumCircuit -from qiskit.quantum_info import Clifford, PauliList -from mqt import qcec, qmap +from mqt import qmap @dataclass @@ -19,6 +17,7 @@ class Configuration: expected_minimal_gates: int expected_minimal_depth: int + expected_minimal_two_qubit_depth: int expected_minimal_gates_at_minimal_depth: int expected_minimal_two_qubit_gates: int expected_minimal_gates_at_minimal_two_qubit_gates: int @@ -45,244 +44,16 @@ def create_tableau_tests() -> list[Configuration]: return [Configuration(**t) for t in tableaus] -@pytest.mark.parametrize("test_config", create_circuit_tests()) -@pytest.mark.parametrize("use_maxsat", [True, False], ids=["maxsat", "binary_search"]) -def test_optimize_clifford_gates(test_config: Configuration, use_maxsat: bool) -> None: - """Test gate-optimal Clifford synthesis.""" - circ, results = qmap.optimize_clifford( - circuit=test_config.initial_circuit, use_maxsat=use_maxsat, target_metric="gates" - ) - - assert results.gates == test_config.expected_minimal_gates - print("\n", circ) - - -@pytest.mark.parametrize("test_config", create_circuit_tests()) -@pytest.mark.parametrize("use_maxsat", [True, False], ids=["maxsat", "binary_search"]) -def test_optimize_clifford_depth(test_config: Configuration, use_maxsat: bool) -> None: - """Test depth-optimal Clifford synthesis.""" - circ, results = qmap.optimize_clifford( - circuit=test_config.initial_circuit, use_maxsat=use_maxsat, target_metric="depth" - ) - - assert results.depth == test_config.expected_minimal_depth - print("\n", circ) - - -@pytest.mark.parametrize("test_config", create_circuit_tests()) -@pytest.mark.parametrize("use_maxsat", [True, False], ids=["maxsat", "binary_search"]) -def test_optimize_clifford_gates_at_minimal_depth(test_config: Configuration, use_maxsat: bool) -> None: - """Test gate-optimal Clifford synthesis at minimal depth.""" - circ, results = qmap.optimize_clifford( - circuit=test_config.initial_circuit, - use_maxsat=use_maxsat, - target_metric="depth", - minimize_gates_after_depth_optimization=True, - ) - - assert results.gates == test_config.expected_minimal_gates_at_minimal_depth - print("\n", circ) - - -@pytest.mark.parametrize("test_config", create_circuit_tests()) -@pytest.mark.parametrize("use_maxsat", [True, False], ids=["maxsat", "binary_search"]) -def test_optimize_clifford_two_qubit_gates(test_config: Configuration, use_maxsat: bool) -> None: - """Test two-qubit gate-optimal Clifford synthesis.""" - circ, results = qmap.optimize_clifford( - circuit=test_config.initial_circuit, - use_maxsat=use_maxsat, - target_metric="two_qubit_gates", - try_higher_gate_limit_for_two_qubit_gate_optimization=True, - ) - - assert results.two_qubit_gates == test_config.expected_minimal_two_qubit_gates - print("\n", circ) - - -@pytest.mark.parametrize("test_config", create_circuit_tests()) -@pytest.mark.parametrize("use_maxsat", [True, False], ids=["maxsat", "binary_search"]) -def test_optimize_clifford_gates_at_minimal_two_qubit_gates(test_config: Configuration, use_maxsat: bool) -> None: - """Test gate-optimal Clifford synthesis at minimal two-qubit gate count.""" - circ, results = qmap.optimize_clifford( - circuit=test_config.initial_circuit, - use_maxsat=use_maxsat, - target_metric="two_qubit_gates", - try_higher_gate_limit_for_two_qubit_gate_optimization=True, - minimize_gates_after_two_qubit_gate_optimization=True, - ) - - assert results.gates == test_config.expected_minimal_gates_at_minimal_two_qubit_gates - print("\n", circ) - - -@pytest.mark.parametrize("test_config", create_circuit_tests()) -def test_heuristic(test_config: Configuration) -> None: - """Test heuristic synthesis method.""" - circ, _ = qmap.optimize_clifford( - circuit=test_config.initial_circuit, - heuristic=True, - split_size=10, - target_metric="depth", - include_destabilizers=True, - ) - - circ_opt, _ = qmap.optimize_clifford( - circuit=test_config.initial_circuit, heuristic=False, target_metric="depth", include_destabilizers=True - ) - - assert circ.depth() >= circ_opt.depth() - assert Clifford(circ) == Clifford(circ_opt) - print("\n", circ) - - -@pytest.mark.parametrize("test_config", create_tableau_tests()) -@pytest.mark.parametrize("use_maxsat", [True, False], ids=["maxsat", "binary_search"]) -def test_synthesize_clifford_gates(test_config: Configuration, use_maxsat: bool) -> None: - """Test gate-optimal tableau synthesis.""" - circ, results = qmap.synthesize_clifford( - target_tableau=test_config.target_tableau, - initial_tableau=test_config.initial_tableau, - use_maxsat=use_maxsat, - target_metric="gates", - ) - - assert results.gates == test_config.expected_minimal_gates - print("\n", circ) - - -@pytest.mark.parametrize("test_config", create_tableau_tests()) -@pytest.mark.parametrize("use_maxsat", [True, False], ids=["maxsat", "binary_search"]) -def test_synthesize_clifford_depth(test_config: Configuration, use_maxsat: bool) -> None: - """Test depth-optimal tableau synthesis.""" - circ, results = qmap.synthesize_clifford( - target_tableau=test_config.target_tableau, - initial_tableau=test_config.initial_tableau, - use_maxsat=use_maxsat, - target_metric="depth", - ) - - assert results.depth == test_config.expected_minimal_depth - print("\n", circ) - - -@pytest.mark.parametrize("test_config", create_tableau_tests()) -@pytest.mark.parametrize("use_maxsat", [True, False], ids=["maxsat", "binary_search"]) -def test_synthesize_clifford_gates_at_minimal_depth(test_config: Configuration, use_maxsat: bool) -> None: - """Test gate-optimal tableau synthesis at minimal depth.""" - circ, results = qmap.synthesize_clifford( - target_tableau=test_config.target_tableau, - initial_tableau=test_config.initial_tableau, - use_maxsat=use_maxsat, - target_metric="depth", - minimize_gates_after_depth_optimization=True, - ) - - assert results.gates == test_config.expected_minimal_gates_at_minimal_depth - print("\n", circ) - - -@pytest.mark.parametrize("test_config", create_tableau_tests()) -@pytest.mark.parametrize("use_maxsat", [True, False], ids=["maxsat", "binary_search"]) -def test_synthesize_clifford_two_qubit_gates(test_config: Configuration, use_maxsat: bool) -> None: - """Test two-qubit gate-optimal tableau synthesis.""" - circ, results = qmap.synthesize_clifford( - target_tableau=test_config.target_tableau, - initial_tableau=test_config.initial_tableau, - use_maxsat=use_maxsat, - target_metric="two_qubit_gates", - try_higher_gate_limit_for_two_qubit_gate_optimization=True, - ) - - assert results.two_qubit_gates == test_config.expected_minimal_two_qubit_gates - print("\n", circ) - - @pytest.mark.parametrize("test_config", create_tableau_tests()) -@pytest.mark.parametrize("use_maxsat", [True, False], ids=["maxsat", "binary_search"]) -def test_synthesize_clifford_gates_at_minimal_two_qubit_gates(test_config: Configuration, use_maxsat: bool) -> None: - """Test gate-optimal tableau synthesis at minimal two-qubit gate count.""" +@pytest.mark.parametrize("use_maxsat", [True, False], ids=["maxsat", "linear_search"]) +def test_synthesize_clifford_tqdepth(test_config: Configuration, use_maxsat: bool) -> None: + """Test TQDepth-optimal tableau synthesis.""" circ, results = qmap.synthesize_clifford( target_tableau=test_config.target_tableau, initial_tableau=test_config.initial_tableau, - use_maxsat=use_maxsat, - target_metric="two_qubit_gates", - try_higher_gate_limit_for_two_qubit_gate_optimization=True, - minimize_gates_after_two_qubit_gate_optimization=True, + use_maxsat=False, + target_metric="TQDepth", ) - assert results.gates == test_config.expected_minimal_gates_at_minimal_two_qubit_gates + assert results.depth == test_config.expected_minimal_two_qubit_depth print("\n", circ) - - -# The following tests merely check that all kinds of conversions work as expected. -# They only check for the correctness of the result for a simple circuit. - - -@pytest.fixture() -def bell_circuit() -> QuantumCircuit: - """Return a Bell state circuit.""" - circ = QuantumCircuit(2) - circ.h(0) - circ.cx(0, 1) - return circ - - -def test_optimize_quantum_computation(bell_circuit: QuantumCircuit) -> None: - """Test that we can optimize an MQT QuantumComputation.""" - qc = qmap.QuantumComputation.from_qiskit(bell_circuit) - circ, results = qmap.optimize_clifford(circuit=qc) - assert qcec.verify(circ, bell_circuit).considered_equivalent() - - -def test_optimize_from_qasm_file(bell_circuit: QuantumCircuit) -> None: - """Test that we can optimize from a QASM file.""" - with Path("bell.qasm").open("w") as f: - f.write(bell_circuit.qasm()) - circ, results = qmap.optimize_clifford(circuit="bell.qasm") - assert qcec.verify(circ, bell_circuit).considered_equivalent() - - -def test_optimize_qiskit_circuit(bell_circuit: QuantumCircuit) -> None: - """Test that we can optimize a Qiskit QuantumCircuit.""" - circ, results = qmap.optimize_clifford(circuit=bell_circuit) - assert qcec.verify(circ, bell_circuit).considered_equivalent() - - -def test_optimize_with_initial_tableau(bell_circuit: QuantumCircuit) -> None: - """Test that we can optimize a circuit with an initial tableau.""" - circ, results = qmap.optimize_clifford(circuit=bell_circuit, initial_tableau=qmap.Tableau(bell_circuit.num_qubits)) - assert qcec.verify(circ, bell_circuit).considered_equivalent() - - -def test_synthesize_from_tableau(bell_circuit: QuantumCircuit) -> None: - """Test that we can synthesize a circuit from an MQT Tableau.""" - tableau = qmap.Tableau("['XX', 'ZZ']") - circ, results = qmap.synthesize_clifford(target_tableau=tableau) - assert qcec.verify(circ, bell_circuit).considered_equivalent() - - -def test_synthesize_from_qiskit_clifford(bell_circuit: QuantumCircuit) -> None: - """Test that we can synthesize a circuit from a Qiskit Clifford.""" - cliff = Clifford(bell_circuit) - circ, results = qmap.synthesize_clifford(target_tableau=cliff) - assert qcec.verify(circ, bell_circuit).considered_equivalent() - - -def test_synthesize_from_qiskit_pauli_list(bell_circuit: QuantumCircuit) -> None: - """Test that we can synthesize a circuit from a Qiskit PauliList.""" - pauli_list = PauliList(["XX", "ZZ"]) - circ, results = qmap.synthesize_clifford(target_tableau=pauli_list) - assert qcec.verify(circ, bell_circuit).considered_equivalent() - - -def test_synthesize_from_string(bell_circuit: QuantumCircuit) -> None: - """Test that we can synthesize a circuit from a String.""" - pauli_str = "[XX,ZZ]" - circ, results = qmap.synthesize_clifford(target_tableau=pauli_str) - assert qcec.verify(circ, bell_circuit).considered_equivalent() - - -def test_invalid_kwarg_to_synthesis() -> None: - """Test that we raise an error if we pass an invalid kwarg to synthesis.""" - with pytest.raises(ValueError, match="Invalid keyword argument"): - qmap.synthesize_clifford(target_tableau=qmap.Tableau("Z"), invalid_kwarg=True)