From fc43ebf3adf0f7cb96a214d051034eb95822377a Mon Sep 17 00:00:00 2001 From: Will Simmons Date: Tue, 31 Oct 2023 13:22:06 +0000 Subject: [PATCH] Refactor PauliStrings as templates for easier interop (#1081) --- pytket/binders/circuit/boxes.cpp | 47 +- pytket/binders/partition.cpp | 6 +- pytket/binders/pauli.cpp | 171 +-- pytket/binders/tableau.cpp | 18 +- pytket/binders/tailoring.cpp | 18 +- pytket/conanfile.py | 2 +- pytket/pytket/_tket/pauli.pyi | 2 +- tket/CMakeLists.txt | 4 +- tket/conanfile.py | 2 +- .../Characterisation/FrameRandomisation.hpp | 4 +- .../tket/Circuit/AssertionSynthesis.hpp | 2 +- tket/include/tket/Circuit/Boxes.hpp | 6 +- tket/include/tket/Circuit/CircUtils.hpp | 2 +- tket/include/tket/Circuit/PauliExpBoxes.hpp | 29 +- tket/include/tket/Clifford/ChoiMixTableau.hpp | 8 +- .../tket/Clifford/SymplecticTableau.hpp | 4 +- tket/include/tket/Clifford/UnitaryTableau.hpp | 24 +- tket/include/tket/Converters/PauliGadget.hpp | 32 +- .../tket/Diagonalisation/DiagUtils.hpp | 9 +- .../tket/Diagonalisation/Diagonalisation.hpp | 19 +- .../tket/Diagonalisation/PauliPartition.hpp | 12 +- .../MeasurementSetup/MeasurementReduction.hpp | 2 +- .../MeasurementSetup/MeasurementSetup.hpp | 16 +- tket/include/tket/Ops/Op.hpp | 3 +- .../PauliGraph/ConjugatePauliFunctions.hpp | 10 +- tket/include/tket/PauliGraph/PauliGraph.hpp | 6 +- tket/include/tket/Utils/PauliStrings.hpp | 571 --------- tket/include/tket/Utils/PauliTensor.hpp | 1051 +++++++++++++++++ .../Characterisation/FrameRandomisation.cpp | 15 +- tket/src/Circuit/AssertionSynthesis.cpp | 15 +- tket/src/Circuit/Boxes.cpp | 34 +- tket/src/Circuit/PauliExpBoxes.cpp | 190 ++- .../PauliExpBoxUnitaryCalculator.cpp | 10 +- tket/src/Clifford/ChoiMixTableau.cpp | 40 +- tket/src/Clifford/SymplecticTableau.cpp | 10 +- tket/src/Clifford/UnitaryTableau.cpp | 93 +- .../Converters/ChoiMixTableauConverters.cpp | 46 +- tket/src/Converters/PauliGadget.cpp | 173 ++- tket/src/Converters/PauliGraphConverters.cpp | 60 +- tket/src/Diagonalisation/DiagUtils.cpp | 12 +- tket/src/Diagonalisation/Diagonalisation.cpp | 96 +- tket/src/Diagonalisation/PauliPartition.cpp | 55 +- tket/src/Gate/Gate.cpp | 2 +- .../MeasurementSetup/MeasurementReduction.cpp | 30 +- .../src/MeasurementSetup/MeasurementSetup.cpp | 37 +- .../PauliGraph/ConjugatePauliFunctions.cpp | 53 +- tket/src/PauliGraph/PauliGraph.cpp | 76 +- .../src/Transformations/PauliOptimisation.cpp | 37 +- tket/src/Utils/PauliStrings.cpp | 580 --------- tket/src/Utils/PauliTensor.cpp | 734 ++++++++++++ tket/test/CMakeLists.txt | 2 +- tket/test/src/Circuit/test_Boxes.cpp | 60 +- tket/test/src/Circuit/test_Circ.cpp | 30 +- tket/test/src/Circuit/test_PauliExpBoxes.cpp | 183 +-- tket/test/src/Ops/test_Ops.cpp | 5 +- .../test_PauliExpBoxUnitaryCalculator.cpp | 8 +- tket/test/src/test_Assertion.cpp | 35 +- tket/test/src/test_ChoiMixTableau.cpp | 173 ++- tket/test/src/test_Clifford.cpp | 1 - tket/test/src/test_CompilerPass.cpp | 4 +- tket/test/src/test_MeasurementReduction.cpp | 50 +- tket/test/src/test_MeasurementSetup.cpp | 78 +- tket/test/src/test_Partition.cpp | 26 +- tket/test/src/test_PauliGraph.cpp | 232 ++-- tket/test/src/test_PauliString.cpp | 277 ----- tket/test/src/test_PauliTensor.cpp | 623 ++++++++++ tket/test/src/test_Predicates.cpp | 2 +- tket/test/src/test_UnitaryTableau.cpp | 143 ++- tket/test/src/test_json.cpp | 24 +- 69 files changed, 3634 insertions(+), 2800 deletions(-) delete mode 100644 tket/include/tket/Utils/PauliStrings.hpp create mode 100644 tket/include/tket/Utils/PauliTensor.hpp delete mode 100644 tket/src/Utils/PauliStrings.cpp create mode 100644 tket/src/Utils/PauliTensor.cpp delete mode 100644 tket/test/src/test_PauliString.cpp create mode 100644 tket/test/src/test_PauliTensor.cpp diff --git a/pytket/binders/circuit/boxes.cpp b/pytket/binders/circuit/boxes.cpp index 792bf58002..9a20f64f64 100644 --- a/pytket/binders/circuit/boxes.cpp +++ b/pytket/binders/circuit/boxes.cpp @@ -237,9 +237,10 @@ void init_boxes(py::module &m) { "An operation defined as the exponential of a tensor of Pauli " "operations and a (possibly symbolic) phase parameter.") .def( - py::init< - const py::tket_custom::SequenceVec &, const Expr &, - const CXConfigType &>(), + py::init([](const py::tket_custom::SequenceVec &paulis, Expr t, + CXConfigType config) { + return PauliExpBox(SymPauliTensor(paulis, t), config); + }), "Construct :math:`e^{-\\frac12 i \\pi t \\sigma_0 \\otimes " "\\sigma_1 \\otimes \\cdots}` from Pauli operators " ":math:`\\sigma_i \\in \\{I,X,Y,Z\\}` and a parameter " @@ -263,10 +264,14 @@ void init_boxes(py::module &m) { "An operation defined as a pair of exponentials of a tensor of Pauli " "operations and their (possibly symbolic) phase parameters.") .def( - py::init< - const py::tket_custom::SequenceVec &, const Expr &, - const py::tket_custom::SequenceVec &, Expr, - CXConfigType>(), + py::init([](const py::tket_custom::SequenceVec &paulis0, + Expr t0, + const py::tket_custom::SequenceVec &paulis1, + Expr t1, CXConfigType config) { + return PauliExpPairBox( + SymPauliTensor(paulis0, t0), SymPauliTensor(paulis1, t1), + config); + }), "Construct a pair of Pauli exponentials of the form" " :math:`e^{-\\frac12 i \\pi t_j \\sigma_0 \\otimes " "\\sigma_1 \\otimes \\cdots}` from Pauli operator strings " @@ -297,12 +302,13 @@ void init_boxes(py::module &m) { .def( py::init([](const py::tket_custom::SequenceVec< std::pair, Expr>> - &py_gadgets, - const CXConfigType &cx_config) { - std::vector, Expr>> gadgets( - std::make_move_iterator(py_gadgets.begin()), - std::make_move_iterator(py_gadgets.end())); - return PauliExpCommutingSetBox(gadgets, cx_config); + &pauli_gadgets, + CXConfigType config) { + std::vector gadgets; + for (const std::pair, Expr> &g : + pauli_gadgets) + gadgets.push_back(SymPauliTensor(g.first, g.second)); + return PauliExpCommutingSetBox(gadgets, config); }), "Construct a set of necessarily commuting Pauli exponentials of the " "form" @@ -317,7 +323,14 @@ void init_boxes(py::module &m) { [](PauliExpCommutingSetBox &pbox) { return *pbox.to_circuit(); }, ":return: the :py:class:`Circuit` described by the box") .def( - "get_paulis", &PauliExpCommutingSetBox::get_pauli_gadgets, + "get_paulis", + [](const PauliExpCommutingSetBox &pbox) { + // For backwards compatibility with before templated PauliTensor + std::vector> gadgets; + for (const SymPauliTensor &g : pbox.get_pauli_gadgets()) + gadgets.push_back({g.string, g.coeff}); + return gadgets; + }, ":return: the corresponding list of Pauli gadgets") .def( "get_cx_config", &PauliExpCommutingSetBox::get_cx_config, @@ -677,15 +690,15 @@ void init_boxes(py::module &m) { .def( py::init([](const py::tket_custom::SequenceVec &pauli_strings) { - PauliStabiliserList stabilisers; + PauliStabiliserVec stabilisers; for (auto &raw_string : pauli_strings) { std::vector string; - bool coeff = true; + quarter_turns_t coeff = 0; for (unsigned i = 0; i < raw_string.size(); i++) { switch (raw_string[i]) { case '-': if (i == 0) { - coeff = false; + coeff = 2; } else { throw std::invalid_argument( "Invalid Pauli string: " + raw_string); diff --git a/pytket/binders/partition.cpp b/pytket/binders/partition.cpp index 784d3a418c..f08163e732 100644 --- a/pytket/binders/partition.cpp +++ b/pytket/binders/partition.cpp @@ -126,7 +126,7 @@ PYBIND11_MODULE(partition, m) { .def( "add_result_for_term", (void(MeasurementSetup::*)( - const QubitPauliString &, + const SpPauliString &, const MeasurementSetup::MeasurementBitMap &)) & MeasurementSetup::add_result_for_term, "Add a new Pauli string with a corresponding BitMap", py::arg("term"), @@ -153,7 +153,7 @@ PYBIND11_MODULE(partition, m) { m.def( "measurement_reduction", - [](const py::tket_custom::SequenceList &strings, + [](const py::tket_custom::SequenceList &strings, PauliPartitionStrat strat, GraphColourMethod method, CXConfigType cx_config) { return measurement_reduction(strings, strat, method, cx_config); @@ -173,7 +173,7 @@ PYBIND11_MODULE(partition, m) { m.def( "term_sequence", - [](const py::tket_custom::SequenceList &strings, + [](const py::tket_custom::SequenceList &strings, PauliPartitionStrat strat, GraphColourMethod method) { return term_sequence(strings, strat, method); }, diff --git a/pytket/binders/pauli.cpp b/pytket/binders/pauli.cpp index 584579fe0b..c69677980e 100644 --- a/pytket/binders/pauli.cpp +++ b/pytket/binders/pauli.cpp @@ -20,7 +20,7 @@ #include "binder_json.hpp" #include "deleted_hash.hpp" #include "py_operators.hpp" -#include "tket/Utils/PauliStrings.hpp" +#include "tket/Utils/PauliTensor.hpp" #include "typecast.hpp" namespace py = pybind11; @@ -38,7 +38,7 @@ PYBIND11_MODULE(pauli, m) { .value("Z", Pauli::Z) .export_values(); - py::class_( + py::class_( m, "QubitPauliString", "A string of Pauli letters from the alphabet {I, X, Y, Z}, " "implemented as a sparse list, indexed by qubit.") @@ -60,22 +60,23 @@ PYBIND11_MODULE(pauli, m) { ":py:class:`Qubit` to :py:class:`Pauli`.", py::arg("map")) .def( - "__hash__", - [](const QubitPauliString &qps) { return hash_value(qps); }) - .def("__repr__", &QubitPauliString::to_str) - .def("__eq__", &py_equals) - .def("__ne__", &py_not_equals) - .def("__lt__", &QubitPauliString::operator<) - .def("__getitem__", &QubitPauliString::get) - .def("__setitem__", &QubitPauliString::set) + "__hash__", [](const SpPauliString &qps) { return qps.hash_value(); }) + .def("__repr__", &SpPauliString::to_str) + .def("__eq__", &py_equals) + .def("__ne__", &py_not_equals) + .def("__lt__", &SpPauliString::operator<) + .def("__getitem__", &SpPauliString::get) + .def("__setitem__", &SpPauliString::set) .def_property_readonly( - "map", [](const QubitPauliString &qps) { return qps.map; }, + "map", [](const SpPauliString &qps) { return qps.string; }, ":return: the QubitPauliString's underlying dict mapping " ":py:class:`Qubit` to :py:class:`Pauli`") .def( "to_list", - [](const QubitPauliString &qps) { - return py::object(json(qps)).cast(); + [](const SpPauliString &qps) { + // Just return the QubitPauliMap for backwards compatibility with + // before templated PauliTensor + return py::object(json(qps.string)).cast(); }, "A JSON-serializable representation of the QubitPauliString.\n\n" ":return: a list of :py:class:`Qubit`-to-:py:class:`Pauli` " @@ -84,22 +85,23 @@ PYBIND11_MODULE(pauli, m) { .def_static( "from_list", [](const py::list &qubit_pauli_string_list) { - return json(qubit_pauli_string_list).get(); + return SpPauliString( + json(qubit_pauli_string_list).get()); }, "Construct a new QubitPauliString instance from a JSON serializable " "list " "representation.") .def( - "compress", &QubitPauliString::compress, + "compress", &SpPauliString::compress, "Removes I terms to compress the sparse representation.") .def( - "commutes_with", &QubitPauliString::commutes_with, + "commutes_with", &SpPauliString::commutes_with, ":return: True if the two strings commute, else False", py::arg("other")) .def( "to_sparse_matrix", - (CmplxSpMat(QubitPauliString::*)(void) const) & - QubitPauliString::to_sparse_matrix, + (CmplxSpMat(SpPauliString::*)(void) const) & + SpPauliString::to_sparse_matrix, "Represents the sparse string as a dense string (without " "padding for extra qubits) and generates the matrix for the " "tensor. Uses the ILO-BE convention, so ``Qubit(\"a\", 0)`` " @@ -108,8 +110,8 @@ PYBIND11_MODULE(pauli, m) { "\n\n:return: a sparse matrix corresponding to the operator") .def( "to_sparse_matrix", - (CmplxSpMat(QubitPauliString::*)(const unsigned) const) & - QubitPauliString::to_sparse_matrix, + (CmplxSpMat(SpPauliString::*)(const unsigned) const) & + SpPauliString::to_sparse_matrix, "Represents the sparse string as a dense string over " "`n_qubits` qubits (sequentially indexed from 0 in the " "default register) and generates the matrix for the tensor. " @@ -121,7 +123,7 @@ PYBIND11_MODULE(pauli, m) { py::arg("n_qubits")) .def( "to_sparse_matrix", - [](const QubitPauliString &self, const py_qubit_vector_t &qubits) { + [](const SpPauliString &self, const py_qubit_vector_t &qubits) { return self.to_sparse_matrix(qubits); }, "Represents the sparse string as a dense string and generates " @@ -135,9 +137,8 @@ PYBIND11_MODULE(pauli, m) { py::arg("qubits")) .def( "dot_state", - (Eigen::VectorXcd(QubitPauliString::*)(const Eigen::VectorXcd &) - const) & - QubitPauliString::dot_state, + (Eigen::VectorXcd(SpPauliString::*)(const Eigen::VectorXcd &) const) & + SpPauliString::dot_state, "Performs the dot product of the state with the pauli string. " "Maps the qubits of the statevector with sequentially-indexed " "qubits in the default register, with ``Qubit(0)`` being the " @@ -148,7 +149,7 @@ PYBIND11_MODULE(pauli, m) { py::arg("state")) .def( "dot_state", - [](const QubitPauliString &self, const Eigen::VectorXcd &state, + [](const SpPauliString &self, const Eigen::VectorXcd &state, const py_qubit_vector_t &qubits) { return self.dot_state(state, qubits); }, @@ -163,7 +164,7 @@ PYBIND11_MODULE(pauli, m) { py::arg("state"), py::arg("qubits")) .def( "state_expectation", - [](const QubitPauliString &self, const Eigen::VectorXcd &state) { + [](const SpPauliString &self, const Eigen::VectorXcd &state) { return self.state_expectation(state); }, "Calculates the expectation value of the state with the pauli " @@ -176,7 +177,7 @@ PYBIND11_MODULE(pauli, m) { py::arg("state")) .def( "state_expectation", - [](const QubitPauliString &self, const Eigen::VectorXcd &state, + [](const SpPauliString &self, const Eigen::VectorXcd &state, const py_qubit_vector_t &qubits) { return self.state_expectation(state, qubits); }, @@ -191,11 +192,11 @@ PYBIND11_MODULE(pauli, m) { py::arg("state"), py::arg("qubits")) .def(py::pickle( - [](const QubitPauliString &qps) { + [](const SpPauliString &qps) { /* Hackery to avoid pickling an opaque object */ std::list qubits; std::list paulis; - for (const std::pair &qp_pair : qps.map) { + for (const std::pair &qp_pair : qps.string) { qubits.push_back(qp_pair.first); paulis.push_back(qp_pair.second); } @@ -205,16 +206,16 @@ PYBIND11_MODULE(pauli, m) { if (t.size() != 2) throw std::runtime_error( "Invalid state: tuple size: " + std::to_string(t.size())); - return QubitPauliString( + return SpPauliString( t[0].cast>(), t[1].cast>()); })); m.def( "pauli_string_mult", - [](const QubitPauliString &qps1, const QubitPauliString &qps2) { - QubitPauliTensor product_tensor = - QubitPauliTensor(qps1) * QubitPauliTensor(qps2); - return std::pair( + [](const SpPauliString &qps1, const SpPauliString &qps2) { + SpCxPauliTensor product_tensor = + SpCxPauliTensor(qps1) * SpCxPauliTensor(qps2); + return std::pair( product_tensor.string, product_tensor.coeff); }, ":return: the product of two QubitPauliString objects as a pair " @@ -230,10 +231,10 @@ PYBIND11_MODULE(pauli, m) { py::init([](const py::tket_custom::SequenceVec &string, const int &coeff) { if (coeff == 1) { - return PauliStabiliser(string, true); + return PauliStabiliser(string, 0); } if (coeff == -1) { - return PauliStabiliser(string, false); + return PauliStabiliser(string, 2); } throw std::invalid_argument("Coefficient must be -1 or 1."); }), @@ -242,8 +243,7 @@ PYBIND11_MODULE(pauli, m) { .def_property_readonly( "coeff", [](const PauliStabiliser &stabiliser) { - if (stabiliser.coeff) return 1; - return -1; + return stabiliser.is_real_negative() ? -1 : 1; }, "The coefficient of the stabiliser") .def_property_readonly( @@ -254,13 +254,14 @@ PYBIND11_MODULE(pauli, m) { .def("__hash__", &deletedHash, deletedHashDocstring) .def("__ne__", &py_not_equals); - py::class_( + py::class_( m, "QubitPauliTensor", "A tensor formed by Pauli terms, consisting of a sparse map from " ":py:class:`Qubit` to :py:class:`Pauli` (implemented as a " ":py:class:`QubitPauliString`) and a complex coefficient.") .def( - py::init(), + py::init( + [](const Complex &coeff) { return SpCxPauliTensor({}, coeff); }), "Constructs an empty QubitPauliTensor, representing the identity.", py::arg("coeff") = 1.) .def( @@ -271,7 +272,7 @@ PYBIND11_MODULE(pauli, m) { py::init([](const py::tket_custom::SequenceList &qubits, const py::tket_custom::SequenceList &paulis, const Complex &coeff) { - return QubitPauliTensor(QubitPauliString(qubits, paulis), coeff); + return SpCxPauliTensor(qubits, paulis, coeff); }), "Constructs a QubitPauliTensor from two matching lists of " "Qubits and Paulis.", @@ -282,42 +283,51 @@ PYBIND11_MODULE(pauli, m) { ":py:class:`Qubit` to :py:class:`Pauli`.", py::arg("map"), py::arg("coeff") = 1.) .def( - py::init(), + py::init([](const SpPauliString &qps, const Complex &c) { + return SpCxPauliTensor(qps.string, c); + }), "Construct a QubitPauliTensor from a QubitPauliString.", py::arg("string"), py::arg("coeff") = 1.) .def( "__hash__", - [](const QubitPauliTensor &qps) { return hash_value(qps); }) - .def("__repr__", &QubitPauliTensor::to_str) - .def("__eq__", &py_equals) - .def("__ne__", &py_not_equals) - .def("__lt__", &QubitPauliTensor::operator<) - .def( - "__getitem__", [](const QubitPauliTensor &qpt, - const Qubit &q) { return qpt.string.get(q); }) - .def( - "__setitem__", [](QubitPauliTensor &qpt, const Qubit &q, - Pauli p) { return qpt.string.set(q, p); }) + [](const SpCxPauliTensor &qps) { return qps.hash_value(); }) + .def("__repr__", &SpCxPauliTensor::to_str) + .def("__eq__", &py_equals) + .def("__ne__", &py_not_equals) + .def("__lt__", &SpCxPauliTensor::operator<) + .def("__getitem__", &SpCxPauliTensor::get) + .def("__setitem__", &SpCxPauliTensor::set) .def(py::self * py::self) - .def(Complex() * py::self) - .def_readwrite( - "string", &QubitPauliTensor::string, + .def( + "__rmul__", + [](const SpCxPauliTensor &qpt, const Complex &c) { + return SpCxPauliTensor(qpt.string, qpt.coeff * c); + }, + py::is_operator()) + .def_property( + "string", + [](const SpCxPauliTensor &qpt) { + // Return as SpPauliString for backwards compatibility with before + // templated PauliTensor + return SpPauliString(qpt.string); + }, + [](SpCxPauliTensor &qpt, const SpPauliString &qps) { + qpt.string = qps.string; + }, "The QubitPauliTensor's underlying :py:class:`QubitPauliString`") .def_readwrite( - "coeff", &QubitPauliTensor::coeff, + "coeff", &SpCxPauliTensor::coeff, "The global coefficient of the tensor") .def( - "compress", &QubitPauliTensor::compress, + "compress", &SpCxPauliTensor::compress, "Removes I terms to compress the sparse representation.") .def( - "commutes_with", &QubitPauliTensor::commutes_with, + "commutes_with", &SpCxPauliTensor::commutes_with, ":return: True if the two tensors commute, else False", py::arg("other")) .def( "to_sparse_matrix", - [](const QubitPauliTensor &qpt) { - return (CmplxSpMat)(qpt.coeff * qpt.string.to_sparse_matrix()); - }, + [](const SpCxPauliTensor &qpt) { return qpt.to_sparse_matrix(); }, "Represents the sparse string as a dense string (without " "padding for extra qubits) and generates the matrix for the " "tensor. Uses the ILO-BE convention, so ``Qubit(\"a\", 0)`` " @@ -326,9 +336,8 @@ PYBIND11_MODULE(pauli, m) { "\n\n:return: a sparse matrix corresponding to the tensor") .def( "to_sparse_matrix", - [](const QubitPauliTensor &qpt, unsigned n_qubits) { - return (CmplxSpMat)(qpt.coeff * - qpt.string.to_sparse_matrix(n_qubits)); + [](const SpCxPauliTensor &qpt, unsigned n_qubits) { + return qpt.to_sparse_matrix(n_qubits); }, "Represents the sparse string as a dense string over " "`n_qubits` qubits (sequentially indexed from 0 in the " @@ -341,9 +350,8 @@ PYBIND11_MODULE(pauli, m) { py::arg("n_qubits")) .def( "to_sparse_matrix", - [](const QubitPauliTensor &qpt, const py_qubit_vector_t &qubits) { - return (CmplxSpMat)(qpt.coeff * - qpt.string.to_sparse_matrix(qubits)); + [](const SpCxPauliTensor &qpt, const py_qubit_vector_t &qubits) { + return qpt.to_sparse_matrix(qubits); }, "Represents the sparse string as a dense string and generates " "the matrix for the tensor. Orders qubits according to " @@ -356,8 +364,8 @@ PYBIND11_MODULE(pauli, m) { py::arg("qubits")) .def( "dot_state", - [](const QubitPauliTensor &qpt, const Eigen::VectorXcd &state) { - return qpt.coeff * qpt.string.dot_state(state); + [](const SpCxPauliTensor &qpt, const Eigen::VectorXcd &state) { + return qpt.dot_state(state); }, "Performs the dot product of the state with the pauli tensor. " "Maps the qubits of the statevector with sequentially-indexed " @@ -369,9 +377,9 @@ PYBIND11_MODULE(pauli, m) { py::arg("state")) .def( "dot_state", - [](const QubitPauliTensor &qpt, const Eigen::VectorXcd &state, + [](const SpCxPauliTensor &qpt, const Eigen::VectorXcd &state, const py_qubit_vector_t &qubits) { - return qpt.coeff * qpt.string.dot_state(state, qubits); + return qpt.dot_state(state, qubits); }, "Performs the dot product of the state with the pauli tensor. " "Maps the qubits of the statevector according to the ordered " @@ -384,8 +392,8 @@ PYBIND11_MODULE(pauli, m) { py::arg("state"), py::arg("qubits")) .def( "state_expectation", - [](const QubitPauliTensor &qpt, const Eigen::VectorXcd &state) { - return qpt.coeff * qpt.string.state_expectation(state); + [](const SpCxPauliTensor &qpt, const Eigen::VectorXcd &state) { + return qpt.state_expectation(state); }, "Calculates the expectation value of the state with the pauli " "operator. Maps the qubits of the statevector with " @@ -397,9 +405,9 @@ PYBIND11_MODULE(pauli, m) { py::arg("state")) .def( "state_expectation", - [](const QubitPauliTensor &qpt, const Eigen::VectorXcd &state, + [](const SpCxPauliTensor &qpt, const Eigen::VectorXcd &state, const py_qubit_vector_t &qubits) { - return qpt.coeff * qpt.string.state_expectation(state, qubits); + return qpt.state_expectation(state, qubits); }, "Calculates the expectation value of the state with the pauli " "operator. Maps the qubits of the statevector according to the " @@ -412,11 +420,10 @@ PYBIND11_MODULE(pauli, m) { py::arg("state"), py::arg("qubits")) .def(py::pickle( - [](const QubitPauliTensor &qpt) { + [](const SpCxPauliTensor &qpt) { std::list qubits; std::list paulis; - for (const std::pair &qp_pair : - qpt.string.map) { + for (const std::pair &qp_pair : qpt.string) { qubits.push_back(qp_pair.first); paulis.push_back(qp_pair.second); } @@ -426,10 +433,8 @@ PYBIND11_MODULE(pauli, m) { if (t.size() != 3) throw std::runtime_error( "Invalid state: tuple size: " + std::to_string(t.size())); - return QubitPauliTensor( - QubitPauliString( - t[0].cast>(), - t[1].cast>()), + return SpCxPauliTensor( + t[0].cast>(), t[1].cast>(), t[2].cast()); })); } diff --git a/pytket/binders/tableau.cpp b/pytket/binders/tableau.cpp index d3ee43c885..a9f898ca10 100644 --- a/pytket/binders/tableau.cpp +++ b/pytket/binders/tableau.cpp @@ -69,19 +69,31 @@ PYBIND11_MODULE(tableau, m) { return str.str(); }) .def( - "get_xrow", &UnitaryTableau::get_xrow, + "get_xrow", + [](const UnitaryTableau& tab, const Qubit& qb) { + return SpCxPauliTensor(tab.get_xrow(qb)); + }, "Read off an X row as a Pauli string." "\n\n:param qb: The qubits whose X row to read off." "\n:return: The Pauli string :math:`P` such that :math:`PU=UX_{qb}`.", py::arg("qb")) .def( - "get_zrow", &UnitaryTableau::get_zrow, + "get_zrow", + [](const UnitaryTableau& tab, const Qubit& qb) { + return SpCxPauliTensor(tab.get_zrow(qb)); + }, "Read off an Z row as a Pauli string." "\n\n:param qb: The qubits whose Z row to read off." "\n:return: The Pauli string :math:`P` such that :math:`PU=UZ_{qb}`.", py::arg("qb")) .def( - "get_row_product", &UnitaryTableau::get_row_product, + "get_row_product", + [](const UnitaryTableau& tab, const SpCxPauliTensor& paulis) { + SpCxPauliTensor res = + tab.get_row_product(SpPauliStabiliser(paulis.string)); + res.coeff *= paulis.coeff; + return res; + }, "Combine rows to yield the effect of a given Pauli string." "\n\n:param paulis: The Pauli string :math:`P` to consider at the " "input." diff --git a/pytket/binders/tailoring.cpp b/pytket/binders/tailoring.cpp index ad3f1ea1d7..bc78f98333 100644 --- a/pytket/binders/tailoring.cpp +++ b/pytket/binders/tailoring.cpp @@ -26,19 +26,21 @@ namespace py = pybind11; namespace tket { -QubitPauliTensor apply_clifford_basis_change_tensor( - const QubitPauliTensor &in_pauli, const Circuit &circ) { +SpCxPauliTensor apply_clifford_basis_change_tensor( + const SpCxPauliTensor &in_pauli, const Circuit &circ) { + SpPauliStabiliser in_string(in_pauli.string); UnitaryRevTableau tab = circuit_to_unitary_rev_tableau(circ); - QubitPauliTensor new_operator = tab.get_row_product(in_pauli); + SpCxPauliTensor new_operator = tab.get_row_product(in_string); + new_operator.coeff *= in_pauli.coeff; return new_operator; } -QubitPauliString apply_clifford_basis_change_string( - const QubitPauliString &in_pauli, const Circuit &circ) { +SpPauliString apply_clifford_basis_change_string( + const SpPauliString &in_pauli, const Circuit &circ) { UnitaryRevTableau tab = circuit_to_unitary_rev_tableau(circ); - QubitPauliTensor new_operator = - tab.get_row_product(QubitPauliTensor(in_pauli)); - return new_operator.string; + SpPauliStabiliser new_operator = + tab.get_row_product(SpPauliStabiliser(in_pauli)); + return SpPauliString(new_operator.string); } PYBIND11_MODULE(tailoring, m) { diff --git a/pytket/conanfile.py b/pytket/conanfile.py index 62a6ab520b..3642b3862e 100644 --- a/pytket/conanfile.py +++ b/pytket/conanfile.py @@ -32,7 +32,7 @@ def package(self): cmake.install() def requirements(self): - self.requires("tket/1.2.62@tket/stable") + self.requires("tket/1.2.63@tket/stable") self.requires("tklog/0.3.3@tket/stable") self.requires("tkrng/0.3.3@tket/stable") self.requires("tkassert/0.3.3@tket/stable") diff --git a/pytket/pytket/_tket/pauli.pyi b/pytket/pytket/_tket/pauli.pyi index ad9daf70b4..a3ab039645 100644 --- a/pytket/pytket/_tket/pauli.pyi +++ b/pytket/pytket/_tket/pauli.pyi @@ -333,7 +333,7 @@ class QubitPauliTensor: The QubitPauliTensor's underlying :py:class:`QubitPauliString` """ @string.setter - def string(self, arg0: QubitPauliString) -> None: + def string(self, arg1: QubitPauliString) -> None: ... def pauli_string_mult(qubitpaulistring1: QubitPauliString, qubitpaulistring2: QubitPauliString) -> tuple[QubitPauliString, complex]: """ diff --git a/tket/CMakeLists.txt b/tket/CMakeLists.txt index 69d9cee448..aaa4497799 100644 --- a/tket/CMakeLists.txt +++ b/tket/CMakeLists.txt @@ -126,7 +126,7 @@ target_sources(tket src/Utils/UnitID.cpp src/Utils/HelperFunctions.cpp src/Utils/MatrixAnalysis.cpp - src/Utils/PauliStrings.cpp + src/Utils/PauliTensor.cpp src/Utils/CosSinDecomposition.cpp src/Utils/Expression.cpp src/OpType/OpDesc.cpp @@ -291,7 +291,7 @@ target_sources(tket include/tket/Utils/HelperFunctions.hpp include/tket/Utils/Json.hpp include/tket/Utils/MatrixAnalysis.hpp - include/tket/Utils/PauliStrings.hpp + include/tket/Utils/PauliTensor.hpp include/tket/Utils/SequencedContainers.hpp include/tket/Utils/Symbols.hpp include/tket/Utils/UnitID.hpp diff --git a/tket/conanfile.py b/tket/conanfile.py index dfad9fefba..c21629af57 100644 --- a/tket/conanfile.py +++ b/tket/conanfile.py @@ -23,7 +23,7 @@ class TketConan(ConanFile): name = "tket" - version = "1.2.62" + version = "1.2.63" package_type = "library" license = "Apache 2" homepage = "https://github.com/CQCL/tket" diff --git a/tket/include/tket/Characterisation/FrameRandomisation.hpp b/tket/include/tket/Characterisation/FrameRandomisation.hpp index 80895b4db0..fccbed498d 100644 --- a/tket/include/tket/Characterisation/FrameRandomisation.hpp +++ b/tket/include/tket/Characterisation/FrameRandomisation.hpp @@ -106,7 +106,7 @@ class PauliFrameRandomisation : public FrameRandomisation { } protected: - // QubitPauliTensor class used to find "out_frame" + // PauliTensor class used to find "out_frame" std::pair> get_out_frame( const OpTypeVector& in_frame, const Cycle& cycle); }; @@ -121,7 +121,7 @@ class UniversalFrameRandomisation : public FrameRandomisation { virtual ~UniversalFrameRandomisation() {} protected: - // QubitPauliTensor class used to find "out_frame" + // PauliTensor class used to find "out_frame" std::pair> get_out_frame( const OpTypeVector& in_frame, const Cycle& cycle); }; diff --git a/tket/include/tket/Circuit/AssertionSynthesis.hpp b/tket/include/tket/Circuit/AssertionSynthesis.hpp index 4ea2dfe625..1b5a48b7af 100644 --- a/tket/include/tket/Circuit/AssertionSynthesis.hpp +++ b/tket/include/tket/Circuit/AssertionSynthesis.hpp @@ -42,5 +42,5 @@ std::tuple> projector_assertion_synthesis( * readouts */ std::tuple> stabiliser_assertion_synthesis( - const PauliStabiliserList &paulis); + const PauliStabiliserVec &paulis); } // namespace tket diff --git a/tket/include/tket/Circuit/Boxes.hpp b/tket/include/tket/Circuit/Boxes.hpp index 7d1e2324a2..9b642b6927 100644 --- a/tket/include/tket/Circuit/Boxes.hpp +++ b/tket/include/tket/Circuit/Boxes.hpp @@ -619,7 +619,7 @@ class StabiliserAssertionBox : public Box { * * @param paulis a set of stabiliser Pauli strings */ - explicit StabiliserAssertionBox(const PauliStabiliserList &paulis); + explicit StabiliserAssertionBox(const PauliStabiliserVec &paulis); /** * Copy constructor @@ -640,7 +640,7 @@ class StabiliserAssertionBox : public Box { bool is_equal(const Op &op_other) const override; /** Get the pauli stabilisers */ - PauliStabiliserList get_stabilisers() const { return paulis_; } + PauliStabiliserVec get_stabilisers() const { return paulis_; } std::vector get_expected_readouts() const { return expected_readouts_; } Op_ptr dagger() const override; @@ -657,7 +657,7 @@ class StabiliserAssertionBox : public Box { void generate_circuit() const override; private: - const PauliStabiliserList paulis_; + const PauliStabiliserVec paulis_; // expected readouts the debug bits // false -> 0 // true -> 1 diff --git a/tket/include/tket/Circuit/CircUtils.hpp b/tket/include/tket/Circuit/CircUtils.hpp index 04306bfbe5..c9706161ac 100644 --- a/tket/include/tket/Circuit/CircUtils.hpp +++ b/tket/include/tket/Circuit/CircUtils.hpp @@ -18,7 +18,7 @@ #include "DAGDefs.hpp" #include "tket/Gate/GatePtr.hpp" #include "tket/Utils/EigenConfig.hpp" -#include "tket/Utils/PauliStrings.hpp" +#include "tket/Utils/PauliTensor.hpp" namespace tket { diff --git a/tket/include/tket/Circuit/PauliExpBoxes.hpp b/tket/include/tket/Circuit/PauliExpBoxes.hpp index 8fcf256c42..78dc171ea3 100644 --- a/tket/include/tket/Circuit/PauliExpBoxes.hpp +++ b/tket/include/tket/Circuit/PauliExpBoxes.hpp @@ -32,10 +32,11 @@ class PauliExpBox : public Box { /** * The operation implements the unitary operator * \f$ e^{-\frac12 i \pi t \sigma_0 \otimes \sigma_1 \otimes \cdots} \f$ - * where \f$ \sigma_i \in \{I,X,Y,Z\} \f$ are the Pauli operators. + * where \f$ \sigma_i \in \{I,X,Y,Z\} \f$ are the Pauli operators and \f$ t + * \f$ is the coefficient. */ PauliExpBox( - const std::vector &paulis, const Expr &t, + const SymPauliTensor &paulis, CXConfigType cx_config_type = CXConfigType::Tree); /** @@ -60,10 +61,10 @@ class PauliExpBox : public Box { bool is_equal(const Op &op_other) const override; /** Get the Pauli string */ - std::vector get_paulis() const { return paulis_; } + std::vector get_paulis() const { return paulis_.string; } /** Get the phase parameter */ - Expr get_phase() const { return t_; } + Expr get_phase() const { return paulis_.coeff; } /** Get the cx_config parameter (affects box decomposition) */ CXConfigType get_cx_config() const { return cx_config_; } @@ -83,16 +84,14 @@ class PauliExpBox : public Box { void generate_circuit() const override; private: - std::vector paulis_; - Expr t_; + SymPauliTensor paulis_; CXConfigType cx_config_; }; class PauliExpPairBox : public Box { public: PauliExpPairBox( - const std::vector &paulis0, const Expr &t0, - const std::vector &paulis1, const Expr &t1, + const SymPauliTensor &paulis0, const SymPauliTensor &paulis1, CXConfigType cx_config_type = CXConfigType::Tree); /** @@ -118,12 +117,12 @@ class PauliExpPairBox : public Box { /** Get Pauli strings for the pair */ std::pair, std::vector> get_paulis_pair() const { - return std::make_pair(paulis0_, paulis1_); + return std::make_pair(paulis0_.string, paulis1_.string); } /** Get phase parameters for the pair */ std::pair get_phase_pair() const { - return std::make_pair(t0_, t1_); + return std::make_pair(paulis0_.coeff, paulis1_.coeff); } /** Get the cx_config parameter (affects box decomposition) */ @@ -144,17 +143,15 @@ class PauliExpPairBox : public Box { void generate_circuit() const override; private: - std::vector paulis0_; - Expr t0_; - std::vector paulis1_; - Expr t1_; + SymPauliTensor paulis0_; + SymPauliTensor paulis1_; CXConfigType cx_config_; }; class PauliExpCommutingSetBox : public Box { public: PauliExpCommutingSetBox( - const std::vector, Expr>> &pauli_gadgets, + const std::vector &pauli_gadgets, CXConfigType cx_config_type = CXConfigType::Tree); /** @@ -201,7 +198,7 @@ class PauliExpCommutingSetBox : public Box { void generate_circuit() const override; private: - std::vector, Expr>> pauli_gadgets_; + std::vector pauli_gadgets_; CXConfigType cx_config_; }; diff --git a/tket/include/tket/Clifford/ChoiMixTableau.hpp b/tket/include/tket/Clifford/ChoiMixTableau.hpp index e275b49608..1a3a8dae91 100644 --- a/tket/include/tket/Clifford/ChoiMixTableau.hpp +++ b/tket/include/tket/Clifford/ChoiMixTableau.hpp @@ -43,7 +43,7 @@ class ChoiMixTableau { * Each row is divided into its input segment and output segment. Under the CJ * isomorphism, a row RxS means (in matrix multiplication order) SCR^T = C. * When mapped to a sparse readable representation, independent - * QubitPauliTensor objects are used for each segment, so we no longer expect + * SpPauliStabiliser objects are used for each segment, so we no longer expect * their individual phases to be +-1, instead only requiring this on their * product. * @@ -56,7 +56,7 @@ class ChoiMixTableau { enum class TableauSegment { Input, Output }; typedef std::pair col_key_t; typedef boost::bimap tableau_col_index_t; - typedef std::pair row_tensor_t; + typedef std::pair row_tensor_t; /** * The actual binary tableau. @@ -91,7 +91,7 @@ class ChoiMixTableau { unsigned n_ins = 0); /** * Construct a tableau directly from its rows. - * Each row is represented as a product of QubitPauliTensors where the first + * Each row is represented as a product of SpPauliStabilisers where the first * is over the input qubits and the second is over the outputs. */ explicit ChoiMixTableau(const std::list& rows); @@ -162,7 +162,7 @@ class ChoiMixTableau { * @param seg Whether to apply the Pauli gadget over the inputs or outputs */ void apply_pauli( - const QubitPauliTensor& pauli, unsigned half_pis, + const SpPauliStabiliser& pauli, unsigned half_pis, TableauSegment seg = TableauSegment::Output); /** diff --git a/tket/include/tket/Clifford/SymplecticTableau.hpp b/tket/include/tket/Clifford/SymplecticTableau.hpp index bff4255f7d..86c7c63532 100644 --- a/tket/include/tket/Clifford/SymplecticTableau.hpp +++ b/tket/include/tket/Clifford/SymplecticTableau.hpp @@ -16,7 +16,7 @@ #include "tket/OpType/OpType.hpp" #include "tket/Utils/MatrixAnalysis.hpp" -#include "tket/Utils/PauliStrings.hpp" +#include "tket/Utils/PauliTensor.hpp" namespace tket { @@ -92,7 +92,7 @@ class SymplecticTableau { */ explicit SymplecticTableau( const MatrixXb &xmat, const MatrixXb &zmat, const VectorXb &phase); - explicit SymplecticTableau(const PauliStabiliserList &rows); + explicit SymplecticTableau(const PauliStabiliserVec &rows); /** * Other required constructors diff --git a/tket/include/tket/Clifford/UnitaryTableau.hpp b/tket/include/tket/Clifford/UnitaryTableau.hpp index 8dcb5bef5f..45388acda0 100644 --- a/tket/include/tket/Clifford/UnitaryTableau.hpp +++ b/tket/include/tket/Clifford/UnitaryTableau.hpp @@ -78,17 +78,17 @@ class UnitaryTableau { /** * Read off an X row as a Pauli string */ - QubitPauliTensor get_xrow(const Qubit& qb) const; + SpPauliStabiliser get_xrow(const Qubit& qb) const; /** * Read off a Z row as a Pauli string */ - QubitPauliTensor get_zrow(const Qubit& qb) const; + SpPauliStabiliser get_zrow(const Qubit& qb) const; /** - * Combine rows into a single row according to a QubitPauliTensor + * Combine rows into a single row according to a SpPauliStabiliser */ - QubitPauliTensor get_row_product(const QubitPauliTensor& qpt) const; + SpPauliStabiliser get_row_product(const SpPauliStabiliser& qpt) const; /** * Access all IDs for the qubits used in the tableau. @@ -116,8 +116,8 @@ class UnitaryTableau { * @param half_pis The Clifford angle: {0, 1, 2, 3} represents {0, pi/2, pi, * -pi/2} */ - void apply_pauli_at_front(const QubitPauliTensor& pauli, unsigned half_pis); - void apply_pauli_at_end(const QubitPauliTensor& pauli, unsigned half_pis); + void apply_pauli_at_front(const SpPauliStabiliser& pauli, unsigned half_pis); + void apply_pauli_at_end(const SpPauliStabiliser& pauli, unsigned half_pis); /** * Combine two tableaux in sequence. @@ -213,17 +213,17 @@ class UnitaryRevTableau { /** * Read off an X row as a Pauli string */ - QubitPauliTensor get_xrow(const Qubit& qb) const; + SpPauliStabiliser get_xrow(const Qubit& qb) const; /** * Read off a Z row as a Pauli string */ - QubitPauliTensor get_zrow(const Qubit& qb) const; + SpPauliStabiliser get_zrow(const Qubit& qb) const; /** - * Combine rows into a single row according to a QubitPauliTensor + * Combine rows into a single row according to a SpPauliStabiliser */ - QubitPauliTensor get_row_product(const QubitPauliTensor& qpt) const; + SpPauliStabiliser get_row_product(const SpPauliStabiliser& qpt) const; /** * Access all IDs for the qubits used in the tableau. @@ -251,8 +251,8 @@ class UnitaryRevTableau { * @param half_pis The Clifford angle: {0, 1, 2, 3} represents {0, pi/2, pi, * -pi/2} */ - void apply_pauli_at_front(const QubitPauliTensor& pauli, unsigned half_pis); - void apply_pauli_at_end(const QubitPauliTensor& pauli, unsigned half_pis); + void apply_pauli_at_front(const SpPauliStabiliser& pauli, unsigned half_pis); + void apply_pauli_at_end(const SpPauliStabiliser& pauli, unsigned half_pis); /** * Combine two tableaux in sequence. diff --git a/tket/include/tket/Converters/PauliGadget.hpp b/tket/include/tket/Converters/PauliGadget.hpp index cfe8157c3c..6413ee34c5 100644 --- a/tket/include/tket/Converters/PauliGadget.hpp +++ b/tket/include/tket/Converters/PauliGadget.hpp @@ -17,7 +17,7 @@ #include #include "tket/Circuit/Circuit.hpp" -#include "tket/Utils/PauliStrings.hpp" +#include "tket/Utils/PauliTensor.hpp" namespace tket { @@ -32,12 +32,12 @@ class ImplicitPermutationNotAllowed : public std::logic_error { * Automatically uses Snake CX configuration * * @param circ circuit to append to - * @param pauli Pauli operators and their respective qubits - * @param angle angle in half-turns + * @param pauli Pauli operators and their respective qubits; coefficient gives + * rotation angle in half-turns * @param cx_config which type of CX configuration to decompose into */ void append_single_pauli_gadget( - Circuit& circ, const QubitPauliTensor& pauli, Expr angle, + Circuit& circ, const SpSymPauliTensor& pauli, CXConfigType cx_config = CXConfigType::Snake); /** @@ -46,13 +46,12 @@ void append_single_pauli_gadget( * Automatically uses Snake CX configuration * * @param circ circuit to append to - * @param pauli Pauli operators and their respective qubits - * @param angle angle in half-turns + * @param pauli Pauli operators and their respective qubits; coefficient gives + * rotation angle in half-turns * @param cx_config which type of CX configuration to decompose into */ void append_single_pauli_gadget_as_pauli_exp_box( - Circuit& circ, const QubitPauliTensor& pauli, Expr angle, - CXConfigType cx_config); + Circuit& circ, const SpSymPauliTensor& pauli, CXConfigType cx_config); /** * Append a pair of Pauli gadgets to the end of a given circuit. @@ -63,23 +62,22 @@ void append_single_pauli_gadget_as_pauli_exp_box( * (!shallow) Uses the original method with naive arrangement of CXs. * * @param circ circuit to append to - * @param pauli0 first Pauli string - * @param angle0 angle for \p pauli0 (half-turns) - * @param pauli1 second Pauli string - * @param angle1 angle for \p pauli1 (half-turns) + * @param pauli0 first Pauli string; coefficient gives rotation angle in + * half-turns + * @param pauli1 second Pauli string; coefficient gives rotation angle in + * half-turns * @param cx_config which type of CX configuration to decompose into */ void append_pauli_gadget_pair( - Circuit& circ, QubitPauliTensor pauli0, Expr angle0, - QubitPauliTensor pauli1, Expr angle1, + Circuit& circ, SpSymPauliTensor pauli0, SpSymPauliTensor pauli1, CXConfigType cx_config = CXConfigType::Snake); void append_pauli_gadget_pair_as_box( - Circuit& circ, const QubitPauliTensor& pauli0, Expr angle0, - const QubitPauliTensor& pauli1, Expr angle1, CXConfigType cx_config); + Circuit& circ, const SpSymPauliTensor& pauli0, + const SpSymPauliTensor& pauli1, CXConfigType cx_config); void append_commuting_pauli_gadget_set_as_box( - Circuit& circ, const std::list>& gadgets, + Circuit& circ, const std::list& gadgets, CXConfigType cx_config); } // namespace tket diff --git a/tket/include/tket/Diagonalisation/DiagUtils.hpp b/tket/include/tket/Diagonalisation/DiagUtils.hpp index 6cdd24b4ce..e24cd0d51e 100644 --- a/tket/include/tket/Diagonalisation/DiagUtils.hpp +++ b/tket/include/tket/Diagonalisation/DiagUtils.hpp @@ -18,18 +18,11 @@ namespace tket { -struct cmp_tensors { - bool operator()( - const QubitPauliTensor &qps1, const QubitPauliTensor &qps2) const { - return (qps1.string < qps2.string); - } -}; - /** * QubitOperator, defined to be useful for diagonalisation and * partitioning. */ -typedef std::map QubitOperator; +typedef std::map QubitOperator; void insert_into_gadget_map( QubitOperator &gadget_map, const PauliGadgetProperties &pgp); diff --git a/tket/include/tket/Diagonalisation/Diagonalisation.hpp b/tket/include/tket/Diagonalisation/Diagonalisation.hpp index a8dc4523dd..aa66d8f370 100644 --- a/tket/include/tket/Diagonalisation/Diagonalisation.hpp +++ b/tket/include/tket/Diagonalisation/Diagonalisation.hpp @@ -25,8 +25,8 @@ namespace tket { * qubit Clifford to make all Paulis I or Z */ void check_easy_diagonalise( - std::list> &gadgets, - std::set &qubits, Circuit &circ); + std::list &gadgets, std::set &qubits, + Circuit &circ); /** * Given two qubits, attempt to find a basis in which a single CX will @@ -34,7 +34,7 @@ void check_easy_diagonalise( */ std::optional> check_pair_compatibility( const Qubit &qb1, const Qubit &qb2, - const std::list> &gadgets); + const std::list &gadgets); /** * Diagonalise a qubit greedily by finding the Pauli Gadget with @@ -42,9 +42,8 @@ std::optional> check_pair_compatibility( * single qubit Cliffords and CXs to make it a `ZIII...I` string */ void greedy_diagonalise( - const std::list> &gadgets, - std::set &qubits, Conjugations &conjugations, Circuit &circ, - CXConfigType cx_config); + const std::list &gadgets, std::set &qubits, + Conjugations &conjugations, Circuit &circ, CXConfigType cx_config); /** * Diagonalise a mutually commuting set of Pauli strings. Modifies the @@ -52,13 +51,13 @@ void greedy_diagonalise( * required to generate the initial set. */ Circuit mutual_diagonalise( - std::list> &gadgets, - std::set qubits, CXConfigType cx_config); + std::list &gadgets, std::set qubits, + CXConfigType cx_config); /** - * Applies Clifford conjugations to a QubitPauliTensor + * Applies Clifford conjugations to a SpSymPauliTensor */ void apply_conjugations( - QubitPauliTensor &qps, const Conjugations &conjugations); + SpSymPauliTensor &qps, const Conjugations &conjugations); /** * Given two qubits on which to conjugate a CX gate, try to conjugate with a diff --git a/tket/include/tket/Diagonalisation/PauliPartition.hpp b/tket/include/tket/Diagonalisation/PauliPartition.hpp index 6f7a17f69c..37a64b7f3a 100644 --- a/tket/include/tket/Diagonalisation/PauliPartition.hpp +++ b/tket/include/tket/Diagonalisation/PauliPartition.hpp @@ -32,7 +32,7 @@ class UnknownPauliPartitionStrat : public std::logic_error { */ typedef boost::adjacency_list< - boost::vecS, boost::vecS, boost::undirectedS, QubitPauliString> + boost::vecS, boost::vecS, boost::undirectedS, SpPauliString> PauliACGraph; typedef boost::graph_traits::vertex_descriptor PauliACVertex; @@ -83,10 +83,10 @@ enum class GraphColourMethod { class PauliPartitionerGraph { public: explicit PauliPartitionerGraph( - const std::list& strings, PauliPartitionStrat strat); + const std::list& strings, PauliPartitionStrat strat); // KEY: the colour VALUE: all the Pauli strings assigned that colour. - std::map> partition_paulis( + std::map> partition_paulis( GraphColourMethod method) const; private: @@ -95,12 +95,12 @@ class PauliPartitionerGraph { /** * Partitions a QubitOperator into lists of mutually commuting gadgets. - * Assumes that each `QubitPauliString` is unique and does not attempt + * Assumes that each `SpPauliString` is unique and does not attempt * to combine them. If it is given non-unique tensors it will produce * inefficient results. */ -std::list> term_sequence( - const std::list& strings, PauliPartitionStrat strat, +std::list> term_sequence( + const std::list& strings, PauliPartitionStrat strat, GraphColourMethod method = GraphColourMethod::Lazy); } // namespace tket diff --git a/tket/include/tket/MeasurementSetup/MeasurementReduction.hpp b/tket/include/tket/MeasurementSetup/MeasurementReduction.hpp index b713ce2308..1b2163c512 100644 --- a/tket/include/tket/MeasurementSetup/MeasurementReduction.hpp +++ b/tket/include/tket/MeasurementSetup/MeasurementReduction.hpp @@ -29,7 +29,7 @@ namespace tket { * https://arxiv.org/abs/1908.06942, https://arxiv.org/abs/1907.03358 */ MeasurementSetup measurement_reduction( - const std::list& strings, PauliPartitionStrat strat, + const std::list& strings, PauliPartitionStrat strat, GraphColourMethod method = GraphColourMethod::Lazy, CXConfigType cx_config = CXConfigType::Snake); diff --git a/tket/include/tket/MeasurementSetup/MeasurementSetup.hpp b/tket/include/tket/MeasurementSetup/MeasurementSetup.hpp index a2744c9a9f..1b2909d932 100644 --- a/tket/include/tket/MeasurementSetup/MeasurementSetup.hpp +++ b/tket/include/tket/MeasurementSetup/MeasurementSetup.hpp @@ -16,7 +16,7 @@ #include "tket/Circuit/Circuit.hpp" #include "tket/Utils/Json.hpp" -#include "tket/Utils/PauliStrings.hpp" +#include "tket/Utils/PauliTensor.hpp" namespace tket { @@ -55,12 +55,12 @@ class MeasurementSetup { std::string to_str() const; }; struct QPSHasher { - std::size_t operator()(const QubitPauliString &qps) const { - return hash_value(qps); + std::size_t operator()(const SpPauliString &qps) const { + return qps.hash_value(); } }; typedef std::unordered_map< - QubitPauliString, std::vector, QPSHasher> + SpPauliString, std::vector, QPSHasher> measure_result_map_t; const std::vector &get_circs() const { return measurement_circs; } @@ -68,13 +68,7 @@ class MeasurementSetup { void add_measurement_circuit(const Circuit &circ); void add_result_for_term( - const QubitPauliString &term, const MeasurementBitMap &result); - /** - * Rejects the coefficient in the QubitPauliTensor. Used for - * convenience at the C++ level, and should not be exposed. - */ - void add_result_for_term( - const QubitPauliTensor &term, const MeasurementBitMap &result); + const SpPauliString &term, const MeasurementBitMap &result); /** * Checks that the tensors to be measured correspond to the * correct tensors generated by the measurement circs. Includes diff --git a/tket/include/tket/Ops/Op.hpp b/tket/include/tket/Ops/Op.hpp index f8784bb063..ef23176101 100644 --- a/tket/include/tket/Ops/Op.hpp +++ b/tket/include/tket/Ops/Op.hpp @@ -32,9 +32,10 @@ #include "tket/OpType/OpTypeFunctions.hpp" #include "tket/OpType/OpTypeInfo.hpp" #include "tket/Utils/Constants.hpp" +#include "tket/Utils/EigenConfig.hpp" #include "tket/Utils/Expression.hpp" #include "tket/Utils/Json.hpp" -#include "tket/Utils/PauliStrings.hpp" +#include "tket/Utils/PauliTensor.hpp" #include "tket/Utils/UnitID.hpp" namespace tket { diff --git a/tket/include/tket/PauliGraph/ConjugatePauliFunctions.hpp b/tket/include/tket/PauliGraph/ConjugatePauliFunctions.hpp index 724cc26e63..cef3088298 100644 --- a/tket/include/tket/PauliGraph/ConjugatePauliFunctions.hpp +++ b/tket/include/tket/PauliGraph/ConjugatePauliFunctions.hpp @@ -15,7 +15,7 @@ #pragma once #include "tket/OpType/OpType.hpp" -#include "tket/Utils/PauliStrings.hpp" +#include "tket/Utils/PauliTensor.hpp" namespace tket { @@ -28,18 +28,18 @@ std::pair conjugate_Pauli( OpType op, Pauli p, bool reverse = false); /** - * Methods to conjugate a QubitPauliTensor with Clifford gates to + * Methods to conjugate a SpPauliStabiliser with Clifford gates to * change basis * Transforms P to P' such that * reverse = false : --P'-- = --op--P--opdg-- * reverse = true : --P'-- = --opdg--P--op-- */ void conjugate_PauliTensor( - QubitPauliTensor &qpt, OpType op, const Qubit &q, bool reverse = false); + SpPauliStabiliser &qpt, OpType op, const Qubit &q, bool reverse = false); void conjugate_PauliTensor( - QubitPauliTensor &qpt, OpType op, const Qubit &q0, const Qubit &q1); + SpPauliStabiliser &qpt, OpType op, const Qubit &q0, const Qubit &q1); void conjugate_PauliTensor( - QubitPauliTensor &qpt, OpType op, const Qubit &q0, const Qubit &q1, + SpPauliStabiliser &qpt, OpType op, const Qubit &q0, const Qubit &q1, const Qubit &qb2); } // namespace tket diff --git a/tket/include/tket/PauliGraph/PauliGraph.hpp b/tket/include/tket/PauliGraph/PauliGraph.hpp index 44e1be3316..135729094a 100644 --- a/tket/include/tket/PauliGraph/PauliGraph.hpp +++ b/tket/include/tket/PauliGraph/PauliGraph.hpp @@ -20,14 +20,14 @@ #include "tket/Clifford/UnitaryTableau.hpp" #include "tket/Utils/Expression.hpp" #include "tket/Utils/GraphHeaders.hpp" -#include "tket/Utils/PauliStrings.hpp" +#include "tket/Utils/PauliTensor.hpp" #include "tket/Utils/SequencedContainers.hpp" namespace tket { class Gate; struct PauliGadgetProperties { - QubitPauliTensor tensor_; + SpPauliStabiliser tensor_; Expr angle_; }; @@ -146,7 +146,7 @@ class PauliGraph { * tableau. */ void apply_pauli_gadget_at_end( - const QubitPauliTensor &pauli, const Expr &angle); + const SpPauliStabiliser &pauli, const Expr &angle); }; } // namespace tket diff --git a/tket/include/tket/Utils/PauliStrings.hpp b/tket/include/tket/Utils/PauliStrings.hpp deleted file mode 100644 index 5d0393b871..0000000000 --- a/tket/include/tket/Utils/PauliStrings.hpp +++ /dev/null @@ -1,571 +0,0 @@ -// Copyright 2019-2023 Cambridge Quantum Computing -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once - -#include -#include -#include -#include -#include - -#include "tket/Utils/Constants.hpp" -#include "tket/Utils/EigenConfig.hpp" -#include "tket/Utils/Json.hpp" -#include "tket/Utils/UnitID.hpp" -namespace tket { - -/** Pauli not supported */ -class UnknownPauli : public std::logic_error { - public: - UnknownPauli() - : std::logic_error("Unknown Pauli. This code should be unreachable!") {} -}; -/** OpType not supported */ -class UnknownOpType : public std::logic_error { - public: - UnknownOpType() - : std::logic_error( - "Unknown OpType received when applying conjugations.") {} -}; - -class UnknownCXConfigType : public std::logic_error { - public: - UnknownCXConfigType() - : std::logic_error( - "Unknown CXConfigType received when decomposing gadget.") {} -}; - -class NonDefaultQubit : public std::logic_error { - public: - NonDefaultQubit() - : std::logic_error("Only default register Qubits are supported.") {} -}; - -typedef Eigen::SparseMatrix CmplxSpMat; - -/** Symbols for the Pauli operators (and identity) */ -enum Pauli { I, X, Y, Z }; - -NLOHMANN_JSON_SERIALIZE_ENUM( - Pauli, { - {Pauli::I, "I"}, - {Pauli::X, "X"}, - {Pauli::Y, "Y"}, - {Pauli::Z, "Z"}, - }); - -/** - * Whenever a decomposition choice of Pauli gadgets is presented, - * users may use either Snake (a.k.a. cascade, ladder), Tree (i.e. CX - * balanced tree) or Star (i.e. CXs target a common qubit). - */ -enum class CXConfigType { Snake, Tree, Star, MultiQGate }; - -NLOHMANN_JSON_SERIALIZE_ENUM( - CXConfigType, {{CXConfigType::Snake, "Snake"}, - {CXConfigType::Tree, "Tree"}, - {CXConfigType::Star, "Star"}, - {CXConfigType::MultiQGate, "MultiQGate"}}); - -typedef std::map QubitPauliMap; - -/** - * A string of Pauli letters from the alphabet {I, X, Y, Z}, - * implemented as a sparse list, indexed by qubit - */ -class QubitPauliString { - public: - QubitPauliMap map; - - /** - * Construct an identity map - */ - QubitPauliString() : map() {} - - /** - * Construct a single Pauli term - */ - QubitPauliString(const Qubit &qubit, Pauli p) : map({{qubit, p}}) {} - - /** - * Construct a string of many Pauli terms. Shortcut to make a - * string for a default qubit register, without explicitly - * constructing the QubitPauliMap - * - * @param _paulis initializer_list of Pauli letters - */ - explicit QubitPauliString(const std::initializer_list &_paulis); - - /** - * Construct a string of many Pauli terms. Shortcut to make a - * string for a default qubit register, without explicitly - * constructing the QubitPauliMap - * - * @param _paulis list of Pauli letters - */ - explicit QubitPauliString(const std::list &_paulis); - - /** - * Construct a string of many Pauli terms. Shortcut to make a - * string for a default qubit register, without explicitly - * constructing the QubitPauliMap - * - * @param _paulis vector of Pauli letters - */ - explicit QubitPauliString(const std::vector &_paulis); - - /** - * Construct several terms from lists - * (useful for python hackery, and therefore not extended to QubitPauliTensor, - * which is not exposed to python) - * - * @param qubits list of Qubits with corresponding Paulis - * @param paulis Pauli letters - */ - QubitPauliString( - const std::list &qubits, const std::list &paulis); - - /** - * Construct a string of many Pauli terms - * - * @param _map sparse representation of the full tensor - */ - explicit QubitPauliString(const QubitPauliMap &_map) : map(_map) {} - - /** - * Determine whether two strings are equivalent (ignoring I terms) - * - * @param other second string - * - * @return true iff *this and other represent the same numerical tensor - */ - bool operator==(const QubitPauliString &other) const; - - /** - * Determine whether two strings are not equivalent (ignoring I terms) - * - * @param other second string - * - * @return true iff *this and other represent different numerical tensors - */ - bool operator!=(const QubitPauliString &other) const; - - /** - * Determine the lexicographic ordering of two strings. - * Ignores I terms. - * Orders individual terms by the ordering of Qubits. - * - * @param other other string - * - * @return true iff *this orders strictly before other - */ - bool operator<(const QubitPauliString &other) const; - - /** - * Compares the lexicographical ordering of two strings. - * Ignores I terms. - * Ordered individual terms by the ordering of Qubits. - * - * @param other other string - * - * @returns -1 if *this orders strictly before other - * @returns 0 if *this and other represent the same numerical tensor - * @returns 1 if *this orders strictly after other - */ - int compare(const QubitPauliString &other) const; - - /** - * Removes I terms to compress the sparse representation. - */ - void compress(); - - /** - * Determines whether or not two strings commute. - * - * @param other String to compare this to - * - * @return true if \p other commutes with this - * @return false if \p other anti-commutes with this - */ - bool commutes_with(const QubitPauliString &other) const; - - /** - * Finds qubits with the same (non-trivial) Pauli term. - * - * @param other String to compare this to - * - * @return All qubits q where this->map[q] == other.map[q] != I - */ - std::set common_qubits(const QubitPauliString &other) const; - - /** - * Finds qubits that only occur in this string. - * - * @param other String to compare this to - * - * @return All qubits q where this->map[q] != I and other.map[q] == I - */ - std::set own_qubits(const QubitPauliString &other) const; - - /** - * Finds qubits with different (non-trivial) Pauli terms. - * - * @param other String to compare this to - * - * @return All qubits q where I != this->map[q] != other.map[q] != I - */ - std::set conflicting_qubits(const QubitPauliString &other) const; - - /** - * Readable string for sparse operator - */ - std::string to_str() const; - - /** - * Gets Pauli for a given Qubit - * - * @param q Qubit to lookup - * - * @return this->map[q] if defined, Pauli::I otherwise - */ - Pauli get(const Qubit &q) const; - - /** - * Updates this->map[q] to p - * - * @param q Qubit to update - * @param p Pauli to set this->map[q] = p - */ - void set(const Qubit &q, Pauli p); - - /** - * Hash method, required for top-level python. Does not - * distinguish between equivalent Strings, i.e. ignores I terms. - */ - friend std::size_t hash_value(const QubitPauliString &qps); - - /** - * Calculate a sparse matrix corresponding to this string. - * - * @param n_qubits Number of qubits the string covers - * - * @return A complex sparse matrix corresponding to the n_qubit operator - */ - - CmplxSpMat to_sparse_matrix() const; - CmplxSpMat to_sparse_matrix(const unsigned n_qubits) const; - CmplxSpMat to_sparse_matrix(const qubit_vector_t &qubits) const; - - Eigen::VectorXcd dot_state(const Eigen::VectorXcd &state) const; - Eigen::VectorXcd dot_state( - const Eigen::VectorXcd &state, const qubit_vector_t &qubits) const; - - Complex state_expectation(const Eigen::VectorXcd &state) const; - Complex state_expectation( - const Eigen::VectorXcd &state, const qubit_vector_t &qubits) const; -}; - -JSON_DECL(QubitPauliString); - -// a sum of QubitPauliString with complex coefficients -typedef std::vector> OperatorSum; - -/** - * A simple struct for Pauli strings with +/- phase, - * used to represent Pauli strings in a stabiliser subgroup - */ -struct PauliStabiliser { - std::vector string; - - /** true -> 1 - * false -> -1 - */ - bool coeff; - - PauliStabiliser() {} - - PauliStabiliser(const std::vector string, const bool coeff); - - /** - * Determine whether two PauliStabilisers are equivalent - * - * @param other second PauliStabiliser - * - * @return true iff *this and other represent PauliStabiliser - */ - bool operator==(const PauliStabiliser &other) const; - - /** - * Determine whether two PauliStabilisers are not equivalent - * - * @param other second PauliStabiliser - * - * @return true iff *this and other represent different PauliStabiliser - */ - bool operator!=(const PauliStabiliser &other) const; -}; - -typedef std::vector PauliStabiliserList; - -JSON_DECL(PauliStabiliser) - -/** - * Calculate a sparse matrix corresponding to a sum of QubitPauliString. - * - * @param total_operator Operator specified by sum of pauli strings - * @param n_qubits Number of qubits the string covers - * - * @return A complex sparse matrix corresponding to the n_qubit operator - */ -CmplxSpMat operator_tensor( - const OperatorSum &total_operator, unsigned n_qubits); -CmplxSpMat operator_tensor( - const OperatorSum &total_operator, const qubit_vector_t &qubits); -/** - * Calculate expectation value of QubitPauliString with respect to a state. - * - * - * @param total_operator Operator specified by sum of pauli strings - * @param state state, encoded by a complex vector - * - * @return Complex expectation value - */ -Complex operator_expectation( - const OperatorSum &total_operator, const Eigen::VectorXcd &state); -Complex operator_expectation( - const OperatorSum &total_operator, const Eigen::VectorXcd &state, - const qubit_vector_t &qubits); - -/** - * A tensor of Pauli terms - * \f$ P = i^k \sigma_1 \otimes \sigma_2 \otimes \cdots \otimes \sigma_n \f$. - * This is a QubitPauliString but with an additional complex coefficient. - */ -class QubitPauliTensor { - public: - typedef std::map, std::pair> - Mult_Matrix; - static const Mult_Matrix &get_mult_matrix(); - - QubitPauliString string; - Complex coeff; - - /** - * Construct an identity operator - */ - QubitPauliTensor() : string(), coeff(1.) {} - - /** - * Construct a constant multiple of the identity - */ - explicit QubitPauliTensor(Complex _coeff) : string(), coeff(_coeff) {} - - /** - * Construct a single Pauli term - */ - QubitPauliTensor(const Qubit &qubit, Pauli p) - : string({{qubit, p}}), coeff(1.) {} - - /** - * Construct a tensor of many Pauli terms. Shortcut to make a - * tensor for a default qubit register, without explicitly - * constructing the QubitPauliMap - * - * @param _paulis initializer_list of Pauli letters - */ - explicit QubitPauliTensor(const std::initializer_list &_paulis) - : string({_paulis}), coeff(1.) {} - - /** - * Construct a tensor of many Pauli terms. Shortcut to make a - * tensor for a default qubit register, without explicitly - * constructing the QubitPauliMap - * - * @param _paulis list of Pauli letters - */ - explicit QubitPauliTensor(const std::list &_paulis) - : string({_paulis}), coeff(1.) {} - - /** - * Construct a tensor of many Pauli terms. Shortcut to make a - * tensor for a default qubit register, without explicitly - * constructing the QubitPauliMap - * - * @param _paulis vector of Pauli letters - */ - explicit QubitPauliTensor(const std::vector &_paulis) - : string({_paulis}), coeff(1.) {} - - /** - * Construct a constant multiple of a single Pauli term - */ - QubitPauliTensor(const Qubit &qubit, Pauli p, Complex _coeff) - : string({{qubit, p}}), coeff(_coeff) {} - - /** - * Construct a tensor product of many Pauli terms - * - * @param _string sparse representation of the full tensor - */ - explicit QubitPauliTensor(const QubitPauliString &_string) - : string(_string), coeff(1.) {} - - /** - * Construct a tensor product of many Pauli terms - * - * @param _map sparse representation of the full tensor - */ - explicit QubitPauliTensor(const QubitPauliMap &_map) - : string(_map), coeff(1.) {} - - /** - * Construct an arbitrary QubitPauliTensor - * - * @param _string sparse representation of the full tensor - * @param _coeff complex coefficient - */ - QubitPauliTensor(const QubitPauliString &_string, Complex _coeff) - : string(_string), coeff(_coeff) {} - - /** - * Construct an arbitrary QubitPauliTensor - * - * @param _map sparse representation of the full tensor - * @param _coeff complex coefficient - */ - QubitPauliTensor(const QubitPauliMap &_map, Complex _coeff) - : string(_map), coeff(_coeff) {} - - /** - * Transpose as a tensor - * Since all Paulis are self-adjoint, this is the same as complex conjugate - * Adds a -1 phase if the string contains an odd number of Y's - */ - void transpose(); - - /** - * Calculate the product of two tensors - * - * @param other second tensor - * - * @return *this x other - */ - QubitPauliTensor operator*(const QubitPauliTensor &other) const; - - /** - * Determine whether two tensors are equivalent (ignoring I terms) - * - * @param other second tensor - * - * @return true iff *this and other represent the same numerical tensor - */ - bool operator==(const QubitPauliTensor &other) const; - - /** - * Determine whether two tensors are not equivalent (ignoring I terms) - * - * @param other second tensor - * - * @return true iff *this and other represent different numerical tensors - */ - bool operator!=(const QubitPauliTensor &other) const; - - /** - * Determine the lexicographic ordering of two tensors. - * Ignores I terms. - * Orders individual terms by the ordering of Qubits. - * If two terms have equivalent tensors, they are ordered by coefficient - * (lexicographically according to the pair ). - * - * @param other second tensor - * - * @return true iff *this orders strictly before other - */ - bool operator<(const QubitPauliTensor &other) const; - - /** - * Removes I terms to compress the sparse representation. - * Wrapper method for `QubitPauliString` method. - */ - void compress(); - - /** - * Determines whether or not two tensor commute. - * Wrapper method for `QubitPauliString` method. - * - * @param other Tensor to compare this to - * - * @return true if \p other commutes with this - * @return false if \p other anti-commutes with this - */ - bool commutes_with(const QubitPauliTensor &other) const; - - /** - * Finds qubits with the same (non-trivial) Pauli term. - * Wrapper method for `QubitPauliString` method. - * - * @param other Tensor to compare this to - * - * @return All qubits q where this->string.map[q] == other.string.map[q] != I - */ - std::set common_qubits(const QubitPauliTensor &other) const; - - /** - * Finds qubits that only occur in this tensor. - * Wrapper method for `QubitPauliString` method. - * - * @param other Tensor to compare this to - * - * @return All qubits q where this->string.map[q] != I and other.string.map[q] - * == I - */ - std::set own_qubits(const QubitPauliTensor &other) const; - - /** - * Finds qubits with different (non-trivial) Pauli terms. - * Wrapper method for `QubitPauliString` method. - * - * @param other Tensor to compare this to - * - * @return All qubits q where I != this->string.map[q] != other.string.map[q] - * != I - */ - std::set conflicting_qubits(const QubitPauliTensor &other) const; - - /** - * Readable string for sparse operator - */ - std::string to_str() const; - - /** - * Hash method, required for top-level python. Does not - * distinguish between equivalent Strings, i.e. ignores I terms. - */ - friend std::size_t hash_value(const QubitPauliTensor &qpt); -}; - -JSON_DECL(QubitPauliTensor); - -/** - * Multiply coefficient by a scalar - * - * @param a scalar multiplier - * @param qpt tensor - * - * @return result of the scalar multiplication - */ -QubitPauliTensor operator*(Complex a, const QubitPauliTensor &qpt); - -} // namespace tket diff --git a/tket/include/tket/Utils/PauliTensor.hpp b/tket/include/tket/Utils/PauliTensor.hpp new file mode 100644 index 0000000000..393c13e8b4 --- /dev/null +++ b/tket/include/tket/Utils/PauliTensor.hpp @@ -0,0 +1,1051 @@ +// Copyright 2019-2023 Cambridge Quantum Computing +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "tket/Utils/Constants.hpp" +#include "tket/Utils/EigenConfig.hpp" +#include "tket/Utils/Expression.hpp" +#include "tket/Utils/Json.hpp" +#include "tket/Utils/UnitID.hpp" + +namespace tket { + +/** Symbols for the Pauli operators (and identity) */ +enum Pauli { I, X, Y, Z }; + +NLOHMANN_JSON_SERIALIZE_ENUM( + Pauli, { + {Pauli::I, "I"}, + {Pauli::X, "X"}, + {Pauli::Y, "Y"}, + {Pauli::Z, "Z"}, + }); + +/** + * Whenever a decomposition choice of Pauli gadgets is presented, + * users may use either Snake (a.k.a. cascade, ladder), Tree (i.e. CX + * balanced tree) or Star (i.e. CXs target a common qubit). + */ +enum class CXConfigType { Snake, Tree, Star, MultiQGate }; + +NLOHMANN_JSON_SERIALIZE_ENUM( + CXConfigType, {{CXConfigType::Snake, "Snake"}, + {CXConfigType::Tree, "Tree"}, + {CXConfigType::Star, "Star"}, + {CXConfigType::MultiQGate, "MultiQGate"}}); + +typedef Eigen::SparseMatrix CmplxSpMat; + +/******************************************************************************* + * SCALAR COEFFICIENTS + ******************************************************************************/ + +/** + * A trivial option for PauliTensor to represent Pauli strings up to global + * scalar. + * + * Treated as +1 for casting to other coefficients and matrix evaluation. + */ +struct no_coeff_t {}; + +JSON_DECL(no_coeff_t) + +/** + * A fourth root of unity {1, i, -1, -i}, represented as an unsigned integer + * giving the power of i. + * + * E.g. val % 4: + * 0: +1 + * 1: +i + * 2: -1 + * 3: -i + * + * These are the phase coefficients generated in the Pauli group. Whilst + * stabilisers are restricted to {1, -1}, the imaginary numbers are required for + * closure under multiplication. For settings where a real value is needed, use + * PauliTensor::is_real_negative() which asserts the value is real (throws an + * exception otherwise) and returns a bool value to distinguish. + */ +typedef unsigned quarter_turns_t; + +/** + * Other options for scalar coefficients include: + * + * Complex; floating-point complex number. Mostly used by the python binder to + * present a phaseful interface with guaranteed success on converting to a + * sparse matrix. + * + * Expr; a symbolic expression for a (possibly complex) coefficient. This tends + * to be used in the context of Pauli exponentials of tracking the rotation + * angle (along with its phase), where such synthesis functions map cP to + * exp(i*cP*pi/2) (i.e. the coefficient is the angle of rotation in half-turns). + */ + +/** + * Returns the default coefficient value (scalar 1) for each coefficient type. + * + * @retval {} (no_coeff_t) + * @retval 0 (quarter_turns_t) + * @retval 1. (Complex) + * @retval 1 (Expr) + */ +template +CoeffType default_coeff() = delete; +template <> +no_coeff_t default_coeff(); +template <> +quarter_turns_t default_coeff(); +template <> +Complex default_coeff(); +template <> +Expr default_coeff(); + +/** + * Cast a coefficient to a different type. + * + * Casting to no_coeff_t just drops the coefficient to focus on the string. + * Casting from no_coeff_t treats it as the scalar 1. + * + * Casting to quarter_turns_t throws an exception if value is not in the range + * {1, i, -1, -i}. + * + * Casting from Expr throws an exception if the coefficient is symbolic. + * + * @param coeff The coefficient to cast to another type. + */ +template +NewCoeff cast_coeff(const OriginalCoeff &coeff) = delete; +template <> +no_coeff_t cast_coeff(const no_coeff_t &); +template <> +quarter_turns_t cast_coeff(const no_coeff_t &); +template <> +Complex cast_coeff(const no_coeff_t &); +template <> +Expr cast_coeff(const no_coeff_t &); +template <> +no_coeff_t cast_coeff(const quarter_turns_t &); +template <> +quarter_turns_t cast_coeff( + const quarter_turns_t &coeff); +template <> +Complex cast_coeff(const quarter_turns_t &coeff); +template <> +Expr cast_coeff(const quarter_turns_t &coeff); +template <> +no_coeff_t cast_coeff(const Complex &); +template <> +quarter_turns_t cast_coeff(const Complex &coeff); +template <> +Complex cast_coeff(const Complex &coeff); +template <> +Expr cast_coeff(const Complex &coeff); +template <> +no_coeff_t cast_coeff(const Expr &); +template <> +quarter_turns_t cast_coeff(const Expr &coeff); +template <> +Complex cast_coeff(const Expr &coeff); +template <> +Expr cast_coeff(const Expr &coeff); + +/** + * Compare two coefficients of the same type with respect to an ordering. + * + * All values of no_coeff_t are equal. + * Values of quarter_turns_t are compared modulo 4. + * Values of Complex are compared lexicographically by the real part and then + * imaginary. + * Non-symbolic values of Expr are compared as Complex, symbolic + * values are compared by the canonical form of SymEngine expressions. + * + * @param first A coefficient. + * @param second Another coefficient of the same type. + * @retval -1 first < second + * @retval 0 first == second + * @retval 1 first > second + */ +template +int compare_coeffs(const CoeffType &first, const CoeffType &second) = delete; +template <> +int compare_coeffs(const no_coeff_t &, const no_coeff_t &); +template <> +int compare_coeffs( + const quarter_turns_t &first, const quarter_turns_t &second); +template <> +int compare_coeffs(const Complex &first, const Complex &second); +template <> +int compare_coeffs(const Expr &first, const Expr &second); + +template +void print_coeff(std::ostream &os, const CoeffType &coeff) = delete; + +/** + * Generates the coefficient prefix for PauliTensor::to_str() + */ +template <> +void print_coeff(std::ostream &, const no_coeff_t &); +template <> +void print_coeff( + std::ostream &os, const quarter_turns_t &coeff); +template <> +void print_coeff(std::ostream &os, const Complex &coeff); +template <> +void print_coeff(std::ostream &os, const Expr &coeff); + +/** + * Hash a coefficient, combining it with an existing hash of another structure. + */ +template +void hash_combine_coeff(std::size_t &seed, const CoeffType &coeff) = delete; +template <> +void hash_combine_coeff(std::size_t &, const no_coeff_t &); +template <> +void hash_combine_coeff( + std::size_t &seed, const quarter_turns_t &coeff); +template <> +void hash_combine_coeff(std::size_t &seed, const Complex &coeff); +template <> +void hash_combine_coeff(std::size_t &seed, const Expr &coeff); + +/** + * Multiply together two coefficients of the same type. + * + * Multiplication of no_coeff_t is trivial. + * Multiplication of quarter_turns_t adds the unsigned values (e^{i*a*pi/2} * + * e^{i*b*pi/2} = e^{i*(a+b)*pi/2}). Multiplication of Complex/Expr is standard + * multiplication. + */ +template +CoeffType multiply_coeffs(const CoeffType &first, const CoeffType &second) = + delete; +template <> +no_coeff_t multiply_coeffs(const no_coeff_t &, const no_coeff_t &); +template <> +quarter_turns_t multiply_coeffs( + const quarter_turns_t &first, const quarter_turns_t &second); +template <> +Complex multiply_coeffs(const Complex &first, const Complex &second); +template <> +Expr multiply_coeffs(const Expr &first, const Expr &second); + +/******************************************************************************* + * PAULI CONTAINERS + ******************************************************************************/ + +/** + * A sparse, Qubit-indexed Pauli container. + * + * A QubitPauliMap is generally treated the same as if all Pauli::I entries were + * removed. + */ +typedef std::map QubitPauliMap; + +/** + * A dense, unsigned-indexed Pauli container. + * + * A DensePauliMap is generally treated the same regardless of any Pauli::Is + * padded at the end. Each qubit index is treated as the corresponding Qubit id + * from the default register. + * + * In future work, it may be interesting to consider + * a symplectic representation (representing each Pauli by a pair of bits + * representing X and Z) for both speed and memory efficiency. + */ +typedef std::vector DensePauliMap; + +/** + * Cast between two different Pauli container types. + * + * Casting into a type with restricted indices (e.g. DensePauliMap expecting + * default register qubits) may throw an exception. + * + * @param cont The Pauli container to convert to another type. + */ +template +NewContainer cast_container(const OriginalContainer &cont) = delete; +template <> +QubitPauliMap cast_container( + const QubitPauliMap &cont); +template <> +QubitPauliMap cast_container( + const DensePauliMap &cont); +template <> +DensePauliMap cast_container( + const QubitPauliMap &cont); +template <> +DensePauliMap cast_container( + const DensePauliMap &cont); + +/** + * Compare two Pauli containers of the same type for ordering. + * + * Individual Paulis are ordered alphabetically as I < X < Y < Z. + * Strings are ordered lexicographically, IZ < ZI (Zq[1] < Zq[0]). + * + * @param first A Pauli container + * @param second Another Pauli container of the same type. + * @retval -1 first < second + * @retval 0 first == second + * @retval 1 first > second + */ +template +int compare_containers( + const PauliContainer &first, const PauliContainer &second) = delete; +template <> +int compare_containers( + const QubitPauliMap &first, const QubitPauliMap &second); +template <> +int compare_containers( + const DensePauliMap &first, const DensePauliMap &second); + +/** + * Find the set of Qubits on which \p first and \p second have the same + * non-trivial Pauli (X, Y, Z). + */ +std::set common_qubits( + const QubitPauliMap &first, const QubitPauliMap &second); + +/** + * Find the set of qubits (as unsigned integer indices) on which \p first and \p + * second have the same non-trivial Pauli (X, Y, Z). + */ +std::set common_indices( + const DensePauliMap &first, const DensePauliMap &second); + +/** + * Find the set of Qubits on which \p first has a non-trivial Pauli (X, Y, Z) + * but \p second either doesn't contain or maps to I. + */ +std::set own_qubits( + const QubitPauliMap &first, const QubitPauliMap &second); + +/** + * Find the set of qubits (as unsigned integer indices) on which \p first has a + * non-trivial Pauli (X, Y, Z) but \p second either doesn't contain (>= size) or + * maps to I. + */ +std::set own_indices( + const DensePauliMap &first, const DensePauliMap &second); + +/** + * Find the set of Qubits on which \p first and \p second have distinct + * non-trivial Paulis (X, Y, Z). + */ +std::set conflicting_qubits( + const QubitPauliMap &first, const QubitPauliMap &second); + +/** + * Find the set of qubits (as unsigned integer indices) on which \p first and \p + * second have distinct non-trivial Paulis (X, Y, Z). + */ +std::set conflicting_indices( + const DensePauliMap &first, const DensePauliMap &second); + +/** + * Return whether two Pauli containers commute as Pauli strings (there are an + * even number of qubits on which they have distinct non-trivial Paulis). + */ +template +bool commuting_containers( + const PauliContainer &first, const PauliContainer &second) = delete; +template <> +bool commuting_containers( + const QubitPauliMap &first, const QubitPauliMap &second); +template <> +bool commuting_containers( + const DensePauliMap &first, const DensePauliMap &second); + +/** + * Generates the readable Pauli string portion of PauliTensor::to_str(). + * + * Dense strings are printed as e.g. XIYZ. + * Sparse strings are printed as e.g. (Xq[0], Yq[2], Zq[3]). + */ +template +void print_paulis(std::ostream &os, const PauliContainer &paulis) = delete; +template <> +void print_paulis(std::ostream &os, const QubitPauliMap &paulis); +template <> +void print_paulis(std::ostream &os, const DensePauliMap &paulis); + +/** + * Hash a Pauli container, combining it with an existing hash of another + * structure. + */ +template +void hash_combine_paulis(std::size_t &seed, const PauliContainer &paulis) = + delete; +template <> +void hash_combine_paulis( + std::size_t &seed, const QubitPauliMap &paulis); +template <> +void hash_combine_paulis( + std::size_t &seed, const DensePauliMap &paulis); + +/** + * Return the number of Pauli::Ys in the container. Used for + * PauliTensor::transpose(). + */ +template +unsigned n_ys(const PauliContainer &paulis) = delete; +template <> +unsigned n_ys(const QubitPauliMap &paulis); +template <> +unsigned n_ys(const DensePauliMap &paulis); + +/** + * Returns a const reference to a lookup table for multiplying individual + * Paulis. + * + * Maps {p0, p1} -> {k, p2} where p0*p1 = e^{i*k*pi/2}*p2, e.g. {Pauli::X, + * Pauli::Y} -> {1, Pauli::Z} (X*Y = iZ). + */ +const std::map, std::pair> & +get_mult_matrix(); + +/** + * Multiplies two Pauli containers component-wise, returning both the resulting + * phase and string. + * + * Maps {P0, P1} -> {k, P2} where P0*P1 = e^{i*k*pi/2}*P2, e.g. {XIY, YZY} -> + * {1, ZZI} (XIY*YZY = iZZI). + */ +template +std::pair multiply_strings( + const PauliContainer &first, const PauliContainer &second) = delete; +template <> +std::pair multiply_strings( + const QubitPauliMap &first, const QubitPauliMap &second); +template <> +std::pair multiply_strings( + const DensePauliMap &first, const DensePauliMap &second); + +/** + * Evaluates a Pauli container to a sparse matrix describing the tensor product + * of each Pauli in the string. + * + * The matrix gives the operator in ILO-BE format, e.g. (Zq[0], Xq[1]): + * 0 1 0 0 + * 1 0 0 0 + * 0 0 0 -1 + * 0 0 -1 0 + * + * For sparse Pauli containers, just those qubits present in the container will + * be treated as part of the Pauli string (e.g. (Zq[1], Iq[2]) is treated as ZI + * since q[0] is ignored but q[2] is present in the string), so it is often + * preferred to use the other variants which explicitly provide the expected + * qubits. + */ +template +CmplxSpMat to_sparse_matrix(const PauliContainer &paulis) = delete; +template <> +CmplxSpMat to_sparse_matrix(const QubitPauliMap &paulis); +template <> +CmplxSpMat to_sparse_matrix(const DensePauliMap &paulis); + +/** + * Evaluates a Pauli container to a sparse matrix describing the tensor product + * of each Pauli in the string. + * + * Qubits are restricted to the default register, from q[0] to q[ \p n_qubits - + * 1], then presents the operator in ILO-BE format (if a sparse container + * contains Qubits outside of this range or not in the default register, an + * exception is thrown). E.g. (Zq[0]), 2: + * 1 0 0 0 + * 0 -1 0 0 + * 0 0 1 0 + * 0 0 0 -1 + */ +template +CmplxSpMat to_sparse_matrix(const PauliContainer &paulis, unsigned n_qubits) = + delete; +template <> +CmplxSpMat to_sparse_matrix( + const QubitPauliMap &paulis, unsigned n_qubits); +template <> +CmplxSpMat to_sparse_matrix( + const DensePauliMap &paulis, unsigned n_qubits); + +/** + * Evaluates a Pauli container to a sparse matrix describing the tensor product + * of each Pauli in the string. + * + * \p qubits dictates the order of the qubits in the tensor product, with the + * operator returned in Big Endian format. E.g. (Zq[0], Xq[1]), [q[1], q[0]]: + * 0 0 1 0 + * 0 0 0 -1 + * 1 0 0 0 + * 0 -1 0 0 + */ +template +CmplxSpMat to_sparse_matrix( + const PauliContainer &paulis, const qubit_vector_t &qubits) = delete; +template <> +CmplxSpMat to_sparse_matrix( + const QubitPauliMap &paulis, const qubit_vector_t &qubits); +template <> +CmplxSpMat to_sparse_matrix( + const DensePauliMap &paulis, const qubit_vector_t &qubits); + +/******************************************************************************* + * PauliTensor TEMPLATE CLASS + ******************************************************************************/ + +/** + * PauliTensor + * + * A unified type for tensor products of Pauli operators, possibly with some + * global scalar coefficient. It is parameterised in two ways: + * - PauliContainer describes the data structure used to map qubits to Paulis. + * This may be sparse or dense, and indexed by arbitrary Qubits or unsigneds + * (referring to indices in the default register). + * - CoeffType describes the kind of coefficient stored, ranging from no data to + * restricted values, to symbolic expressions. + * + * Each implementation should be interoperable by casting. Some methods may only + * be available for certain specialisations. + */ +template +class PauliTensor { + static_assert( + std::is_same::value || + std::is_same::value, + "PauliTensor must be either dense or qubit-indexed."); + static_assert( + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value, + "PauliTensor must either support no coefficient, quarter turns, or " + "arbitrary complex (floating point or symbolic) coefficients."); + + static const CoeffType default_coeff; + + public: + PauliContainer string; + CoeffType coeff; + + /** + * Default constructor, giving the empty Pauli string with unit scalar. + */ + PauliTensor() : string(), coeff(default_coeff) {} + + /** + * Constructor directly instantiating the Pauli string and coefficient. + */ + PauliTensor( + const PauliContainer &_string, const CoeffType &_coeff = default_coeff) + : string(_string), coeff(_coeff) {} + + /** + * Convenience constructor for an individual Pauli in a sparse representation. + */ + template + PauliTensor( + const Qubit &q, Pauli p, const CoeffType &_coeff = default_coeff, + typename std::enable_if::value>::type * = + 0) + : string({{q, p}}), coeff(_coeff) {} + + /** + * Convenience constructor to immediately cast a dense Pauli string on the + * default register to a sparse representation. + */ + template + PauliTensor( + const DensePauliMap &_string, const CoeffType &_coeff = default_coeff, + typename std::enable_if::value>::type * = + 0) + : string(cast_container(_string)), + coeff(_coeff) {} + + /** + * Constructor for sparse representations which zips together an ordered list + * of Qubits and Paulis. + */ + template + PauliTensor( + const std::list &qubits, const std::list &paulis, + const CoeffType &_coeff = default_coeff, + typename std::enable_if::value>::type * = + 0) + : string(), coeff(_coeff) { + if (qubits.size() != paulis.size()) { + throw std::logic_error( + "Mismatch of Qubits and Paulis upon QubitPauliString " + "construction"); + } + std::list::const_iterator p_it = paulis.begin(); + for (const Qubit &qb : qubits) { + Pauli p = *p_it; + if (string.find(qb) != string.end()) { + throw std::logic_error( + "Non-unique Qubit inserted into QubitPauliString map"); + } + string[qb] = p; + ++p_it; + } + } + + /** + * Casting operator between different specialisations of PauliTensor. Casts + * the Pauli container and coefficient separately. + */ + template + operator PauliTensor() const { + return PauliTensor( + cast_container(string), + cast_coeff(coeff)); + } + + /** + * Compares two PauliTensors of the same type in lexicographical order by the + * Paulis first, then coefficients. + * + * Ordering rules for each of the Pauli containers or coefficient types may + * vary. + * + * @param other Another PauliTensor of the same type. + * @retval -1 this < other + * @retval 0 this == other + * @retval 1 this > other + */ + int compare(const PauliTensor &other) const { + int cont_comp = + compare_containers(this->string, other.string); + if (cont_comp != 0) return cont_comp; + return compare_coeffs(this->coeff, other.coeff); + } + bool operator==(const PauliTensor &other) const { + return (compare(other) == 0); + } + bool operator!=(const PauliTensor &other) const { + return !(*this == other); + } + bool operator<(const PauliTensor &other) const { + return compare(other) < 0; + } + + /** + * Checks for equivalence of PauliTensors, explicitly taking the coefficient + * modulo \p n + * + * This is useful for treatment of Pauli exponentials, where the coefficient + * becomes the rotation angle under exponentiation and so is unchanged under + * some modulus. + */ + template + typename std::enable_if::value, bool>::type equiv_mod( + const PauliTensor &other, unsigned n) const { + int cont_comp = + compare_containers(this->string, other.string); + return (cont_comp == 0) && equiv_expr(this->coeff, other.coeff, n); + } + + /** + * Compress a sparse PauliTensor by removing identity terms. + */ + template + typename std::enable_if::value>::type + compress() { + QubitPauliMap::iterator it = string.begin(); + while (it != string.end()) { + if (it->second == Pauli::I) + it = string.erase(it); + else + ++it; + } + } + + /** + * Checks commutation of two PauliTensors by evaluating how many Qubits have + * anti-commuting Paulis in the string. + */ + template + bool commutes_with( + const PauliTensor &other) const { + return commuting_containers(string, other.string); + } + + /** + * Find the set of Qubits on which this and \p other have the same + * non-trivial Pauli (X, Y, Z). + */ + template + typename std::enable_if< + std::is_same::value, std::set>::type + common_qubits( + const PauliTensor &other) const { + return tket::common_qubits(string, other.string); + } + + /** + * Find the set of Qubits on which this has a non-trivial Pauli (X, Y, Z) + * but \p other either doesn't contain or maps to I. + */ + template + typename std::enable_if< + std::is_same::value, std::set>::type + own_qubits(const PauliTensor &other) const { + return tket::own_qubits(string, other.string); + } + + /** + * Find the set of Qubits on which this and \p other have distinct + * non-trivial Paulis (X, Y, Z). + */ + template + typename std::enable_if< + std::is_same::value, std::set>::type + conflicting_qubits( + const PauliTensor &other) const { + return tket::conflicting_qubits(string, other.string); + } + + /** + * Find the set of qubits (as unsigned integer indices) on which this and + * \p other have the same non-trivial Pauli (X, Y, Z). + */ + template + typename std::enable_if< + std::is_same::value, std::set>::type + common_indices( + const PauliTensor &other) const { + return tket::common_indices(string, other.string); + } + + /** + * Find the set of qubits (as unsigned integer indices) on which this has a + * non-trivial Pauli (X, Y, Z) but \p other either doesn't contain (>= size) + * or maps to I. + */ + template + typename std::enable_if< + std::is_same::value, std::set>::type + own_indices(const PauliTensor &other) const { + return tket::own_indices(string, other.string); + } + + /** + * Find the set of qubits (as unsigned integer indices) on which this and + * \p other have distinct non-trivial Paulis (X, Y, Z). + */ + template + typename std::enable_if< + std::is_same::value, std::set>::type + conflicting_indices( + const PauliTensor &other) const { + return tket::conflicting_indices(string, other.string); + } + + /** + * Gets the Pauli at the given index within the string. + * + * For sparse representations, returns Pauli::I if index is not present. + */ + template + typename std::enable_if::value, Pauli>::type + get(const Qubit &qb) const { + QubitPauliMap::const_iterator i = string.find(qb); + if (i == string.end()) + return Pauli::I; + else + return i->second; + } + template + typename std::enable_if::value, Pauli>::type + get(unsigned qb) const { + if (qb >= string.size()) + return Pauli::I; + else + return string.at(qb); + } + + /** + * Sets the Pauli at the given index within the string. + */ + template + typename std::enable_if::value>::type set( + const Qubit &qb, Pauli p) { + QubitPauliMap::iterator i = string.find(qb); + if (i == string.end()) { + if (p != Pauli::I) string.insert({qb, p}); + } else { + if (p == Pauli::I) + string.erase(i); + else + i->second = p; + } + } + template + typename std::enable_if::value>::type set( + unsigned qb, Pauli p) { + if (qb >= string.size()) string.resize(qb + 1, Pauli::I); + string.at(qb) = p; + } + + /** + * Asserts coefficient is real, and returns whether it is negative. + * + * quarter_turns_t is used as the coefficient to restrict to the Pauli group. + * This is most commonly used for stabiliser methods, in which case valid + * coefficients must be +-1. It is common in such representations for these to + * be distinguished just by a single phase bit which is true if negative, + * false if positive. This method immediately gives that phase bit. + * + * @retval true if coeff % 4 == 2 (coefficient is -1) + * @retval false if coeff % 4 == 0 (coefficient is +1) + */ + template + typename std::enable_if::value, bool>::type + is_real_negative() const { + switch (coeff % 4) { + case 0: { + return false; + } + case 2: { + return true; + } + default: { + throw std::logic_error( + "is_real_negative() called on a PauliTensor with imaginary phase"); + } + } + } + + /** + * A human-readable form of the PauliTensor, incorporating the coefficient and + * Pauli string. Format may depend on the type specialisations. + */ + std::string to_str() const { + std::stringstream ss; + print_coeff(ss, coeff); + print_paulis(ss, string); + return ss.str(); + } + + /** + * Hash the PauliTensor. + */ + std::size_t hash_value() const { + std::size_t seed = 0; + hash_combine_paulis(seed, string); + hash_combine_coeff(seed, coeff); + return seed; + } + + /** + * Update this to the transpose by negating the coefficient if the string + * contains an odd number of Pauli::Ys. + */ + void transpose() { + if (n_ys(string) % 2 == 1) + coeff = multiply_coeffs( + coeff, cast_coeff(2)); + } + + /** + * Qubit-wise multiplication of two PauliTensors of the same type. + */ + PauliTensor operator*( + const PauliTensor &other) const { + std::pair prod = + multiply_strings(this->string, other.string); + CoeffType new_coeff = multiply_coeffs(this->coeff, other.coeff); + new_coeff = multiply_coeffs( + new_coeff, cast_coeff(prod.first)); + return PauliTensor(prod.second, new_coeff); + } + + /** + * Returns the set of free symbols in a symbolic PauliTensor. + */ + template + typename std::enable_if::value, SymSet>::type + free_symbols() const { + return expr_free_symbols(coeff); + } + /** + * Replaces given symbols with values in a symbolic PauliTensor. + */ + template + typename std::enable_if< + std::is_same::value, + PauliTensor>::type + symbol_substitution(const SymEngine::map_basic_basic &sub_map) const { + return PauliTensor(string, coeff.subs(sub_map)); + } + + /** + * Returns the size of the underlying Pauli string. + * + * This is taken directly from the container, so may include some qubits + * mapped to Pauli::I and vary around calling PauliTensor::compress(). + */ + unsigned size() const { return string.size(); } + + /** + * Evaluates a PauliTensor to a sparse matrix describing the tensor product + * of each Pauli in the string. Throws an exception if the coefficient is + * symbolic. + * + * The matrix gives the operator in ILO-BE format, e.g. (Zq[0], Xq[1]): + * 0 1 0 0 + * 1 0 0 0 + * 0 0 0 -1 + * 0 0 -1 0 + * + * For sparse Pauli containers, just those qubits present in the container + * will be treated as part of the Pauli string (e.g. (Zq[1], Iq[2]) is treated + * as ZI since q[0] is ignored but q[2] is present in the string), so it is + * often preferred to use the other variants which explicitly provide the + * expected qubits. + */ + CmplxSpMat to_sparse_matrix() const { + return cast_coeff(coeff) * + tket::to_sparse_matrix(string); + } + + /** + * Evaluates a PauliTensor to a sparse matrix describing the tensor product + * of each Pauli in the string. Throws an exception if the coefficient is + * symbolic. + * + * Qubits are restricted to the default register, from q[0] to q[ \p n_qubits + * - 1], then presents the operator in ILO-BE format (if a sparse container + * contains Qubits outside of this range or not in the default register, an + * exception is thrown). E.g. (Zq[0]), 2: + * 1 0 0 0 + * 0 -1 0 0 + * 0 0 1 0 + * 0 0 0 -1 + */ + CmplxSpMat to_sparse_matrix(const unsigned n_qubits) const { + return cast_coeff(coeff) * + tket::to_sparse_matrix(string, n_qubits); + } + + /** + * Evaluates a PauliTensor to a sparse matrix describing the tensor product + * of each Pauli in the string. Throws an exception if the coefficient is + * symbolic. + * + * \p qubits dictates the order of the qubits in the tensor product, with the + * operator returned in Big Endian format. E.g. (Zq[0], Xq[1]), [q[1], q[0]]: + * 0 0 1 0 + * 0 0 0 -1 + * 1 0 0 0 + * 0 -1 0 0 + */ + CmplxSpMat to_sparse_matrix(const qubit_vector_t &qubits) const { + return cast_coeff(coeff) * + tket::to_sparse_matrix(string, qubits); + } + + /** + * Applies the PauliTensor to a given statevector by matrix multiplication. + * + * Determines the number of qubits from the size of the statevector, and + * assumes default register qubits in ILO-BE format. + */ + Eigen::VectorXcd dot_state(const Eigen::VectorXcd &state) const { + // allowing room for big states + unsigned long long n = state.size(); + if (!(n && (!(n & (n - 1))))) + throw std::logic_error("Statevector size is not a power of two."); + unsigned n_qubits = 0; + while (n) { + n >>= 1; + ++n_qubits; + } + --n_qubits; + return (to_sparse_matrix(n_qubits) * state); + } + /** + * Applies the PauliTensor to a given statevector by matrix multiplication. + * + * \p qubits dictates the order of Qubits in the state, assuming a Big Endian + * format. An exception is thrown if the size of the state does not match up + * with the number of Qubits given. + */ + Eigen::VectorXcd dot_state( + const Eigen::VectorXcd &state, const qubit_vector_t &qubits) const { + if (state.size() != 1 << qubits.size()) + throw std::logic_error( + "Size of statevector does not match number of qubits passed to " + "dot_state"); + return (to_sparse_matrix(qubits) * state); + } + + /** + * Determines the expectation value of a given statevector with respect to the + * PauliTensor by matrix multiplication. + * + * Determines the number of qubits from the size of the statevector, and + * assumes default register qubits in ILO-BE format. + */ + Complex state_expectation(const Eigen::VectorXcd &state) const { + return state.dot(dot_state(state)); + } + /** + * Determines the expectation value of a given statevector with respect to the + * PauliTensor by matrix multiplication. + * + * \p qubits dictates the order of Qubits in the state, assuming a Big Endian + * format. An exception is thrown if the size of the state does not match up + * with the number of Qubits given. + */ + Complex state_expectation( + const Eigen::VectorXcd &state, const qubit_vector_t &qubits) const { + return state.dot(dot_state(state, qubits)); + } +}; + +// Initialise default coefficient +template +const CoeffType PauliTensor::default_coeff = + tket::default_coeff(); + +template +void to_json( + nlohmann::json &j, const PauliTensor &tensor) { + j["string"] = tensor.string; + j["coeff"] = tensor.coeff; +} + +template +void from_json( + const nlohmann::json &j, PauliTensor &tensor) { + tensor = PauliTensor( + j.at("string").get(), j.at("coeff").get()); +} + +/******************************************************************************* + * PauliTensor SPECIALISATION TYPEDEFS + ******************************************************************************/ + +typedef PauliTensor SpPauliString; +typedef PauliTensor PauliString; +typedef PauliTensor SpPauliStabiliser; +typedef PauliTensor PauliStabiliser; +typedef PauliTensor SpCxPauliTensor; +typedef PauliTensor CxPauliTensor; +typedef PauliTensor SpSymPauliTensor; +typedef PauliTensor SymPauliTensor; + +typedef std::vector PauliStabiliserVec; + +} // namespace tket diff --git a/tket/src/Characterisation/FrameRandomisation.cpp b/tket/src/Characterisation/FrameRandomisation.cpp index 92bd53a401..c6667ce0be 100644 --- a/tket/src/Characterisation/FrameRandomisation.cpp +++ b/tket/src/Characterisation/FrameRandomisation.cpp @@ -18,7 +18,6 @@ #include "tket/Ops/BarrierOp.hpp" #include "tket/PauliGraph/ConjugatePauliFunctions.hpp" -#include "tket/Utils/PauliStrings.hpp" namespace tket { @@ -206,7 +205,7 @@ PauliFrameRandomisation::get_out_frame( } } - QubitPauliTensor qpt(qpm); + SpPauliStabiliser qpt(qpm); for (const CycleCom& cycle_op : cycle.coms_) { switch (cycle_op.type) { @@ -243,8 +242,8 @@ PauliFrameRandomisation::get_out_frame( } } - OpTypeVector out_frame(in_frame.size()); - for (const auto& entry : qpt.string.map) { + OpTypeVector out_frame(in_frame.size(), OpType::noop); + for (const auto& entry : qpt.string) { switch (entry.second) { case Pauli::I: out_frame[entry.first.index()[0]] = OpType::noop; @@ -290,11 +289,11 @@ UniversalFrameRandomisation::get_out_frame( } } - QubitPauliTensor qpt(qpm); + SpPauliStabiliser qpt(qpm); for (const CycleCom& cycle_op : cycle.coms_) { if (cycle_op.type == OpType::Rz) { - Pauli frame_type = qpt.string.map[Qubit("frame", cycle_op.indices[0])]; + Pauli frame_type = qpt.get(Qubit("frame", cycle_op.indices[0])); if (frame_type == Pauli::X || frame_type == Pauli::Y) { to_dagger.push_back(cycle_op.address); } @@ -311,8 +310,8 @@ UniversalFrameRandomisation::get_out_frame( } } - OpTypeVector out_frame(in_frame.size()); - for (const auto& entry : qpt.string.map) { + OpTypeVector out_frame(in_frame.size(), OpType::noop); + for (const auto& entry : qpt.string) { switch (entry.second) { case Pauli::I: out_frame[entry.first.index()[0]] = OpType::noop; diff --git a/tket/src/Circuit/AssertionSynthesis.cpp b/tket/src/Circuit/AssertionSynthesis.cpp index a4e35759e1..05a7e6408e 100644 --- a/tket/src/Circuit/AssertionSynthesis.cpp +++ b/tket/src/Circuit/AssertionSynthesis.cpp @@ -280,7 +280,7 @@ std::tuple> projector_assertion_synthesis( } static unsigned get_n_qubits_from_stabilisers( - const PauliStabiliserList &paulis) { + const PauliStabiliserVec &paulis) { if (paulis.size() == 0) { throw CircuitInvalidity("Stabilisers cannot be empty"); } @@ -311,32 +311,39 @@ static unsigned add_assertion_operator( const Qubit &ancilla, std::vector &expected_readouts) { circ.add_op(OpType::Reset, {ancilla}); circ.add_op(OpType::H, {ancilla}); - for (unsigned i = 0; i < pauli.string.size(); i++) { + bool identity = true; + for (unsigned i = 0; i < pauli.size(); i++) { switch (pauli.string[i]) { case Pauli::I: break; case Pauli::X: circ.add_op(OpType::CX, {ancilla, Qubit(i)}); + identity = false; break; case Pauli::Y: circ.add_op(OpType::CY, {ancilla, Qubit(i)}); + identity = false; break; case Pauli::Z: circ.add_op(OpType::CZ, {ancilla, Qubit(i)}); + identity = false; break; } } + if (identity) + throw std::invalid_argument( + "StabiliserAssertionBox cannot assert an identity."); circ.add_op(OpType::H, {ancilla}); Bit b(debug_bit_index++); circ.add_bit(b); circ.add_op(OpType::Measure, {ancilla, b}); - expected_readouts.push_back(!pauli.coeff); + expected_readouts.push_back(pauli.is_real_negative()); return debug_bit_index; } // Assume all Pauli stabilisers have equal lengths and no identity std::tuple> stabiliser_assertion_synthesis( - const PauliStabiliserList &paulis) { + const PauliStabiliserVec &paulis) { unsigned n_qubits = get_n_qubits_from_stabilisers(paulis); std::vector expected_readouts; Circuit circ(n_qubits); diff --git a/tket/src/Circuit/Boxes.cpp b/tket/src/Circuit/Boxes.cpp index 9b811d19f6..e25d4ea83b 100644 --- a/tket/src/Circuit/Boxes.cpp +++ b/tket/src/Circuit/Boxes.cpp @@ -30,7 +30,7 @@ #include "tket/Utils/Expression.hpp" #include "tket/Utils/HelperFunctions.hpp" #include "tket/Utils/Json.hpp" -#include "tket/Utils/PauliStrings.hpp" +#include "tket/Utils/PauliTensor.hpp" namespace tket { @@ -489,8 +489,7 @@ bool ProjectorAssertionBox::is_equal(const Op &op_other) const { return m_.isApprox(other.m_); } -StabiliserAssertionBox::StabiliserAssertionBox( - const PauliStabiliserList &paulis) +StabiliserAssertionBox::StabiliserAssertionBox(const PauliStabiliserVec &paulis) : Box(OpType::StabiliserAssertionBox), paulis_(paulis), expected_readouts_({}) { @@ -508,15 +507,10 @@ Op_ptr StabiliserAssertionBox::dagger() const { } Op_ptr StabiliserAssertionBox::transpose() const { - PauliStabiliserList new_pauli_list; - for (auto &pauli : paulis_) { - int y_pauli_counter = - std::count(pauli.string.begin(), pauli.string.end(), Pauli::Y); - if (y_pauli_counter % 2 == 0) { - new_pauli_list.push_back(PauliStabiliser(pauli.string, pauli.coeff)); - } else { - new_pauli_list.push_back(PauliStabiliser(pauli.string, !pauli.coeff)); - }; + PauliStabiliserVec new_pauli_list; + for (PauliStabiliser pauli : paulis_) { + pauli.transpose(); + new_pauli_list.push_back(pauli); } return std::make_shared(new_pauli_list); } @@ -691,13 +685,23 @@ Op_ptr ProjectorAssertionBox::from_json(const nlohmann::json &j) { nlohmann::json StabiliserAssertionBox::to_json(const Op_ptr &op) { const auto &box = static_cast(*op); nlohmann::json j = core_box_json(box); - j["stabilisers"] = box.get_stabilisers(); + // Encode PauliStabiliser as Pauli vector and bool (true iff coeff 0) for + // backwards compatibility with before templated PauliTensor + std::vector, bool>> stabiliser_encoding; + for (const PauliStabiliser &stab : box.get_stabilisers()) + stabiliser_encoding.push_back({stab.string, !stab.is_real_negative()}); + j["stabilisers"] = stabiliser_encoding; return j; } Op_ptr StabiliserAssertionBox::from_json(const nlohmann::json &j) { - StabiliserAssertionBox box = - StabiliserAssertionBox(j.at("stabilisers").get()); + std::vector, bool>> stabiliser_encoding = + j.at("stabilisers") + .get, bool>>>(); + PauliStabiliserVec stabs; + for (const std::pair, bool> &stab : stabiliser_encoding) + stabs.push_back(PauliStabiliser(stab.first, stab.second ? 0 : 2)); + StabiliserAssertionBox box = StabiliserAssertionBox(stabs); return set_box_id( box, boost::lexical_cast(j.at("id").get())); diff --git a/tket/src/Circuit/PauliExpBoxes.cpp b/tket/src/Circuit/PauliExpBoxes.cpp index 22f9734048..23f81c9d8e 100644 --- a/tket/src/Circuit/PauliExpBoxes.cpp +++ b/tket/src/Circuit/PauliExpBoxes.cpp @@ -26,63 +26,56 @@ namespace tket { PauliExpBox::PauliExpBox( - const std::vector &paulis, const Expr &t, - CXConfigType cx_config_type) + const SymPauliTensor &paulis, CXConfigType cx_config_type) : Box(OpType::PauliExpBox, op_signature_t(paulis.size(), EdgeType::Quantum)), paulis_(paulis), - t_(t), cx_config_(cx_config_type) {} PauliExpBox::PauliExpBox(const PauliExpBox &other) - : Box(other), - paulis_(other.paulis_), - t_(other.t_), - cx_config_(other.cx_config_) {} + : Box(other), paulis_(other.paulis_), cx_config_(other.cx_config_) {} -PauliExpBox::PauliExpBox() : PauliExpBox({}, 0.) {} +PauliExpBox::PauliExpBox() : PauliExpBox({{}, 0}) {} bool PauliExpBox::is_clifford() const { - return equiv_0(4 * t_) || paulis_.empty(); + return equiv_0(4 * paulis_.coeff) || paulis_.string.empty(); } -SymSet PauliExpBox::free_symbols() const { return expr_free_symbols(t_); } +SymSet PauliExpBox::free_symbols() const { return paulis_.free_symbols(); } Op_ptr PauliExpBox::dagger() const { - return std::make_shared(paulis_, -t_, cx_config_); + return std::make_shared( + SymPauliTensor(paulis_.string, -paulis_.coeff), cx_config_); } Op_ptr PauliExpBox::transpose() const { - std::vector paulis = get_paulis(); - int number_y_pauli_mod2 = - std::count(paulis.begin(), paulis.end(), Pauli::Y) % 2; - // Negate the parameter if odd nr of Paulis (mult with 1 if mod2=0, -1 if - // mod2=1) - int t_fac = -number_y_pauli_mod2 * 2 + 1; - return std::make_shared(paulis_, t_fac * t_, cx_config_); + SymPauliTensor tr = paulis_; + tr.transpose(); + return std::make_shared(tr, cx_config_); } Op_ptr PauliExpBox::symbol_substitution( const SymEngine::map_basic_basic &sub_map) const { return std::make_shared( - this->paulis_, this->t_.subs(sub_map), this->cx_config_); + this->paulis_.symbol_substitution(sub_map), this->cx_config_); } void PauliExpBox::generate_circuit() const { - Circuit circ = pauli_gadget(paulis_, t_, cx_config_); + Circuit circ = pauli_gadget(paulis_.string, paulis_.coeff, cx_config_); circ_ = std::make_shared(circ); } bool PauliExpBox::is_equal(const Op &op_other) const { const PauliExpBox &other = dynamic_cast(op_other); if (id_ == other.get_id()) return true; - return equiv_expr(t_, other.t_, 4) && cx_config_ == other.cx_config_ && - paulis_ == other.paulis_; + return cx_config_ == other.cx_config_ && paulis_.equiv_mod(other.paulis_, 4); } nlohmann::json PauliExpBox::to_json(const Op_ptr &op) { const auto &box = static_cast(*op); nlohmann::json j = core_box_json(box); + // Serialise paulis and phase separately for backwards compatibility with + // before templated PauliTensor j["paulis"] = box.get_paulis(); j["phase"] = box.get_phase(); j["cx_config"] = box.get_cx_config(); @@ -91,7 +84,8 @@ nlohmann::json PauliExpBox::to_json(const Op_ptr &op) { Op_ptr PauliExpBox::from_json(const nlohmann::json &j) { PauliExpBox box = PauliExpBox( - j.at("paulis").get>(), j.at("phase").get(), + SymPauliTensor( + j.at("paulis").get>(), j.at("phase").get()), j.at("cx_config").get()); return set_box_id( box, @@ -101,15 +95,12 @@ Op_ptr PauliExpBox::from_json(const nlohmann::json &j) { REGISTER_OPFACTORY(PauliExpBox, PauliExpBox) PauliExpPairBox::PauliExpPairBox( - const std::vector &paulis0, const Expr &t0, - const std::vector &paulis1, const Expr &t1, + const SymPauliTensor &paulis0, const SymPauliTensor &paulis1, CXConfigType cx_config_type) : Box(OpType::PauliExpPairBox, op_signature_t(paulis0.size(), EdgeType::Quantum)), paulis0_(paulis0), - t0_(std::move(t0)), paulis1_(paulis1), - t1_(std::move(t1)), cx_config_(cx_config_type) { if (paulis0.size() != paulis1.size()) { throw PauliExpBoxInvalidity( @@ -121,60 +112,45 @@ PauliExpPairBox::PauliExpPairBox( PauliExpPairBox::PauliExpPairBox(const PauliExpPairBox &other) : Box(other), paulis0_(other.paulis0_), - t0_(other.t0_), paulis1_(other.paulis1_), - t1_(other.t1_), cx_config_(other.cx_config_) {} -PauliExpPairBox::PauliExpPairBox() : PauliExpPairBox({}, 0., {}, 0.) {} +PauliExpPairBox::PauliExpPairBox() : PauliExpPairBox({{}, 0}, {{}, 0}) {} bool PauliExpPairBox::is_clifford() const { - auto is_clifford0 = equiv_0(4 * t0_) || paulis0_.empty(); - auto is_clifford1 = equiv_0(4 * t1_) || paulis1_.empty(); + auto is_clifford0 = equiv_0(4 * paulis0_.coeff) || paulis0_.string.empty(); + auto is_clifford1 = equiv_0(4 * paulis1_.coeff) || paulis1_.string.empty(); return is_clifford0 && is_clifford1; } SymSet PauliExpPairBox::free_symbols() const { - return expr_free_symbols({t0_, t1_}); + return expr_free_symbols({paulis0_.coeff, paulis1_.coeff}); } Op_ptr PauliExpPairBox::dagger() const { return std::make_shared( - paulis1_, -t1_, paulis0_, -t0_, cx_config_); -} - -// Get the multiplicative change factor in Pauli angle during transpose ( -1 for -// odd nr of Y, 1 otherwise) -int transpose_angle_factor(const std::vector &paulis) { - int pauli_odd_number_y = std::count_if( - paulis.begin(), paulis.end(), - [](auto pauli) { return pauli == Pauli::Y; }) % - 2; - // transform 0 -> 1, 1 -> -1 - return -pauli_odd_number_y * 2 + 1; + SymPauliTensor(paulis1_.string, -paulis1_.coeff), + SymPauliTensor(paulis0_.string, -paulis0_.coeff), cx_config_); } Op_ptr PauliExpPairBox::transpose() const { - int pauli0_angle_factor = transpose_angle_factor(paulis0_); - int pauli1_angle_factor = transpose_angle_factor(paulis1_); - return std::make_shared( - paulis1_, pauli1_angle_factor * t1_, paulis0_, pauli0_angle_factor * t0_, - cx_config_); + SymPauliTensor tr0 = paulis0_; + tr0.transpose(); + SymPauliTensor tr1 = paulis1_; + tr1.transpose(); + return std::make_shared(tr1, tr0, cx_config_); } Op_ptr PauliExpPairBox::symbol_substitution( const SymEngine::map_basic_basic &sub_map) const { return std::make_shared( - this->paulis0_, this->t0_.subs(sub_map), this->paulis1_, - this->t1_.subs(sub_map), this->cx_config_); + this->paulis0_.symbol_substitution(sub_map), + this->paulis1_.symbol_substitution(sub_map), this->cx_config_); } void PauliExpPairBox::generate_circuit() const { Circuit circ = Circuit(paulis0_.size()); - QubitPauliTensor pauli_tensor0(paulis0_); - QubitPauliTensor pauli_tensor1(paulis1_); - append_pauli_gadget_pair( - circ, pauli_tensor0, t0_, pauli_tensor1, t1_, cx_config_); + append_pauli_gadget_pair(circ, paulis0_, paulis1_, cx_config_); circ_ = std::make_shared(circ); } @@ -182,14 +158,16 @@ bool PauliExpPairBox::is_equal(const Op &op_other) const { const PauliExpPairBox &other = dynamic_cast(op_other); if (id_ == other.get_id()) return true; - return cx_config_ == other.cx_config_ && equiv_expr(t0_, other.t0_, 4) && - equiv_expr(t1_, other.t1_, 4) && paulis0_ == other.paulis0_ && - paulis1_ == other.paulis1_; + return cx_config_ == other.cx_config_ && + paulis0_.equiv_mod(other.paulis0_, 4) && + paulis1_.equiv_mod(other.paulis1_, 4); } nlohmann::json PauliExpPairBox::to_json(const Op_ptr &op) { const auto &box = static_cast(*op); nlohmann::json j = core_box_json(box); + // Encode pauli strings and phases separately for backwards compatibility from + // before templated PauliTensor auto paulis_pair = box.get_paulis_pair(); // use vector to avoid serialising into a dictionary if the Pauli strings are // of length 2 @@ -208,7 +186,8 @@ Op_ptr PauliExpPairBox::from_json(const nlohmann::json &j) { const auto [phase0, phase1] = j.at("phase_pair").get>(); PauliExpPairBox box = PauliExpPairBox( - paulis0, phase0, paulis1, phase1, j.at("cx_config").get()); + SymPauliTensor(paulis0, phase0), SymPauliTensor(paulis1, phase1), + j.at("cx_config").get()); return set_box_id( box, boost::lexical_cast(j.at("id").get())); @@ -217,7 +196,7 @@ Op_ptr PauliExpPairBox::from_json(const nlohmann::json &j) { REGISTER_OPFACTORY(PauliExpPairBox, PauliExpPairBox) PauliExpCommutingSetBox::PauliExpCommutingSetBox( - const std::vector, Expr>> &pauli_gadgets, + const std::vector &pauli_gadgets, CXConfigType cx_config_type) : Box(OpType::PauliExpCommutingSetBox), pauli_gadgets_(pauli_gadgets), @@ -228,9 +207,9 @@ PauliExpCommutingSetBox::PauliExpCommutingSetBox( "PauliExpCommutingSetBox requires at least one Pauli string"); } // check all gadgets have same Pauli string length - auto n_qubits = pauli_gadgets[0].first.size(); + auto n_qubits = pauli_gadgets[0].size(); for (const auto &gadget : pauli_gadgets) { - if (gadget.first.size() != n_qubits) { + if (gadget.size() != n_qubits) { throw PauliExpBoxInvalidity( "the Pauli strings within PauliExpCommutingSetBox must all be the " "same length"); @@ -256,21 +235,16 @@ PauliExpCommutingSetBox::PauliExpCommutingSetBox() bool PauliExpCommutingSetBox::is_clifford() const { return std::all_of( pauli_gadgets_.begin(), pauli_gadgets_.end(), - [](const std::pair, Expr> &pauli_exp) { - return equiv_0(4 * pauli_exp.second) || pauli_exp.first.empty(); + [](const SymPauliTensor &pauli_exp) { + return equiv_0(4 * pauli_exp.coeff) || pauli_exp.string.empty(); }); } bool PauliExpCommutingSetBox::paulis_commute() const { - std::vector pauli_strings; - pauli_strings.reserve(pauli_gadgets_.size()); - for (const auto &pauli_gadget : pauli_gadgets_) { - pauli_strings.emplace_back(pauli_gadget.first); - } - for (auto string0 = pauli_strings.begin(); string0 != pauli_strings.end(); - string0++) { - for (auto string1 = string0 + 1; string1 != pauli_strings.end(); - string1++) { + for (auto string0 = pauli_gadgets_.begin(); string0 != pauli_gadgets_.end(); + ++string0) { + for (auto string1 = std::next(string0); string1 != pauli_gadgets_.end(); + ++string1) { if (!string0->commutes_with(*string1)) { return false; } @@ -282,25 +256,25 @@ bool PauliExpCommutingSetBox::paulis_commute() const { SymSet PauliExpCommutingSetBox::free_symbols() const { std::vector angles; for (const auto &pauli_exp : pauli_gadgets_) { - angles.push_back(pauli_exp.second); + angles.push_back(pauli_exp.coeff); } return expr_free_symbols(angles); } Op_ptr PauliExpCommutingSetBox::dagger() const { - std::vector, Expr>> dagger_gadgets; + std::vector dagger_gadgets; for (const auto &pauli_exp : pauli_gadgets_) { - dagger_gadgets.emplace_back(pauli_exp.first, -pauli_exp.second); + dagger_gadgets.emplace_back(pauli_exp.string, -pauli_exp.coeff); } return std::make_shared(dagger_gadgets, cx_config_); } Op_ptr PauliExpCommutingSetBox::transpose() const { - std::vector, Expr>> transpose_gadgets; + std::vector transpose_gadgets; for (const auto &pauli_exp : pauli_gadgets_) { - int pauli_angle_factor = transpose_angle_factor(pauli_exp.first); - transpose_gadgets.emplace_back( - pauli_exp.first, pauli_angle_factor * pauli_exp.second); + SymPauliTensor tr = pauli_exp; + tr.transpose(); + transpose_gadgets.push_back(tr); } return std::make_shared( transpose_gadgets, cx_config_); @@ -308,23 +282,21 @@ Op_ptr PauliExpCommutingSetBox::transpose() const { Op_ptr PauliExpCommutingSetBox::symbol_substitution( const SymEngine::map_basic_basic &sub_map) const { - std::vector, Expr>> symbol_sub_gadgets; + std::vector symbol_sub_gadgets; for (const auto &pauli_exp : pauli_gadgets_) { - symbol_sub_gadgets.emplace_back( - pauli_exp.first, pauli_exp.second.subs(sub_map)); + symbol_sub_gadgets.push_back(pauli_exp.symbol_substitution(sub_map)); } return std::make_shared( symbol_sub_gadgets, this->cx_config_); } void PauliExpCommutingSetBox::generate_circuit() const { - unsigned n_qubits = pauli_gadgets_[0].first.size(); + unsigned n_qubits = pauli_gadgets_[0].size(); Circuit circ(n_qubits); - std::list> gadgets; + std::list gadgets; for (const auto &pauli_gadget : pauli_gadgets_) { - gadgets.emplace_back( - QubitPauliTensor(pauli_gadget.first), pauli_gadget.second); + gadgets.push_back((SpSymPauliTensor)pauli_gadget); } std::set qubits; for (unsigned i = 0; i < n_qubits; i++) qubits.insert(Qubit(i)); @@ -333,8 +305,8 @@ void PauliExpCommutingSetBox::generate_circuit() const { Circuit phase_poly_circ(n_qubits); - for (const std::pair &pgp : gadgets) { - append_single_pauli_gadget(phase_poly_circ, pgp.first, pgp.second); + for (const SpSymPauliTensor &pgp : gadgets) { + append_single_pauli_gadget(phase_poly_circ, pgp); } phase_poly_circ.decompose_boxes_recursively(); PhasePolyBox ppbox(phase_poly_circ); @@ -349,39 +321,41 @@ void PauliExpCommutingSetBox::generate_circuit() const { circ_ = std::make_shared(circ); } -// check two gadges are semantically equal -static bool gadget_compare( - const std::vector, Expr>> &g1, - const std::vector, Expr>> &g2) { - return std::equal( - g1.begin(), g1.end(), g2.begin(), g2.end(), - [](const std::pair, Expr> &a, - const std::pair, Expr> &b) { - return a.first == b.first && equiv_expr(a.second, b.second, 4); - }); -} - bool PauliExpCommutingSetBox::is_equal(const Op &op_other) const { const PauliExpCommutingSetBox &other = dynamic_cast(op_other); if (id_ == other.get_id()) return true; - return cx_config_ == other.cx_config_ && - gadget_compare(pauli_gadgets_, other.pauli_gadgets_); + if (cx_config_ != other.cx_config_) return false; + return std::equal( + pauli_gadgets_.begin(), pauli_gadgets_.end(), + other.pauli_gadgets_.begin(), other.pauli_gadgets_.end(), + [](const SymPauliTensor &a, const SymPauliTensor &b) { + return a.equiv_mod(b, 4); + }); } nlohmann::json PauliExpCommutingSetBox::to_json(const Op_ptr &op) { const auto &box = static_cast(*op); nlohmann::json j = core_box_json(box); - j["pauli_gadgets"] = box.get_pauli_gadgets(); + // Encode SymPauliTensor as unlabelled pair of Pauli vector and Expr for + // backwards compatibility from before templated PauliTensor + std::vector, Expr>> gadget_encoding; + for (const SymPauliTensor &g : box.get_pauli_gadgets()) + gadget_encoding.push_back({g.string, g.coeff}); + j["pauli_gadgets"] = gadget_encoding; j["cx_config"] = box.get_cx_config(); return j; } Op_ptr PauliExpCommutingSetBox::from_json(const nlohmann::json &j) { - PauliExpCommutingSetBox box = PauliExpCommutingSetBox( + std::vector, Expr>> gadget_encoding = j.at("pauli_gadgets") - .get, Expr>>>(), - j.at("cx_config").get()); + .get, Expr>>>(); + std::vector gadgets; + for (const std::pair, Expr> &g : gadget_encoding) + gadgets.push_back(SymPauliTensor(g.first, g.second)); + PauliExpCommutingSetBox box = + PauliExpCommutingSetBox(gadgets, j.at("cx_config").get()); return set_box_id( box, boost::lexical_cast(j.at("id").get())); diff --git a/tket/src/Circuit/Simulation/PauliExpBoxUnitaryCalculator.cpp b/tket/src/Circuit/Simulation/PauliExpBoxUnitaryCalculator.cpp index ec00d46d71..3338595e24 100644 --- a/tket/src/Circuit/Simulation/PauliExpBoxUnitaryCalculator.cpp +++ b/tket/src/Circuit/Simulation/PauliExpBoxUnitaryCalculator.cpp @@ -18,7 +18,6 @@ #include #include "tket/Circuit/PauliExpBoxes.hpp" -#include "tket/Utils/PauliStrings.hpp" namespace tket { namespace tket_sim { @@ -69,9 +68,8 @@ struct PauliExpBoxUnitaryCalculator { const std::map> pauli_map; // The tensor product matrix, all factors of i removed. - // Although QubitPauliString and QubitPauliTensor could probably - // be made to do the work for us, they are more complicated - // than we need. + // Although PauliTensor could probably be made to do the work for us, it is + // more complicated than we need. std::vector sparse_matrix; // The number of Y which occurred. @@ -170,10 +168,6 @@ void PauliExpBoxUnitaryCalculator::fill_triplets(double phase) { set_diagonals.clear(); for (const auto& entry : sparse_matrix) { - // In Utils\PauliStrings.hpp, there is - // QubitPauliTensor operator*(Complex a, const QubitPauliTensor &qpt), - // which messes up ordinary std statements like std::complex * int. - // Not really a problem, but can be unexpected. std::complex value = matrix_coefficient * std::complex(entry.value()); diff --git a/tket/src/Clifford/ChoiMixTableau.cpp b/tket/src/Clifford/ChoiMixTableau.cpp index d7e88eff4b..b481d54893 100644 --- a/tket/src/Clifford/ChoiMixTableau.cpp +++ b/tket/src/Clifford/ChoiMixTableau.cpp @@ -80,10 +80,10 @@ ChoiMixTableau::ChoiMixTableau(const std::list& rows) std::set in_qubits; std::set out_qubits; for (const row_tensor_t& row : rows) { - for (const std::pair& qb : row.first.string.map) { + for (const std::pair& qb : row.first.string) { in_qubits.insert(qb.first); } - for (const std::pair& qb : row.second.string.map) { + for (const std::pair& qb : row.second.string) { out_qubits.insert(qb.first); } } @@ -103,26 +103,19 @@ ChoiMixTableau::ChoiMixTableau(const std::list& rows) VectorXb phase = VectorXb::Zero(n_rows); unsigned r = 0; for (const row_tensor_t& row : rows) { - for (const std::pair& qb : row.first.string.map) { + for (const std::pair& qb : row.first.string) { unsigned c = col_index_.left.at(col_key_t{qb.first, TableauSegment::Input}); if (qb.second == Pauli::X || qb.second == Pauli::Y) xmat(r, c) = true; if (qb.second == Pauli::Z || qb.second == Pauli::Y) zmat(r, c) = true; } - for (const std::pair& qb : row.second.string.map) { + for (const std::pair& qb : row.second.string) { unsigned c = col_index_.left.at(col_key_t{qb.first, TableauSegment::Output}); if (qb.second == Pauli::X || qb.second == Pauli::Y) xmat(r, c) = true; if (qb.second == Pauli::Z || qb.second == Pauli::Y) zmat(r, c) = true; } - Complex ph = row.first.coeff * row.second.coeff; - if (std::abs(ph - 1.) < EPS) - phase(r) = false; - else if (std::abs(ph + 1.) < EPS) - phase(r) = true; - else - throw std::invalid_argument( - "Phase coefficient of a Choi tableau row must be +-1"); + phase(r) = row.first.is_real_negative() ^ row.second.is_real_negative(); ++r; } tab_ = SymplecticTableau(xmat, zmat, phase); @@ -163,9 +156,7 @@ ChoiMixTableau::row_tensor_t ChoiMixTableau::stab_to_row_tensor( out_qpm.insert({col.first, p}); } } - return { - QubitPauliTensor(in_qpm), - QubitPauliTensor(out_qpm, stab.coeff ? 1. : -1.)}; + return {SpPauliStabiliser(in_qpm), SpPauliStabiliser(out_qpm, stab.coeff)}; } PauliStabiliser ChoiMixTableau::row_tensor_to_stab( @@ -174,12 +165,11 @@ PauliStabiliser ChoiMixTableau::row_tensor_to_stab( for (unsigned i = 0; i < col_index_.size(); ++i) { col_key_t qb = col_index_.right.at(i); if (qb.second == TableauSegment::Input) - ps.push_back(ten.first.string.get(qb.first)); + ps.push_back(ten.first.get(qb.first)); else - ps.push_back(ten.second.string.get(qb.first)); + ps.push_back(ten.second.get(qb.first)); } - return PauliStabiliser( - ps, std::abs(ten.first.coeff * ten.second.coeff - 1.) < EPS); + return PauliStabiliser(ps, (ten.first.coeff + ten.second.coeff) % 4); } ChoiMixTableau::row_tensor_t ChoiMixTableau::get_row(unsigned i) const { @@ -194,8 +184,8 @@ ChoiMixTableau::row_tensor_t ChoiMixTableau::get_row_product( result.first = result.first * row_i.first; result.second = result.second * row_i.second; } - result.second.coeff *= result.first.coeff; - result.first.coeff = 1.; + result.second.coeff = (result.first.coeff + result.second.coeff) % 4; + result.first.coeff = 0; return result; } @@ -389,14 +379,10 @@ void ChoiMixTableau::apply_gate( } void ChoiMixTableau::apply_pauli( - const QubitPauliTensor& pauli, unsigned half_pis, TableauSegment seg) { - if (std::abs(pauli.coeff - 1.) > EPS && std::abs(pauli.coeff + 1.) > EPS) - throw std::invalid_argument( - "In ChoiMixTableau::apply_pauli, can only rotate about a " - "QubitPauliTensor with coeff +-1"); + const SpPauliStabiliser& pauli, unsigned half_pis, TableauSegment seg) { PauliStabiliser ps; if (seg == TableauSegment::Input) { - QubitPauliTensor tr = pauli; + SpPauliStabiliser tr = pauli; tr.transpose(); ps = row_tensor_to_stab({tr, {}}); } else { diff --git a/tket/src/Clifford/SymplecticTableau.cpp b/tket/src/Clifford/SymplecticTableau.cpp index 49ac53d40c..45ba0011db 100644 --- a/tket/src/Clifford/SymplecticTableau.cpp +++ b/tket/src/Clifford/SymplecticTableau.cpp @@ -75,7 +75,7 @@ SymplecticTableau::SymplecticTableau( "Tableau must have the same number of columns in x and z components."); } -SymplecticTableau::SymplecticTableau(const PauliStabiliserList &rows) { +SymplecticTableau::SymplecticTableau(const PauliStabiliserVec &rows) { n_rows_ = rows.size(); if (n_rows_ == 0) n_qubits_ = 0; @@ -90,11 +90,11 @@ SymplecticTableau::SymplecticTableau(const PauliStabiliserList &rows) { throw std::invalid_argument( "Tableau must have the same number of qubits in each row."); for (unsigned q = 0; q < n_qubits_; ++q) { - const Pauli &p = stab.string[q]; + const Pauli &p = stab.get(q); xmat_(i, q) = (p == Pauli::X) || (p == Pauli::Y); zmat_(i, q) = (p == Pauli::Z) || (p == Pauli::Y); } - phase_(i) = !stab.coeff; + phase_(i) = stab.is_real_negative(); } } @@ -106,7 +106,7 @@ PauliStabiliser SymplecticTableau::get_pauli(unsigned i) const { for (unsigned q = 0; q < n_qubits_; ++q) { str[q] = BoolPauli{xmat_(i, q), zmat_(i, q)}.to_pauli(); } - return PauliStabiliser(str, !phase_(i)); + return PauliStabiliser(str, phase_(i) ? 2 : 0); } std::ostream &operator<<(std::ostream &os, const SymplecticTableau &tab) { @@ -288,7 +288,7 @@ void SymplecticTableau::apply_pauli_gadget( pauli_xrow(i) = (p == Pauli::X) || (p == Pauli::Y); pauli_zrow(i) = (p == Pauli::Z) || (p == Pauli::Y); } - bool phase = (!pauli.coeff) ^ (half_pis == 3); + bool phase = pauli.is_real_negative() ^ (half_pis == 3); for (unsigned i = 0; i < n_rows_; ++i) { bool anti = false; diff --git a/tket/src/Clifford/UnitaryTableau.cpp b/tket/src/Clifford/UnitaryTableau.cpp index 0b7a71d551..4f0527c949 100644 --- a/tket/src/Clifford/UnitaryTableau.cpp +++ b/tket/src/Clifford/UnitaryTableau.cpp @@ -81,40 +81,34 @@ UnitaryTableau::UnitaryTableau( UnitaryTableau::UnitaryTableau() : UnitaryTableau(0) {} -QubitPauliTensor UnitaryTableau::get_xrow(const Qubit& qb) const { +SpPauliStabiliser UnitaryTableau::get_xrow(const Qubit& qb) const { unsigned uq = qubits_.left.at(qb); PauliStabiliser stab = tab_.get_pauli(uq); - std::list qbs; + QubitPauliMap qpm; for (unsigned i = 0; i < qubits_.size(); ++i) { - qbs.push_back(qubits_.right.at(i)); + qpm.insert({qubits_.right.at(i), stab.get(i)}); } - std::list string = {stab.string.begin(), stab.string.end()}; - Complex coeff = 1.; - if (!stab.coeff) coeff *= -1.; - return QubitPauliTensor(QubitPauliString(qbs, string), coeff); + return SpPauliStabiliser(qpm, stab.coeff); } -QubitPauliTensor UnitaryTableau::get_zrow(const Qubit& qb) const { +SpPauliStabiliser UnitaryTableau::get_zrow(const Qubit& qb) const { unsigned uqb = qubits_.left.at(qb); PauliStabiliser stab = tab_.get_pauli(uqb + qubits_.size()); - std::list qbs; + QubitPauliMap qpm; for (unsigned i = 0; i < qubits_.size(); ++i) { - qbs.push_back(qubits_.right.at(i)); + qpm.insert({qubits_.right.at(i), stab.get(i)}); } - std::list string = {stab.string.begin(), stab.string.end()}; - Complex coeff = 1.; - if (!stab.coeff) coeff *= -1.; - return QubitPauliTensor(QubitPauliString(qbs, string), coeff); + return SpPauliStabiliser(qpm, stab.coeff); } -QubitPauliTensor UnitaryTableau::get_row_product( - const QubitPauliTensor& qpt) const { - QubitPauliTensor result(qpt.coeff); - for (const std::pair& p : qpt.string.map) { +SpPauliStabiliser UnitaryTableau::get_row_product( + const SpPauliStabiliser& qpt) const { + SpPauliStabiliser result({}, qpt.coeff); + for (const std::pair& p : qpt.string) { auto qks_it = qubits_.left.find(p.first); if (qks_it == qubits_.left.end()) { // Acts as identity on p.first - result = result * QubitPauliTensor(p.first, p.second); + result = result * SpPauliStabiliser(p.first, p.second); } else { switch (p.second) { case Pauli::I: { @@ -128,7 +122,7 @@ QubitPauliTensor UnitaryTableau::get_row_product( // Y = iXZ result = result * get_xrow(p.first); result = result * get_zrow(p.first); - result.coeff *= i_; + result.coeff += 1; break; } case Pauli::Z: { @@ -284,22 +278,18 @@ void UnitaryTableau::apply_gate_at_end(OpType type, const qubit_vector_t& qbs) { } void UnitaryTableau::apply_pauli_at_front( - const QubitPauliTensor& pauli, unsigned half_pis) { + const SpPauliStabiliser& pauli, unsigned half_pis) { return apply_pauli_at_end(get_row_product(pauli), half_pis); } void UnitaryTableau::apply_pauli_at_end( - const QubitPauliTensor& pauli, unsigned half_pis) { + const SpPauliStabiliser& pauli, unsigned half_pis) { std::vector string(qubits_.size(), Pauli::I); - for (const std::pair& pair : pauli.string.map) { + for (const std::pair& pair : pauli.string) { unsigned uqb = qubits_.left.at(pair.first); string.at(uqb) = pair.second; } - if (pauli.coeff != 1. && pauli.coeff != -1.) - throw std::domain_error( - "Can only apply Pauli gadgets with real unit coefficients to " - "UnitaryTableaux"); - tab_.apply_pauli_gadget({string, pauli.coeff == 1.}, half_pis); + tab_.apply_pauli_gadget(PauliStabiliser(string, pauli.coeff), half_pis); } UnitaryTableau UnitaryTableau::compose( @@ -311,7 +301,7 @@ UnitaryTableau UnitaryTableau::compose( UnitaryTableau result = UnitaryTableau({}); const unsigned nqb = qbs.size(); - std::vector rows; + std::vector rows; unsigned qir = 0; for (const Qubit& qi : qbs) { @@ -321,8 +311,8 @@ UnitaryTableau UnitaryTableau::compose( rows.push_back(second.get_xrow(qi)); } else { // Sum rows of second according to entries of first - QubitPauliTensor fxrow = first.get_xrow(qi); - QubitPauliTensor rxrow = second.get_row_product(fxrow); + SpPauliStabiliser fxrow = first.get_xrow(qi); + SpPauliStabiliser rxrow = second.get_row_product(fxrow); rows.push_back(rxrow); } @@ -338,22 +328,21 @@ UnitaryTableau UnitaryTableau::compose( rows.push_back(second.get_zrow(qi)); } else { // Sum rows of second according to entries of first - QubitPauliTensor fzrow = first.get_zrow(qi); - QubitPauliTensor rzrow = second.get_row_product(fzrow); + SpPauliStabiliser fzrow = first.get_zrow(qi); + SpPauliStabiliser rzrow = second.get_row_product(fzrow); rows.push_back(rzrow); } } // Combine row lists and convert to PauliStabilisers - PauliStabiliserList all_rows; - for (const QubitPauliTensor& row : rows) { - TKET_ASSERT(row.coeff == 1. || row.coeff == -1.); + PauliStabiliserVec all_rows; + for (const SpPauliStabiliser& row : rows) { std::vector ps(nqb, Pauli::I); - for (const std::pair& p : row.string.map) { + for (const std::pair& p : row.string) { unsigned q = result.qubits_.left.at(p.first); ps[q] = p.second; } - all_rows.push_back(PauliStabiliser(ps, row.coeff == 1.)); + all_rows.push_back(PauliStabiliser(ps, row.coeff)); } result.tab_ = SymplecticTableau(all_rows); @@ -409,10 +398,10 @@ UnitaryTableau UnitaryTableau::dagger() const { // Correct phases for (unsigned i = 0; i < nqb; ++i) { - QubitPauliTensor xr = dag.get_xrow(qubits_.right.at(i)); - dag.tab_.phase_(i) = get_row_product(xr).coeff == -1.; - QubitPauliTensor zr = dag.get_zrow(qubits_.right.at(i)); - dag.tab_.phase_(i + nqb) = get_row_product(zr).coeff == -1.; + SpPauliStabiliser xr = dag.get_xrow(qubits_.right.at(i)); + dag.tab_.phase_(i) = get_row_product(xr).is_real_negative(); + SpPauliStabiliser zr = dag.get_zrow(qubits_.right.at(i)); + dag.tab_.phase_(i + nqb) = get_row_product(zr).is_real_negative(); } return dag; @@ -510,16 +499,16 @@ UnitaryRevTableau::UnitaryRevTableau(const qubit_vector_t& qbs) : tab_(qbs) {} UnitaryRevTableau::UnitaryRevTableau() : tab_() {} -QubitPauliTensor UnitaryRevTableau::get_xrow(const Qubit& qb) const { +SpPauliStabiliser UnitaryRevTableau::get_xrow(const Qubit& qb) const { return tab_.get_xrow(qb); } -QubitPauliTensor UnitaryRevTableau::get_zrow(const Qubit& qb) const { +SpPauliStabiliser UnitaryRevTableau::get_zrow(const Qubit& qb) const { return tab_.get_zrow(qb); } -QubitPauliTensor UnitaryRevTableau::get_row_product( - const QubitPauliTensor& qpt) const { +SpPauliStabiliser UnitaryRevTableau::get_row_product( + const SpPauliStabiliser& qpt) const { return tab_.get_row_product(qpt); } @@ -528,19 +517,19 @@ std::set UnitaryRevTableau::get_qubits() const { } void UnitaryRevTableau::apply_S_at_front(const Qubit& qb) { - tab_.apply_pauli_at_end(QubitPauliTensor(qb, Pauli::Z), 3); + tab_.apply_pauli_at_end(SpPauliStabiliser(qb, Pauli::Z), 3); } void UnitaryRevTableau::apply_S_at_end(const Qubit& qb) { - tab_.apply_pauli_at_front(QubitPauliTensor(qb, Pauli::Z), 3); + tab_.apply_pauli_at_front(SpPauliStabiliser(qb, Pauli::Z), 3); } void UnitaryRevTableau::apply_V_at_front(const Qubit& qb) { - tab_.apply_pauli_at_end(QubitPauliTensor(qb, Pauli::X), 3); + tab_.apply_pauli_at_end(SpPauliStabiliser(qb, Pauli::X), 3); } void UnitaryRevTableau::apply_V_at_end(const Qubit& qb) { - tab_.apply_pauli_at_front(QubitPauliTensor(qb, Pauli::X), 3); + tab_.apply_pauli_at_front(SpPauliStabiliser(qb, Pauli::X), 3); } void UnitaryRevTableau::apply_CX_at_front( @@ -566,12 +555,12 @@ void UnitaryRevTableau::apply_gate_at_end( } void UnitaryRevTableau::apply_pauli_at_front( - const QubitPauliTensor& pauli, unsigned half_pis) { + const SpPauliStabiliser& pauli, unsigned half_pis) { tab_.apply_pauli_at_end(pauli, 4 - (half_pis % 4)); } void UnitaryRevTableau::apply_pauli_at_end( - const QubitPauliTensor& pauli, unsigned half_pis) { + const SpPauliStabiliser& pauli, unsigned half_pis) { tab_.apply_pauli_at_front(pauli, 4 - (half_pis % 4)); } diff --git a/tket/src/Converters/ChoiMixTableauConverters.cpp b/tket/src/Converters/ChoiMixTableauConverters.cpp index bf124220c0..5a57e16cc8 100644 --- a/tket/src/Converters/ChoiMixTableauConverters.cpp +++ b/tket/src/Converters/ChoiMixTableauConverters.cpp @@ -101,16 +101,16 @@ std::pair cm_tableau_to_circuit(const ChoiMixTableau& t) { boost::bimap matched_qubits; // Call diagonalisation methods to diagonalise post-selected subspace - std::list> to_diag; + std::list to_diag; for (unsigned r = tab.get_n_rows(); r > 0;) { --r; ChoiMixTableau::row_tensor_t rten = tab.get_row(r); - if (rten.second.string.map.size() != 0) { + if (rten.second.size() != 0) { // Reached the rows with non-empty output segment break; } // Else, we add the row to the subspace - to_diag.push_back({rten.first, 1.}); + to_diag.push_back(rten.first); } unsigned post_selected_size = to_diag.size(); std::set diag_ins{input_qubits.begin(), input_qubits.end()}; @@ -199,8 +199,7 @@ std::pair cm_tableau_to_circuit(const ChoiMixTableau& t) { // computing the minimum distance of a code", 1997). Just settle on using // the first row for now, reducing the input and output to a single qubit ChoiMixTableau::row_tensor_t row_paulis = tab.get_row(*x_row); - for (const std::pair& p : - row_paulis.second.string.map) { + for (const std::pair& p : row_paulis.second.string) { if (matched_qubits.right.find(p.first) == matched_qubits.right.end()) { out_qb = p.first; matched_qubits.insert({in_qb, *out_qb}); @@ -209,13 +208,12 @@ std::pair cm_tableau_to_circuit(const ChoiMixTableau& t) { } // Reduce input string to just X_in_qb - if (row_paulis.first.string.get(in_qb) == Pauli::Y) { + if (row_paulis.first.get(in_qb) == Pauli::Y) { // If it is a Y, extract an Sdg gate so the Pauli is exactly X in_circ.add_op(OpType::Sdg, {in_qb}); tab.apply_S(in_qb, ChoiMixTableau::TableauSegment::Input); } - for (const std::pair& qbp : - row_paulis.first.string.map) { + for (const std::pair& qbp : row_paulis.first.string) { if (qbp.first == in_qb) continue; // Extract an entangling gate to eliminate the qubit switch (qbp.second) { @@ -246,11 +244,11 @@ std::pair cm_tableau_to_circuit(const ChoiMixTableau& t) { } // And then the same for X_out_qb - if (row_paulis.second.string.get(*out_qb) == Pauli::Y) { + if (row_paulis.second.get(*out_qb) == Pauli::Y) { // If it is a Y, extract an Sdg gate so the Pauli is exactly X out_circ_tp.add_op(OpType::Sdg, {*out_qb}); tab.apply_S(*out_qb, ChoiMixTableau::TableauSegment::Output); - } else if (row_paulis.second.string.get(*out_qb) == Pauli::Z) { + } else if (row_paulis.second.get(*out_qb) == Pauli::Z) { // If it is a Z, extract an Vdg and Sdg gate so the Pauli is exactly X out_circ_tp.add_op(OpType::Vdg, {*out_qb}); out_circ_tp.add_op(OpType::Sdg, {*out_qb}); @@ -258,7 +256,7 @@ std::pair cm_tableau_to_circuit(const ChoiMixTableau& t) { tab.apply_S(*out_qb, ChoiMixTableau::TableauSegment::Output); } for (const std::pair& qbp : - row_paulis.second.string.map) { + row_paulis.second.string) { if (qbp.first == *out_qb) continue; // Extract an entangling gate to eliminate the qubit switch (qbp.second) { @@ -307,7 +305,7 @@ std::pair cm_tableau_to_circuit(const ChoiMixTableau& t) { ChoiMixTableau::row_tensor_t row_paulis = tab.get_row(*z_row); if (!x_row) { for (const std::pair& p : - row_paulis.second.string.map) { + row_paulis.second.string) { if (matched_qubits.right.find(p.first) == matched_qubits.right.end()) { out_qb = p.first; @@ -322,8 +320,7 @@ std::pair cm_tableau_to_circuit(const ChoiMixTableau& t) { // row would have been identified as x_row instead of Z row, and if // another row was already chosen as x_row then canonical gaussian form // would imply all other rows do not contain X_in_qb or Y_in_qb - for (const std::pair& qbp : - row_paulis.first.string.map) { + for (const std::pair& qbp : row_paulis.first.string) { if (qbp.first == in_qb) continue; // Extract an entangling gate to eliminate the qubit switch (qbp.second) { @@ -357,11 +354,11 @@ std::pair cm_tableau_to_circuit(const ChoiMixTableau& t) { } // And then reduce output string to just Z_out_qb - if (row_paulis.second.string.get(*out_qb) == Pauli::Y) { + if (row_paulis.second.get(*out_qb) == Pauli::Y) { // If it is a Y, extract a Vdg gate so the Pauli is exactly Z out_circ_tp.add_op(OpType::Vdg, {*out_qb}); tab.apply_V(*out_qb, ChoiMixTableau::TableauSegment::Output); - } else if (row_paulis.second.string.get(*out_qb) == Pauli::X) { + } else if (row_paulis.second.get(*out_qb) == Pauli::X) { // If it is an X, extract an Sdg and Vdg gate so the Pauli is exactly Z // We do not need to care about messing up the X row here since if we // solved an X row then this row can't also have X by commutativity @@ -371,7 +368,7 @@ std::pair cm_tableau_to_circuit(const ChoiMixTableau& t) { tab.apply_V(*out_qb, ChoiMixTableau::TableauSegment::Output); } for (const std::pair& qbp : - row_paulis.second.string.map) { + row_paulis.second.string) { if (qbp.first == *out_qb) continue; // Extract an entangling gate to eliminate the qubit switch (qbp.second) { @@ -424,7 +421,7 @@ std::pair cm_tableau_to_circuit(const ChoiMixTableau& t) { while (out_stabs < tab.get_n_rows()) { ChoiMixTableau::row_tensor_t rten = tab.get_row(tab.get_n_rows() - 1 - out_stabs); - if (rten.first.string.map.size() != 0) { + if (rten.first.size() != 0) { // Reached the rows with non-empty input segment break; } @@ -485,8 +482,7 @@ std::pair cm_tableau_to_circuit(const ChoiMixTableau& t) { ChoiMixTableau::row_tensor_t row_paulis = tab.get_row(tab.get_n_rows() - out_stabs + r); - for (const std::pair& qbp : - row_paulis.second.string.map) { + for (const std::pair& qbp : row_paulis.second.string) { if (matched_qubits.right.find(qbp.first) == matched_qubits.right.end()) continue; // Alternate point is guaranteed to be unmatched, so always needs an @@ -576,7 +572,7 @@ std::pair cm_tableau_to_circuit(const ChoiMixTableau& t) { to_diag.clear(); for (unsigned r = 0; r < tab.get_n_rows(); ++r) { ChoiMixTableau::row_tensor_t rten = tab.get_row(r); - to_diag.push_back({rten.second, 1.}); + to_diag.push_back(rten.second); } std::set diag_outs; for (const Qubit& out : output_qubits) { @@ -614,14 +610,14 @@ std::pair cm_tableau_to_circuit(const ChoiMixTableau& t) { Circuit out_circ(output_qubits, {}); for (unsigned r = 0; r < tab.get_n_rows(); ++r) { ChoiMixTableau::row_tensor_t row = tab.get_row(r); - if (row.first.string.map.size() != 0 || row.second.string.map.size() != 1 || - row.second.string.map.begin()->second != Pauli::Z) + if (row.first.size() != 0 || row.second.size() != 1 || + row.second.string.begin()->second != Pauli::Z) throw std::logic_error( "Unexpected error during zero initialisation in ChoiMixTableau " "synthesis"); - Qubit initialised_qb = row.second.string.map.begin()->first; + Qubit initialised_qb = row.second.string.begin()->first; out_circ.qubit_create(initialised_qb); - if (row.second.coeff == -1.) { + if (row.second.is_real_negative()) { out_circ.add_op(OpType::X, {initialised_qb}); } zero_initialised.insert(initialised_qb); diff --git a/tket/src/Converters/PauliGadget.cpp b/tket/src/Converters/PauliGadget.cpp index 00ef19713e..451003776c 100644 --- a/tket/src/Converters/PauliGadget.cpp +++ b/tket/src/Converters/PauliGadget.cpp @@ -20,119 +20,88 @@ namespace tket { -Expr pauli_angle_convert_or_throw(Complex pauliCoeff, const Expr &angle) { - if (pauliCoeff == -1.) { - return -1 * angle; - } - if (pauliCoeff != 1.) { - throw CircuitInvalidity("Pauli coefficient must be +/- 1"); - } - return angle; -} - void append_single_pauli_gadget( - Circuit &circ, const QubitPauliTensor &pauli, Expr angle, - CXConfigType cx_config) { - auto converted_angle = pauli_angle_convert_or_throw(pauli.coeff, angle); - + Circuit &circ, const SpSymPauliTensor &pauli, CXConfigType cx_config) { std::vector string; unit_map_t mapping; unsigned i = 0; - for (const std::pair &term : pauli.string.map) { + for (const std::pair &term : pauli.string) { string.push_back(term.second); mapping.insert({Qubit(q_default_reg(), i), term.first}); i++; } - Circuit gadget = pauli_gadget(string, converted_angle, cx_config); + Circuit gadget = pauli_gadget(string, pauli.coeff, cx_config); circ.append_with_map(gadget, mapping); } void append_single_pauli_gadget_as_pauli_exp_box( - Circuit &circ, const QubitPauliTensor &pauli, Expr angle, - CXConfigType cx_config) { - auto converted_angle = pauli_angle_convert_or_throw(pauli.coeff, angle); - + Circuit &circ, const SpSymPauliTensor &pauli, CXConfigType cx_config) { std::vector string; std::vector mapping; - for (const std::pair &term : pauli.string.map) { + for (const std::pair &term : pauli.string) { string.push_back(term.second); mapping.push_back(term.first); } - PauliExpBox box(string, converted_angle, cx_config); + PauliExpBox box(SymPauliTensor(string, pauli.coeff), cx_config); circ.add_box(box, mapping); } void append_pauli_gadget_pair_as_box( - Circuit &circ, const QubitPauliTensor &pauli0, Expr angle0, - const QubitPauliTensor &pauli1, Expr angle1, CXConfigType cx_config) { - auto converted_angle0 = pauli_angle_convert_or_throw(pauli0.coeff, angle0); - auto converted_angle1 = pauli_angle_convert_or_throw(pauli1.coeff, angle1); - + Circuit &circ, const SpSymPauliTensor &pauli0, + const SpSymPauliTensor &pauli1, CXConfigType cx_config) { std::vector mapping; std::vector paulis0; std::vector paulis1; - - QubitPauliString pauli0_string(pauli0.string); - QubitPauliString pauli1_string(pauli1.string); - - // remove any identities - pauli0_string.compress(); - pauli1_string.compress(); - + QubitPauliMap p1map = pauli1.string; // add paulis for qubits in pauli0_string - for (const std::pair &term : pauli0_string.map) { + for (const std::pair &term : pauli0.string) { mapping.push_back(term.first); paulis0.push_back(term.second); - auto found = pauli1_string.map.find(term.first); - if (found == pauli1_string.map.end()) { + auto found = p1map.find(term.first); + if (found == p1map.end()) { paulis1.push_back(Pauli::I); } else { paulis1.push_back(found->second); - pauli1_string.map.erase(found); + p1map.erase(found); } } // add paulis for qubits in pauli1_string that weren't in pauli0_string - for (const std::pair &term : pauli1_string.map) { + for (const std::pair &term : p1map) { mapping.push_back(term.first); paulis1.push_back(term.second); paulis0.push_back(Pauli::I); // If pauli0_string contained qubit, would // have been handled above } PauliExpPairBox box( - paulis0, converted_angle0, paulis1, converted_angle1, cx_config); + SymPauliTensor(paulis0, pauli0.coeff), + SymPauliTensor(paulis1, pauli1.coeff), cx_config); circ.add_box(box, mapping); } void append_commuting_pauli_gadget_set_as_box( - Circuit &circ, const std::list> &gadgets, + Circuit &circ, const std::list &gadgets, CXConfigType cx_config) { - // Translate to QubitPauliTensors to vectors of Paulis of same length + // Translate SpSymPauliTensors to vectors of Paulis of same length // Preserves ordering of qubits std::set all_qubits; - for (const auto &gadget : gadgets) { - for (const auto &qubit_pauli : gadget.first.string.map) { + for (const SpSymPauliTensor &gadget : gadgets) { + for (const std::pair &qubit_pauli : gadget.string) { all_qubits.insert(qubit_pauli.first); } } std::vector mapping; - for (const auto &qubit : all_qubits) { + for (const Qubit &qubit : all_qubits) { mapping.push_back(qubit); } - std::vector, Expr>> pauli_gadgets; - for (const auto &gadget : gadgets) { - auto &new_gadget = pauli_gadgets.emplace_back(std::make_pair( - std::vector(), - pauli_angle_convert_or_throw(gadget.first.coeff, gadget.second))); - for (const auto &qubit : mapping) { - auto found = gadget.first.string.map.find(qubit); - if (found == gadget.first.string.map.end()) { - new_gadget.first.push_back(Pauli::I); - } else { - new_gadget.first.push_back(found->second); - } + std::vector pauli_gadgets; + for (const SpSymPauliTensor &gadget : gadgets) { + SymPauliTensor &new_gadget = + pauli_gadgets.emplace_back(DensePauliMap{}, gadget.coeff); + for (const Qubit &qubit : mapping) { + new_gadget.string.push_back(gadget.get(qubit)); } } @@ -141,8 +110,8 @@ void append_commuting_pauli_gadget_set_as_box( } static void reduce_shared_qs_by_CX_snake( - Circuit &circ, std::set &match, QubitPauliTensor &pauli0, - QubitPauliTensor &pauli1) { + Circuit &circ, std::set &match, SpSymPauliTensor &pauli0, + SpSymPauliTensor &pauli1) { unsigned match_size = match.size(); while (match_size > 1) { // We allow one match left over auto it = --match.end(); @@ -151,30 +120,30 @@ static void reduce_shared_qs_by_CX_snake( Qubit helper = *match.rbegin(); // extend CX snake circ.add_op(OpType::CX, {to_eliminate, helper}); - pauli0.string.map.erase(to_eliminate); - pauli1.string.map.erase(to_eliminate); + pauli0.string.erase(to_eliminate); + pauli1.string.erase(to_eliminate); match_size--; } } static void reduce_shared_qs_by_CX_star( - Circuit &circ, std::set &match, QubitPauliTensor &pauli0, - QubitPauliTensor &pauli1) { + Circuit &circ, std::set &match, SpSymPauliTensor &pauli0, + SpSymPauliTensor &pauli1) { std::set::iterator iter = match.begin(); for (std::set::iterator next = match.begin(); match.size() > 1; iter = next) { ++next; Qubit to_eliminate = *iter; circ.add_op(OpType::CX, {to_eliminate, *match.rbegin()}); - pauli0.string.map.erase(to_eliminate); - pauli1.string.map.erase(to_eliminate); + pauli0.string.erase(to_eliminate); + pauli1.string.erase(to_eliminate); match.erase(iter); } } static void reduce_shared_qs_by_CX_tree( - Circuit &circ, std::set &match, QubitPauliTensor &pauli0, - QubitPauliTensor &pauli1) { + Circuit &circ, std::set &match, SpSymPauliTensor &pauli0, + SpSymPauliTensor &pauli1) { while (match.size() > 1) { std::set remaining; std::set::iterator it = match.begin(); @@ -186,8 +155,8 @@ static void reduce_shared_qs_by_CX_tree( Qubit to_eliminate = *it; it++; circ.add_op(OpType::CX, {to_eliminate, maintained}); - pauli0.string.map.erase(to_eliminate); - pauli1.string.map.erase(to_eliminate); + pauli0.string.erase(to_eliminate); + pauli1.string.erase(to_eliminate); } } match = remaining; @@ -195,8 +164,8 @@ static void reduce_shared_qs_by_CX_tree( } static void reduce_shared_qs_by_CX_multiqgate( - Circuit &circ, std::set &match, QubitPauliTensor &pauli0, - QubitPauliTensor &pauli1) { + Circuit &circ, std::set &match, SpSymPauliTensor &pauli0, + SpSymPauliTensor &pauli1) { if (match.size() <= 1) { return; } @@ -208,21 +177,21 @@ static void reduce_shared_qs_by_CX_multiqgate( // use CX Qubit to_eliminate = *iter; match.erase(iter); - pauli0.string.map.erase(to_eliminate); - pauli1.string.map.erase(to_eliminate); + pauli0.string.erase(to_eliminate); + pauli1.string.erase(to_eliminate); circ.add_op(OpType::CX, {to_eliminate, target}); } else { // use XXPhase3 Qubit to_eliminate1 = *iter; match.erase(iter++); - pauli0.string.map.erase(to_eliminate1); - pauli1.string.map.erase(to_eliminate1); + pauli0.string.erase(to_eliminate1); + pauli1.string.erase(to_eliminate1); Qubit to_eliminate2 = *iter; match.erase(iter); - pauli0.string.map.erase(to_eliminate2); - pauli1.string.map.erase(to_eliminate2); + pauli0.string.erase(to_eliminate2); + pauli1.string.erase(to_eliminate2); circ.add_op(OpType::H, {to_eliminate1}); circ.add_op(OpType::H, {to_eliminate2}); @@ -234,8 +203,8 @@ static void reduce_shared_qs_by_CX_multiqgate( } void append_pauli_gadget_pair( - Circuit &circ, QubitPauliTensor pauli0, Expr angle0, - QubitPauliTensor pauli1, Expr angle1, CXConfigType cx_config) { + Circuit &circ, SpSymPauliTensor pauli0, SpSymPauliTensor pauli1, + CXConfigType cx_config) { /* * Cowtan, Dilkes, Duncan, Simmons, Sivarajah: Phase Gadget Synthesis for * Shallow Circuits, Lemma 4.9 @@ -274,16 +243,16 @@ void append_pauli_gadget_pair( * CXs */ for (const Qubit &qb : match) { - switch (pauli0.string.map.at(qb)) { + switch (pauli0.get(qb)) { case Pauli::X: u.add_op(OpType::H, {qb}); - pauli0.string.map.at(qb) = Pauli::Z; - pauli1.string.map.at(qb) = Pauli::Z; + pauli0.set(qb, Pauli::Z); + pauli1.set(qb, Pauli::Z); break; case Pauli::Y: u.add_op(OpType::V, {qb}); - pauli0.string.map.at(qb) = Pauli::Z; - pauli1.string.map.at(qb) = Pauli::Z; + pauli0.set(qb, Pauli::Z); + pauli1.set(qb, Pauli::Z); break; default: break; @@ -307,15 +276,16 @@ void append_pauli_gadget_pair( break; } default: - throw UnknownCXConfigType(); + throw std::logic_error( + "Unknown CXConfigType received when decomposing gadget."); } /* * Step 2.ii: Convert mismatches to Z in pauli0 and X in pauli1 */ for (const Qubit &qb : mismatch) { - switch (pauli0.string.map.at(qb)) { + switch (pauli0.get(qb)) { case Pauli::X: { - switch (pauli1.string.map.at(qb)) { + switch (pauli1.get(qb)) { case Pauli::Y: u.add_op(OpType::Sdg, {qb}); u.add_op(OpType::Vdg, {qb}); @@ -329,7 +299,7 @@ void append_pauli_gadget_pair( break; } case Pauli::Y: { - switch (pauli1.string.map.at(qb)) { + switch (pauli1.get(qb)) { case Pauli::X: u.add_op(OpType::V, {qb}); break; @@ -343,13 +313,12 @@ void append_pauli_gadget_pair( break; } default: { // Necessarily Z - if (pauli1.string.map.at(qb) == Pauli::Y) - u.add_op(OpType::Sdg, {qb}); + if (pauli1.get(qb) == Pauli::Y) u.add_op(OpType::Sdg, {qb}); // No need to act if already X } } - pauli0.string.map.at(qb) = Pauli::Z; - pauli1.string.map.at(qb) = Pauli::X; + pauli0.set(qb, Pauli::Z); + pauli1.set(qb, Pauli::X); } /* @@ -366,8 +335,8 @@ void append_pauli_gadget_pair( u.add_op(OpType::S, {mismatch_used}); u.add_op(OpType::CX, {last_match, mismatch_used}); u.add_op(OpType::Sdg, {mismatch_used}); - pauli0.string.map.erase(last_match); - pauli1.string.map.erase(last_match); + pauli0.string.erase(last_match); + pauli1.string.erase(last_match); } else { just0.insert(last_match); just1.insert(last_match); @@ -388,8 +357,8 @@ void append_pauli_gadget_pair( } else { Qubit x_in_1 = *mis_it; u.add_op(OpType::CX, {x_in_1, z_in_0}); - pauli0.string.map.erase(x_in_1); - pauli1.string.map.erase(z_in_0); + pauli0.string.erase(x_in_1); + pauli1.string.erase(z_in_0); just1.insert(x_in_1); mis_it++; } @@ -398,11 +367,15 @@ void append_pauli_gadget_pair( /* * Step 3: Combine circuits to give final result */ - append_single_pauli_gadget(v, pauli0, angle0); - append_single_pauli_gadget(v, pauli1, angle1); + append_single_pauli_gadget(v, pauli0); + append_single_pauli_gadget(v, pauli1); + // ConjugationBox components must be in the default register + qubit_vector_t all_qubits = u.all_qubits(); + u.flatten_registers(); + v.flatten_registers(); ConjugationBox cjbox( std::make_shared(u), std::make_shared(v)); - circ.add_box(cjbox, u.all_qubits()); + circ.add_box(cjbox, all_qubits); } } // namespace tket diff --git a/tket/src/Converters/PauliGraphConverters.cpp b/tket/src/Converters/PauliGraphConverters.cpp index 8f30dc9a8b..8f345432eb 100644 --- a/tket/src/Converters/PauliGraphConverters.cpp +++ b/tket/src/Converters/PauliGraphConverters.cpp @@ -39,7 +39,7 @@ PauliGraph circuit_to_pauli_graph(const Circuit &circ) { QubitPauliMap qpm; for (unsigned i = 0; i != args.size(); ++i) qpm.insert({Qubit(args[i]), paulis[i]}); - QubitPauliTensor qpt = pg.cliff_.get_row_product(QubitPauliTensor(qpm)); + SpPauliStabiliser qpt = pg.cliff_.get_row_product(SpPauliStabiliser(qpm)); pg.apply_pauli_gadget_at_end(qpt, phase); } else throw BadOpType( @@ -60,9 +60,10 @@ Circuit pauli_graph_to_pauli_exp_box_circuit_individually( circ.add_bit(b); } for (const PauliVert &vert : pg.vertices_in_order()) { - const QubitPauliTensor &pauli = pg.graph_[vert].tensor_; + const SpPauliStabiliser &pauli = pg.graph_[vert].tensor_; const Expr &angle = pg.graph_[vert].angle_; - append_single_pauli_gadget_as_pauli_exp_box(circ, pauli, angle, cx_config); + append_single_pauli_gadget_as_pauli_exp_box( + circ, SpSymPauliTensor(pauli) * SpSymPauliTensor({}, angle), cx_config); } Circuit cliff_circuit = unitary_rev_tableau_to_circuit(pg.cliff_); circ.append(cliff_circuit); @@ -85,22 +86,21 @@ Circuit pauli_graph_to_pauli_exp_box_circuit_pairwise( auto it = vertices.begin(); while (it != vertices.end()) { PauliVert vert0 = *it; - const QubitPauliTensor &pauli0 = pg.graph_[vert0].tensor_; + const SpPauliStabiliser &pauli0 = pg.graph_[vert0].tensor_; const Expr &angle0 = pg.graph_[vert0].angle_; ++it; if (it == vertices.end()) { - // append_single_pauli_gadget(circ, pauli0, angle0, cx_config); append_single_pauli_gadget_as_pauli_exp_box( - circ, pauli0, angle0, cx_config); + circ, SpSymPauliTensor(pauli0) * SpSymPauliTensor({}, angle0), + cx_config); } else { PauliVert vert1 = *it; - const QubitPauliTensor &pauli1 = pg.graph_[vert1].tensor_; + const SpPauliStabiliser &pauli1 = pg.graph_[vert1].tensor_; const Expr &angle1 = pg.graph_[vert1].angle_; ++it; append_pauli_gadget_pair_as_box( - circ, pauli0, angle0, pauli1, angle1, cx_config); - // append_pauli_gadget_pair(circ, pauli0, angle0, pauli1, angle1, - // cx_config); + circ, SpSymPauliTensor(pauli0) * SpSymPauliTensor({}, angle0), + SpSymPauliTensor(pauli1) * SpSymPauliTensor({}, angle1), cx_config); } } Circuit cliff_circuit = unitary_rev_tableau_to_circuit(pg.cliff_); @@ -129,41 +129,37 @@ Circuit pauli_graph_to_pauli_exp_box_circuit_sets( while (it != vertices.end()) { const PauliGadgetProperties &pgp = pg.graph_[*it]; QubitOperator gadget_map; - gadget_map[pgp.tensor_] = pgp.angle_; + insert_into_gadget_map(gadget_map, pgp); ++it; while (it != vertices.end()) { const PauliGadgetProperties &pauli_gadget = pg.graph_[*it]; - QubitOperator::iterator pgs_iter = gadget_map.find(pauli_gadget.tensor_); - if (pgs_iter != gadget_map.end()) { - insert_into_gadget_map(gadget_map, pauli_gadget); - } else { - bool commutes_with_all = true; - for (const std::pair &pv : gadget_map) { - if (!pauli_gadget.tensor_.commutes_with(pv.first)) { - commutes_with_all = false; - break; - } + bool commutes_with_all = true; + for (const std::pair &pv : gadget_map) { + if (!pauli_gadget.tensor_.commutes_with(pv.first)) { + commutes_with_all = false; + break; } - if (!commutes_with_all) break; - insert_into_gadget_map(gadget_map, pauli_gadget); } + if (!commutes_with_all) break; + insert_into_gadget_map(gadget_map, pauli_gadget); ++it; } if (gadget_map.size() == 1) { - const std::pair &pgp0 = *gadget_map.begin(); + const std::pair &pgp0 = *gadget_map.begin(); append_single_pauli_gadget_as_pauli_exp_box( - circ, pgp0.first, pgp0.second, cx_config); + circ, SpSymPauliTensor(pgp0.first.string, pgp0.second), cx_config); } else if (gadget_map.size() == 2) { - const std::pair &pgp0 = *gadget_map.begin(); - const std::pair &pgp1 = + const std::pair &pgp0 = *gadget_map.begin(); + const std::pair &pgp1 = *(++gadget_map.begin()); append_pauli_gadget_pair_as_box( - circ, pgp0.first, pgp0.second, pgp1.first, pgp1.second, cx_config); + circ, SpSymPauliTensor(pgp0.first.string, pgp0.second), + SpSymPauliTensor(pgp1.first.string, pgp1.second), cx_config); } else { - std::list> gadgets; - for (const std::pair &qps_pair : - gadget_map) { - gadgets.push_back(qps_pair); + std::list gadgets; + for (const std::pair &qps_pair : gadget_map) { + gadgets.push_back( + SpSymPauliTensor(qps_pair.first.string, qps_pair.second)); } append_commuting_pauli_gadget_set_as_box(circ, gadgets, cx_config); } diff --git a/tket/src/Diagonalisation/DiagUtils.cpp b/tket/src/Diagonalisation/DiagUtils.cpp index 3177d34675..70d5a5962d 100644 --- a/tket/src/Diagonalisation/DiagUtils.cpp +++ b/tket/src/Diagonalisation/DiagUtils.cpp @@ -18,14 +18,14 @@ namespace tket { void insert_into_gadget_map( QubitOperator &gadget_map, const PauliGadgetProperties &pgp) { - QubitOperator::iterator iter = gadget_map.find(pgp.tensor_); + SpSymPauliTensor gadget(pgp.tensor_); + gadget.coeff *= pgp.angle_; + SpPauliString ps(gadget); + QubitOperator::iterator iter = gadget_map.find(ps); if (iter == gadget_map.end()) - gadget_map[pgp.tensor_] = pgp.angle_; + gadget_map[ps] = gadget.coeff; else { - QubitPauliTensor string_to_insert = pgp.tensor_ * iter->first; - Expr ang_to_insert = pgp.angle_ * iter->second; - gadget_map.erase(iter); - gadget_map[string_to_insert] = ang_to_insert; + iter->second += gadget.coeff; } } diff --git a/tket/src/Diagonalisation/Diagonalisation.cpp b/tket/src/Diagonalisation/Diagonalisation.cpp index a2e30ffb33..354f522d3d 100644 --- a/tket/src/Diagonalisation/Diagonalisation.cpp +++ b/tket/src/Diagonalisation/Diagonalisation.cpp @@ -23,8 +23,8 @@ namespace tket { void check_easy_diagonalise( - std::list> &gadgets, - std::set &qubits, Circuit &circ) { + std::list &gadgets, std::set &qubits, + Circuit &circ) { Conjugations conjugations; std::set::iterator qb_iter = qubits.begin(); for (std::set::iterator next = qb_iter; qb_iter != qubits.end(); @@ -32,11 +32,8 @@ void check_easy_diagonalise( ++next; Pauli p1 = Pauli::I; bool remove_qb = true; - for (const std::pair &pgp : gadgets) { - std::map::const_iterator map_iter = - pgp.first.string.map.find(*qb_iter); - if (map_iter == pgp.first.string.map.end()) continue; - Pauli p2 = map_iter->second; + for (const SpSymPauliTensor &gadget : gadgets) { + Pauli p2 = gadget.get(*qb_iter); if (p2 == Pauli::I) continue; if (p1 == Pauli::I) { p1 = p2; @@ -60,21 +57,20 @@ void check_easy_diagonalise( case Pauli::Z: break; default: - throw UnknownPauli(); + throw std::logic_error( + "Unknown Pauli encountered in checking diagonalisation"); } qubits.erase(qb_iter); } } - for (std::list>::iterator iter = - gadgets.begin(); - iter != gadgets.end(); ++iter) { - apply_conjugations(iter->first, conjugations); + for (SpSymPauliTensor &gadget : gadgets) { + apply_conjugations(gadget, conjugations); } } std::optional> check_pair_compatibility( const Qubit &qb1, const Qubit &qb2, - const std::list> &gadgets) { + const std::list &gadgets) { if (qb1 == qb2) return std::nullopt; /* Do exhaustive search for a Pauli A and Pauli B that @@ -83,20 +79,9 @@ std::optional> check_pair_compatibility( for (Pauli pauli1 : paulis) { for (Pauli pauli2 : paulis) { bool found_pair = true; - for (const std::pair &pgp : gadgets) { - Pauli inner_p_1; - QubitPauliMap::const_iterator iter1 = pgp.first.string.map.find(qb1); - if (iter1 == pgp.first.string.map.end()) - inner_p_1 = Pauli::I; - else - inner_p_1 = iter1->second; - - Pauli inner_p_2; - QubitPauliMap::const_iterator iter2 = pgp.first.string.map.find(qb2); - if (iter2 == pgp.first.string.map.end()) - inner_p_2 = Pauli::I; - else - inner_p_2 = iter2->second; + for (const SpSymPauliTensor &gadget : gadgets) { + Pauli inner_p_1 = gadget.get(qb1); + Pauli inner_p_2 = gadget.get(qb2); if (inner_p_1 == Pauli::I || inner_p_1 == pauli1) { if (!(inner_p_2 == Pauli::I || inner_p_2 == pauli2)) { @@ -120,23 +105,18 @@ std::optional> check_pair_compatibility( } void greedy_diagonalise( - const std::list> &gadgets, - std::set &qubits, Conjugations &conjugations, Circuit &circ, - CXConfigType cx_config) { + const std::list &gadgets, std::set &qubits, + Conjugations &conjugations, Circuit &circ, CXConfigType cx_config) { unsigned total_counter = UINT_MAX; QubitPauliMap to_diag; - for (std::list>::const_iterator pgp_iter = - gadgets.begin(); - pgp_iter != gadgets.end(); ++pgp_iter) { + for (const SpSymPauliTensor &gadget : gadgets) { unsigned support_counter = 0; QubitPauliMap to_diag_candidates; for (const Qubit &qb : qubits) { - QubitPauliMap::const_iterator pauli_iter = - pgp_iter->first.string.map.find(qb); - if (pauli_iter == pgp_iter->first.string.map.end()) continue; - if (pauli_iter->second != Pauli::I) { + Pauli p = gadget.get(qb); + if (p != Pauli::I) { ++support_counter; - to_diag_candidates.insert(*pauli_iter); + to_diag_candidates.insert({qb, p}); } } if (support_counter < total_counter && support_counter > 1) { @@ -147,11 +127,9 @@ void greedy_diagonalise( if (to_diag.empty()) { throw std::logic_error("Brute Force Diagonalise can't find a candidate!"); } - for (QubitPauliMap::iterator qb_p_iter = to_diag.begin(); - qb_p_iter != to_diag.end(); ++qb_p_iter) { - const Qubit &qb = qb_p_iter->first; - Pauli p = qb_p_iter->second; - switch (p) { + for (const std::pair &qp : to_diag) { + const Qubit &qb = qp.first; + switch (qp.second) { case Pauli::X: { conjugations.push_back({OpType::H, {qb}}); circ.add_op(OpType::H, {qb}); @@ -167,7 +145,7 @@ void greedy_diagonalise( } case Pauli::I: default: - throw UnknownPauli(); + throw std::logic_error("Unknown Pauli in greedy diagonalisation."); } } qubit_vector_t diag_qubits; @@ -238,15 +216,15 @@ void greedy_diagonalise( break; } default: - throw UnknownCXConfigType(); + throw std::logic_error("Unknown CXConfigType in greedy diagonalisation."); } qubits.erase(first_qb); } /* Diagonalise a set of Pauli Gadgets simultaneously using Cliffords*/ Circuit mutual_diagonalise( - std::list> &gadgets, - std::set qubits, CXConfigType cx_config) { + std::list &gadgets, std::set qubits, + CXConfigType cx_config) { Circuit cliff_circ; for (const Qubit &qb : qubits) { cliff_circ.add_qubit(qb); @@ -292,7 +270,7 @@ Circuit mutual_diagonalise( } case Pauli::I: default: - throw UnknownPauli(); + throw std::logic_error("Unknown Pauli in mutual diagonalisation."); } switch (p2) { case Pauli::X: { @@ -310,7 +288,7 @@ Circuit mutual_diagonalise( } case Pauli::I: default: - throw UnknownPauli(); + throw std::logic_error("Unknown Pauli in mutual diagonalisation."); } conjugations.push_back({OpType::CX, {qb_a, qb_b}}); cliff_circ.add_op(OpType::CX, {qb_a, qb_b}); @@ -321,10 +299,8 @@ Circuit mutual_diagonalise( if (!found_match) { greedy_diagonalise(gadgets, qubits, conjugations, cliff_circ, cx_config); } - for (std::list>::iterator iter = - gadgets.begin(); - iter != gadgets.end(); ++iter) { - apply_conjugations(iter->first, conjugations); + for (SpSymPauliTensor &gadget : gadgets) { + apply_conjugations(gadget, conjugations); } // we may have made some easy-to-remove qubits check_easy_diagonalise(gadgets, qubits, cliff_circ); @@ -333,7 +309,8 @@ Circuit mutual_diagonalise( } void apply_conjugations( - QubitPauliTensor &qps, const Conjugations &conjugations) { + SpSymPauliTensor &qps, const Conjugations &conjugations) { + SpPauliStabiliser stab(qps.string); for (const auto &optype_qubit_pair : conjugations) { OpType ot = optype_qubit_pair.first; const qubit_vector_t &qbs = optype_qubit_pair.second; @@ -348,18 +325,21 @@ void apply_conjugations( case OpType::Vdg: case OpType::X: case OpType::Z: - conjugate_PauliTensor(qps, ot, qbs[0]); + conjugate_PauliTensor(stab, ot, qbs[0]); break; case OpType::CX: - conjugate_PauliTensor(qps, ot, qbs[0], qbs[1]); + conjugate_PauliTensor(stab, ot, qbs[0], qbs[1]); break; case OpType::XXPhase3: - conjugate_PauliTensor(qps, ot, qbs[0], qbs[1], qbs[2]); + conjugate_PauliTensor(stab, ot, qbs[0], qbs[1], qbs[2]); break; default: - throw UnknownOpType(); + throw std::logic_error( + "Unknown OpType received when applying conjugations."); } } + qps.string = stab.string; + qps.coeff *= cast_coeff(stab.coeff); } } // namespace tket diff --git a/tket/src/Diagonalisation/PauliPartition.cpp b/tket/src/Diagonalisation/PauliPartition.cpp index 39b7f19675..b784284029 100644 --- a/tket/src/Diagonalisation/PauliPartition.cpp +++ b/tket/src/Diagonalisation/PauliPartition.cpp @@ -24,9 +24,9 @@ namespace tket { PauliPartitionerGraph::PauliPartitionerGraph( - const std::list& strings, PauliPartitionStrat strat) { + const std::list& strings, PauliPartitionStrat strat) { pac_graph = {}; - for (const QubitPauliString& tensor : strings) { + for (const SpPauliString& tensor : strings) { PauliACVertex new_vert = boost::add_vertex(tensor, pac_graph); BGL_FORALL_VERTICES(v, pac_graph, PauliACGraph) { if (v != new_vert) { @@ -64,13 +64,13 @@ class AbstractGraphData { // Return the ID of the string (and also assign a new ID if the string // was not seen before); the eventual IDs will form an interval {0,1,2,...,n}. - std::size_t get_vertex_id(const QubitPauliString& pauli_string); + std::size_t get_vertex_id(const SpPauliString& pauli_string); typedef // KEY: the Pauli string present in a vertex // VALUE: an integer label for that vertex. // The labels will be a contiguous interval {0,1,2,...,m}. - std::map + std::map VertexMap; const graphs::AdjacencyData& get_adjacency_data() const; @@ -83,7 +83,7 @@ class AbstractGraphData { }; std::size_t AbstractGraphData::get_vertex_id( - const QubitPauliString& pauli_string) { + const SpPauliString& pauli_string) { const auto citer = m_vertex_map.find(pauli_string); if (citer != m_vertex_map.cend()) { return citer->second; @@ -125,7 +125,7 @@ AbstractGraphData::AbstractGraphData(const PauliACGraph& pac_graph) } } -static std::map> +static std::map> get_partitioned_paulis_for_exhaustive_method(const PauliACGraph& pac_graph) { const AbstractGraphData data(pac_graph); const graphs::GraphColouringResult colouring = @@ -133,10 +133,10 @@ get_partitioned_paulis_for_exhaustive_method(const PauliACGraph& pac_graph) { TKET_ASSERT(data.get_vertex_map().size() == colouring.colours.size()); - std::map> colour_map; + std::map> colour_map; for (const auto& entry : data.get_vertex_map()) { - const QubitPauliString& vertex = entry.first; + const SpPauliString& vertex = entry.first; const std::size_t id = entry.second; TKET_ASSERT(id < colouring.colours.size()); @@ -153,7 +153,7 @@ get_partitioned_paulis_for_exhaustive_method(const PauliACGraph& pac_graph) { return colour_map; } -static std::map> +static std::map> get_partitioned_paulis_for_largest_first_method(const PauliACGraph& pac_graph) { std::vector order_vec(boost::num_vertices(pac_graph)); std::iota(order_vec.begin(), order_vec.end(), 0); @@ -175,7 +175,7 @@ get_partitioned_paulis_for_largest_first_method(const PauliACGraph& pac_graph) { order_vec.begin(), boost::identity_property_map()), colour_prop_map); - std::map> colour_map; + std::map> colour_map; BGL_FORALL_VERTICES(v, pac_graph, PauliACGraph) { unsigned v_colour = colour_prop_map[v]; colour_map[v_colour].push_back(pac_graph[v]); @@ -183,7 +183,7 @@ get_partitioned_paulis_for_largest_first_method(const PauliACGraph& pac_graph) { return colour_map; } -std::map> +std::map> PauliPartitionerGraph::partition_paulis(GraphColourMethod method) const { switch (method) { case GraphColourMethod::LargestFirst: @@ -202,23 +202,20 @@ PauliPartitionerGraph::partition_paulis(GraphColourMethod method) const { } } -static std::list> +static std::list> get_term_sequence_for_lazy_colouring_method( - const std::list& strings, PauliPartitionStrat strat) { - std::list> terms; - for (const QubitPauliString& qpt : strings) { + const std::list& strings, PauliPartitionStrat strat) { + std::list> terms; + for (const SpPauliString& qpt : strings) { if (terms.empty()) { terms.push_back({qpt}); continue; } bool found_bin = false; - for (std::list>::iterator term_iter = - terms.begin(); - term_iter != terms.end(); ++term_iter) { - const std::list& qpt_list = *term_iter; + for (std::list& qpt_list : terms) { bool viable_bin = true; - for (const QubitPauliString& qpt2 : qpt_list) { + for (const SpPauliString& qpt2 : qpt_list) { switch (strat) { case (PauliPartitionStrat::NonConflictingSets): { bool conflict = !qpt.conflicting_qubits(qpt2).empty(); @@ -237,7 +234,7 @@ get_term_sequence_for_lazy_colouring_method( if (viable_bin == false) break; } if (viable_bin) { - term_iter->push_back(qpt); + qpt_list.push_back(qpt); found_bin = true; break; } @@ -250,24 +247,24 @@ get_term_sequence_for_lazy_colouring_method( return terms; } -static std::list> +static std::list> get_term_sequence_with_constructed_dependency_graph( - const std::list& strings, PauliPartitionStrat strat, + const std::list& strings, PauliPartitionStrat strat, GraphColourMethod method) { - std::list> terms; + std::list> terms; PauliPartitionerGraph pp(strings, strat); - std::map> colour_map = + std::map> colour_map = pp.partition_paulis(method); - for (const std::pair>& - colour_pair : colour_map) { + for (const std::pair>& colour_pair : + colour_map) { terms.push_back(colour_pair.second); } return terms; } -std::list> term_sequence( - const std::list& strings, PauliPartitionStrat strat, +std::list> term_sequence( + const std::list& strings, PauliPartitionStrat strat, GraphColourMethod method) { switch (method) { case GraphColourMethod::Lazy: diff --git a/tket/src/Gate/Gate.cpp b/tket/src/Gate/Gate.cpp index af81603356..94c316d8cf 100644 --- a/tket/src/Gate/Gate.cpp +++ b/tket/src/Gate/Gate.cpp @@ -28,7 +28,7 @@ #include "tket/OpType/OpTypeInfo.hpp" #include "tket/Ops/Op.hpp" #include "tket/Utils/Expression.hpp" -#include "tket/Utils/PauliStrings.hpp" +#include "tket/Utils/PauliTensor.hpp" namespace tket { using std::stringstream; diff --git a/tket/src/MeasurementSetup/MeasurementReduction.cpp b/tket/src/MeasurementSetup/MeasurementReduction.cpp index e37e3a55f3..b0a4a09f07 100644 --- a/tket/src/MeasurementSetup/MeasurementReduction.cpp +++ b/tket/src/MeasurementSetup/MeasurementReduction.cpp @@ -17,11 +17,11 @@ namespace tket { MeasurementSetup measurement_reduction( - const std::list& strings, PauliPartitionStrat strat, + const std::list& strings, PauliPartitionStrat strat, GraphColourMethod method, CXConfigType cx_config) { std::set qubits; - for (const QubitPauliString& qpt : strings) { - for (const std::pair& qb_p : qpt.map) + for (const SpPauliString& qpt : strings) { + for (const std::pair& qb_p : qpt.string) qubits.insert(qb_p.first); } @@ -32,15 +32,14 @@ MeasurementSetup measurement_reduction( ++u; } - std::list> all_terms = + std::list> all_terms = term_sequence(strings, strat, method); MeasurementSetup ms; unsigned i = 0; - for (const std::list& terms : all_terms) { - std::list> gadgets; - for (const QubitPauliString& string : terms) { - QubitPauliTensor qps(string); - gadgets.push_back({qps, 1.}); + for (const std::list& terms : all_terms) { + std::list gadgets; + for (const SpPauliString& string : terms) { + gadgets.push_back(string); } std::set mutable_qb_set(qubits); @@ -53,18 +52,15 @@ MeasurementSetup measurement_reduction( } ms.add_measurement_circuit(cliff_circ); - std::list>::const_iterator gadgets_iter = - gadgets.begin(); - for (const QubitPauliString& string : terms) { - const std::pair& new_gadget = *gadgets_iter; + std::list::const_iterator gadgets_iter = gadgets.begin(); + for (const SpPauliString& string : terms) { + SpPauliStabiliser stab(*gadgets_iter); // Force coeff to be real std::vector bits; - for (const std::pair& qp_pair : - new_gadget.first.string.map) { + for (const std::pair& qp_pair : stab.string) { if (qp_pair.second == Pauli::Z) bits.push_back(qb_location_map.at(qp_pair.first)); } - bool invert = (std::abs(new_gadget.first.coeff + Complex(1)) < EPS); - ms.add_result_for_term(string, {i, bits, invert}); + ms.add_result_for_term(string, {i, bits, stab.is_real_negative()}); ++gadgets_iter; } ++i; diff --git a/tket/src/MeasurementSetup/MeasurementSetup.cpp b/tket/src/MeasurementSetup/MeasurementSetup.cpp index 8b556166a5..3b8aead2dd 100644 --- a/tket/src/MeasurementSetup/MeasurementSetup.cpp +++ b/tket/src/MeasurementSetup/MeasurementSetup.cpp @@ -40,17 +40,12 @@ void MeasurementSetup::add_measurement_circuit(const Circuit &circ) { } void MeasurementSetup::add_result_for_term( - const QubitPauliString &term, const MeasurementBitMap &result) { + const SpPauliString &term, const MeasurementBitMap &result) { result_map[term].push_back(result); } -void MeasurementSetup::add_result_for_term( - const QubitPauliTensor &term, const MeasurementBitMap &result) { - add_result_for_term(term.string, result); -} - bool MeasurementSetup::verify() const { - std::map, QubitPauliTensor> pauli_map; + std::map, SpPauliStabiliser> pauli_map; // Identify Paulis measured onto each bit for (unsigned circ_id = 0; circ_id < measurement_circs.size(); ++circ_id) { Circuit circ = measurement_circs[circ_id]; @@ -68,15 +63,15 @@ bool MeasurementSetup::verify() const { pauli_map.insert({{circ_id, readout[qb]}, tab.get_zrow(qb)}); } } - for (const std::pair> + for (const std::pair> &term : result_map) { for (const MeasurementBitMap &bits : term.second) { - QubitPauliTensor total; + SpPauliStabiliser total; for (unsigned bit : bits.bits) { total = total * pauli_map[{bits.circ_index, bit}]; } - if (bits.invert) total.coeff *= -1.; - QubitPauliTensor term_tensor(term.first); + if (bits.invert) total.coeff = (total.coeff + 2) % 4; + SpPauliStabiliser term_tensor(term.first); if (total != term_tensor) { std::stringstream out; out << "Invalid MeasurementSetup: expecting to measure " @@ -94,7 +89,7 @@ std::string MeasurementSetup::to_str() const { ss << "Circuits: "; ss << measurement_circs.size(); ss << "\n"; - for (const std::pair> + for (const std::pair> &tensor_map : result_map) { ss << "|| "; ss << tensor_map.first.to_str(); @@ -123,10 +118,10 @@ void from_json( void to_json(nlohmann::json &j, const MeasurementSetup &setup) { std::vector>> + SpPauliString, std::vector>> map_list; for (const std::pair< - const QubitPauliString, + const SpPauliString, std::vector> &tensor_map : setup.get_result_map()) { map_list.push_back(tensor_map); @@ -135,7 +130,17 @@ void to_json(nlohmann::json &j, const MeasurementSetup &setup) { std::sort(map_list.begin(), map_list.end(), [](auto pair1, auto pair2) { return pair1.first < pair2.first; }); - j["result_map"] = map_list; + std::vector>> + map_encoding; + for (const std::pair< + SpPauliString, std::vector> + &tensor_map : map_list) { + map_encoding.push_back({tensor_map.first.string, tensor_map.second}); + } + // Convert SpPauliString to QubitPauliMap for backwards compatibility with + // before templated PauliTensor + j["result_map"] = map_encoding; j["circs"] = setup.get_circs(); } @@ -147,7 +152,7 @@ void from_json(const nlohmann::json &j, MeasurementSetup &setup) { for (auto second_it = it->at(1).begin(); second_it != it->at(1).end(); ++second_it) { setup.add_result_for_term( - it->at(0).get(), + SpPauliString(it->at(0).get()), second_it->get()); } } diff --git a/tket/src/PauliGraph/ConjugatePauliFunctions.cpp b/tket/src/PauliGraph/ConjugatePauliFunctions.cpp index 7515545d59..31a2b26852 100644 --- a/tket/src/PauliGraph/ConjugatePauliFunctions.cpp +++ b/tket/src/PauliGraph/ConjugatePauliFunctions.cpp @@ -99,20 +99,20 @@ std::pair conjugate_Pauli(OpType op, Pauli p, bool reverse) { } void conjugate_PauliTensor( - QubitPauliTensor& qpt, OpType op, const Qubit& q, bool reverse) { - QubitPauliMap::iterator it = qpt.string.map.find(q); - if (it == qpt.string.map.end()) { + SpPauliStabiliser& qpt, OpType op, const Qubit& q, bool reverse) { + QubitPauliMap::iterator it = qpt.string.find(q); + if (it == qpt.string.end()) { return; } std::pair conj = conjugate_Pauli(op, it->second, reverse); it->second = conj.first; if (conj.second) { - qpt.coeff *= -1; + qpt.coeff = (qpt.coeff + 2) % 4; } } void conjugate_PauliTensor( - QubitPauliTensor& qpt, OpType op, const Qubit& q0, const Qubit& q1) { + SpPauliStabiliser& qpt, OpType op, const Qubit& q0, const Qubit& q1) { static const std::map, std::tuple> cx_conj_lut{ {{Pauli::I, Pauli::I}, {Pauli::I, Pauli::I, false}}, @@ -135,29 +135,18 @@ void conjugate_PauliTensor( if (op != OpType::CX) { throw BadOpType("Conjugations of Pauli strings only defined for CXs", op); } - QubitPauliMap::iterator it0 = qpt.string.map.find(q0); - QubitPauliMap::iterator it1 = qpt.string.map.find(q1); - Pauli p0, p1; - if (it0 == qpt.string.map.end()) { - p0 = Pauli::I; - } else { - p0 = it0->second; - } - if (it1 == qpt.string.map.end()) { - p1 = Pauli::I; - } else { - p1 = it1->second; - } + Pauli p0 = qpt.get(q0); + Pauli p1 = qpt.get(q1); std::tuple conj = cx_conj_lut.at({p0, p1}); - qpt.string.map[q0] = std::get<0>(conj); - qpt.string.map[q1] = std::get<1>(conj); + qpt.set(q0, std::get<0>(conj)); + qpt.set(q1, std::get<1>(conj)); if (std::get<2>(conj)) { - qpt.coeff *= -1; + qpt.coeff = (qpt.coeff + 2) % 4; } } void conjugate_PauliTensor( - QubitPauliTensor& qpt, OpType op, const Qubit& q0, const Qubit& q1, + SpPauliStabiliser& qpt, OpType op, const Qubit& q0, const Qubit& q1, const Qubit& q2) { /* XXPhase3 gates used for conjugations always implicitly use angle π/2 * i.e. XXPhase3(1/2). Note that up to phase the 3-qb gate is self-inverse: @@ -179,20 +168,12 @@ void conjugate_PauliTensor( throw BadOpType( "3qb-Conjugations of Pauli strings only defined for XXPhase3", op); } - Conjugations equiv = {{OpType::H, {q1}}, {OpType::CX, {q1, q2}}, - {OpType::CX, {q1, q0}}, {OpType::H, {q0}}, - {OpType::H, {q1}}, {OpType::CX, {q0, q2}}, - {OpType::H, {q0}}, {OpType::X, {q0}}, - {OpType::X, {q1}}, {OpType::X, {q2}}}; - - for (auto [op, qbs] : equiv) { - if (qbs.size() == 1) { - conjugate_PauliTensor(qpt, op, qbs[0]); - } else { - TKET_ASSERT(qbs.size() == 2); - conjugate_PauliTensor(qpt, op, qbs[0], qbs[1]); - } - } + SpPauliStabiliser xxi_i({{q0, Pauli::X}, {q1, Pauli::X}}, 1); + SpPauliStabiliser xix_i({{q0, Pauli::X}, {q2, Pauli::X}}, 1); + SpPauliStabiliser ixx_i({{q1, Pauli::X}, {q2, Pauli::X}}, 1); + if (!xxi_i.commutes_with(qpt)) qpt = qpt * xxi_i; + if (!xix_i.commutes_with(qpt)) qpt = qpt * xix_i; + if (!ixx_i.commutes_with(qpt)) qpt = qpt * ixx_i; } } // namespace tket diff --git a/tket/src/PauliGraph/PauliGraph.cpp b/tket/src/PauliGraph/PauliGraph.cpp index 1ec7de2a87..7e7f7dc366 100644 --- a/tket/src/PauliGraph/PauliGraph.cpp +++ b/tket/src/PauliGraph/PauliGraph.cpp @@ -19,13 +19,13 @@ #include "tket/Gate/Gate.hpp" #include "tket/OpType/OpType.hpp" #include "tket/Utils/GraphHeaders.hpp" -#include "tket/Utils/PauliStrings.hpp" +#include "tket/Utils/PauliTensor.hpp" namespace tket { bool operator<( const PauliGadgetProperties &pgp1, const PauliGadgetProperties &pgp2) { - return (pgp1.tensor_.string < pgp2.tensor_.string); + return (SpPauliString)pgp1.tensor_ < (SpPauliString)pgp2.tensor_; } PauliGraph::PauliGraph(unsigned n) : cliff_(n) {} @@ -119,7 +119,7 @@ void PauliGraph::apply_gate_at_end( break; } case OpType::Rz: { - QubitPauliTensor pauli = cliff_.get_zrow(qbs.at(0)); + SpPauliStabiliser pauli = cliff_.get_zrow(qbs.at(0)); Expr angle = gate.get_params().at(0); std::optional cliff_angle = equiv_Clifford(angle); if (cliff_angle) { @@ -131,7 +131,7 @@ void PauliGraph::apply_gate_at_end( break; } case OpType::Rx: { - QubitPauliTensor pauli = cliff_.get_xrow(qbs.at(0)); + SpPauliStabiliser pauli = cliff_.get_xrow(qbs.at(0)); Expr angle = gate.get_params().at(0); std::optional cliff_angle = equiv_Clifford(angle); if (cliff_angle) { @@ -154,8 +154,8 @@ void PauliGraph::apply_gate_at_end( cliff_.apply_gate_at_end(OpType::Vdg, qbs); } } else { - QubitPauliTensor ypauli = - cliff_.get_row_product(QubitPauliTensor(qbs.at(0), Pauli::Y)); + SpPauliStabiliser ypauli = + cliff_.get_row_product(SpPauliStabiliser(qbs.at(0), Pauli::Y)); apply_pauli_gadget_at_end(ypauli, angle); } break; @@ -163,8 +163,8 @@ void PauliGraph::apply_gate_at_end( case OpType::PhasedX: { Expr alpha = gate.get_params().at(0); Expr beta = gate.get_params().at(1); - QubitPauliTensor zpauli = cliff_.get_zrow(qbs.at(0)); - QubitPauliTensor xpauli = cliff_.get_xrow(qbs.at(0)); + SpPauliStabiliser zpauli = cliff_.get_zrow(qbs.at(0)); + SpPauliStabiliser xpauli = cliff_.get_xrow(qbs.at(0)); std::optional cliff_alpha = equiv_Clifford(alpha); std::optional cliff_beta = equiv_Clifford(beta); // Rz(-b) @@ -194,12 +194,12 @@ void PauliGraph::apply_gate_at_end( break; } case OpType::T: { - QubitPauliTensor pauli = cliff_.get_zrow(qbs.at(0)); + SpPauliStabiliser pauli = cliff_.get_zrow(qbs.at(0)); apply_pauli_gadget_at_end(pauli, 0.25); break; } case OpType::Tdg: { - QubitPauliTensor pauli = cliff_.get_zrow(qbs.at(0)); + SpPauliStabiliser pauli = cliff_.get_zrow(qbs.at(0)); apply_pauli_gadget_at_end(pauli, -0.25); break; } @@ -220,21 +220,15 @@ void PauliGraph::apply_gate_at_end( std::optional cliff_angle = equiv_Clifford(angle); if (cliff_angle) { if (cliff_angle.value() != 0) { - for (unsigned i = 1; i < qbs.size(); i++) { - cliff_.apply_gate_at_end(OpType::CX, {qbs.at(i - 1), qbs.at(i)}); - } - for (unsigned i = 0; i < cliff_angle.value(); i++) { - cliff_.apply_gate_at_end(OpType::S, {qbs.back()}); - } - for (unsigned i = qbs.size() - 1; i > 0; i--) { - cliff_.apply_gate_at_end(OpType::CX, {qbs.at(i - 1), qbs.at(i)}); - } + QubitPauliMap qpm; + for (const Qubit &q : qbs) qpm.insert({q, Pauli::Z}); + cliff_.apply_pauli_at_end(SpPauliStabiliser(qpm), *cliff_angle); } } else { - QubitPauliTensor pauli = - cliff_.get_row_product(QubitPauliTensor(QubitPauliString( - std::list{qbs.begin(), qbs.end()}, - std::list{qbs.size(), Pauli::Z}))); + QubitPauliMap qpm; + for (const Qubit &q : qbs) qpm.insert({q, Pauli::Z}); + SpPauliStabiliser pauli = + cliff_.get_row_product(SpPauliStabiliser(qpm)); apply_pauli_gadget_at_end(pauli, angle); } break; @@ -244,15 +238,13 @@ void PauliGraph::apply_gate_at_end( std::optional cliff_angle = equiv_Clifford(angle); if (cliff_angle) { if (cliff_angle.value() != 0) { - cliff_.apply_gate_at_end(OpType::CX, {qbs.at(1), qbs.at(0)}); - for (unsigned i = 0; i < cliff_angle.value(); i++) { - cliff_.apply_gate_at_end(OpType::V, {qbs.back()}); - } - cliff_.apply_gate_at_end(OpType::CX, {qbs.at(1), qbs.at(0)}); + cliff_.apply_pauli_at_end( + SpPauliStabiliser({{qbs.at(0), Pauli::X}, {qbs.at(1), Pauli::X}}), + *cliff_angle); } } else { - QubitPauliTensor pauli = cliff_.get_row_product(QubitPauliTensor( - QubitPauliString({qbs.at(0), qbs.at(1)}, {Pauli::X, Pauli::X}))); + SpPauliStabiliser pauli = cliff_.get_row_product( + SpPauliStabiliser({{qbs.at(0), Pauli::X}, {qbs.at(1), Pauli::X}})); apply_pauli_gadget_at_end(pauli, angle); } break; @@ -262,21 +254,13 @@ void PauliGraph::apply_gate_at_end( std::optional cliff_angle = equiv_Clifford(angle); if (cliff_angle) { if (cliff_angle.value() != 0) { - const Qubit &arg0 = qbs.at(0); - const Qubit &arg1 = qbs.at(1); - cliff_.apply_gate_at_end(OpType::S, {arg0}); - cliff_.apply_gate_at_end(OpType::S, {arg1}); - cliff_.apply_gate_at_end(OpType::CX, {arg1, arg0}); - for (unsigned i = 0; i < cliff_angle.value(); i++) { - cliff_.apply_gate_at_end(OpType::V, {arg1}); - } - cliff_.apply_gate_at_end(OpType::CX, {arg1, arg0}); - cliff_.apply_gate_at_end(OpType::Sdg, {arg0}); - cliff_.apply_gate_at_end(OpType::Sdg, {arg1}); + cliff_.apply_pauli_at_end( + SpPauliStabiliser({{qbs.at(0), Pauli::Y}, {qbs.at(1), Pauli::Y}}), + *cliff_angle); } } else { - QubitPauliTensor pauli = cliff_.get_row_product(QubitPauliTensor( - QubitPauliString({qbs.at(0), qbs.at(1)}, {Pauli::Y, Pauli::Y}))); + SpPauliStabiliser pauli = cliff_.get_row_product( + SpPauliStabiliser({{qbs.at(0), Pauli::Y}, {qbs.at(1), Pauli::Y}})); apply_pauli_gadget_at_end(pauli, angle); } break; @@ -288,7 +272,7 @@ void PauliGraph::apply_gate_at_end( } void PauliGraph::apply_pauli_gadget_at_end( - const QubitPauliTensor &pauli, const Expr &angle) { + const SpPauliStabiliser &pauli, const Expr &angle) { PauliVertSet to_search = end_line_; PauliVertSet commuted; PauliVert new_vert = boost::add_vertex(graph_); @@ -309,11 +293,11 @@ void PauliGraph::apply_pauli_gadget_at_end( if (!ready) continue; // Check if we can commute past it - QubitPauliTensor compare_pauli = graph_[to_compare].tensor_; + SpPauliStabiliser compare_pauli = graph_[to_compare].tensor_; if (pauli.commutes_with(compare_pauli)) { if (pauli.string == compare_pauli.string) { // Identical strings - we can merge vertices - if (pauli.coeff == compare_pauli.coeff) { + if (pauli.is_real_negative() == compare_pauli.is_real_negative()) { graph_[to_compare].angle_ += angle; } else { graph_[to_compare].angle_ -= angle; diff --git a/tket/src/Transformations/PauliOptimisation.cpp b/tket/src/Transformations/PauliOptimisation.cpp index 7d4c0f8624..003c5e228c 100644 --- a/tket/src/Transformations/PauliOptimisation.cpp +++ b/tket/src/Transformations/PauliOptimisation.cpp @@ -59,7 +59,7 @@ Transform pairwise_pauli_gadgets(CXConfigType cx_config) { // We effectively commute non-Clifford rotations to the front of the circuit // This gives a sequence of just Pauli gadgets (gadget_circ), followed by // all of the Clifford operations (clifford_circ) - std::vector> pauli_gadgets; + std::vector pauli_gadgets; // rx_pauli[i] specifies which Pauli gadget would be built by applying an Rx // rotation on qubit i and then pushing it through the Cliffords to the // front of the circuit. Likewise for rz_pauli with Rz rotations. Clifford @@ -67,13 +67,13 @@ Transform pairwise_pauli_gadgets(CXConfigType cx_config) { // Pauli gadgets accordingly Circuit gadget_circ; Circuit clifford_circ; - std::map rx_pauli; - std::map rz_pauli; + std::map rx_pauli; + std::map rz_pauli; for (const Qubit &qb : circ.all_qubits()) { gadget_circ.add_qubit(qb); clifford_circ.add_qubit(qb); - rx_pauli.insert({qb, QubitPauliTensor(qb, Pauli::X)}); - rz_pauli.insert({qb, QubitPauliTensor(qb, Pauli::Z)}); + rx_pauli.insert({qb, SpPauliStabiliser(qb, Pauli::X)}); + rz_pauli.insert({qb, SpPauliStabiliser(qb, Pauli::Z)}); } for (const Bit &cb : circ.all_bits()) { gadget_circ.add_bit(cb); @@ -88,32 +88,32 @@ Transform pairwise_pauli_gadgets(CXConfigType cx_config) { // Update rx_pauli and rz_pauli case OpType::S: { Qubit q(args[0]); - rx_pauli[q] = i_ * rz_pauli[q] * rx_pauli[q]; + rx_pauli[q] = SpPauliStabiliser({}, 1) * rz_pauli[q] * rx_pauli[q]; break; } case OpType::V: { Qubit q(args[0]); - rz_pauli[q] = i_ * rx_pauli[q] * rz_pauli[q]; + rz_pauli[q] = SpPauliStabiliser({}, 1) * rx_pauli[q] * rz_pauli[q]; break; } case OpType::Z: { Qubit q(args[0]); - rx_pauli[q] = -1. * rx_pauli[q]; + rx_pauli[q] = SpPauliStabiliser({}, 2) * rx_pauli[q]; break; } case OpType::X: { Qubit q(args[0]); - rz_pauli[q] = -1. * rz_pauli[q]; + rz_pauli[q] = SpPauliStabiliser({}, 2) * rz_pauli[q]; break; } case OpType::Sdg: { Qubit q(args[0]); - rx_pauli[q] = -i_ * rz_pauli[q] * rx_pauli[q]; + rx_pauli[q] = SpPauliStabiliser({}, 3) * rz_pauli[q] * rx_pauli[q]; break; } case OpType::Vdg: { Qubit q(args[0]); - rz_pauli[q] = -i_ * rx_pauli[q] * rz_pauli[q]; + rz_pauli[q] = SpPauliStabiliser({}, 3) * rx_pauli[q] * rz_pauli[q]; break; } case OpType::CX: { @@ -127,13 +127,17 @@ Transform pairwise_pauli_gadgets(CXConfigType cx_config) { case OpType::Rz: { Qubit q(args[0]); Expr angle = (op_ptr)->get_params()[0]; - pauli_gadgets.push_back({rz_pauli[q], angle}); + SpSymPauliTensor g = + (SpSymPauliTensor)rz_pauli[q] * SpSymPauliTensor({}, angle); + pauli_gadgets.push_back(g); break; } case OpType::Rx: { Qubit q(args[0]); Expr angle = (op_ptr)->get_params()[0]; - pauli_gadgets.push_back({rx_pauli[q], angle}); + SpSymPauliTensor g = + (SpSymPauliTensor)rx_pauli[q] * SpSymPauliTensor({}, angle); + pauli_gadgets.push_back(g); break; } case OpType::noop: @@ -164,17 +168,14 @@ Transform pairwise_pauli_gadgets(CXConfigType cx_config) { // Synthesise pairs of Pauli Gadgets unsigned g = 0; while (g + 1 < pauli_gadgets.size()) { - auto [pauli0, angle0] = pauli_gadgets[g]; - auto [pauli1, angle1] = pauli_gadgets[g + 1]; append_pauli_gadget_pair( - gadget_circ, pauli0, angle0, pauli1, angle1, cx_config); + gadget_circ, pauli_gadgets[g], pauli_gadgets[g + 1], cx_config); g += 2; } // As we synthesised Pauli gadgets 2 at a time, if there were an odd // number, we will have one left over, so add that one on its own if (g < pauli_gadgets.size()) { - auto [pauli, angle] = pauli_gadgets[g]; - append_single_pauli_gadget(gadget_circ, pauli, angle, cx_config); + append_single_pauli_gadget(gadget_circ, pauli_gadgets[g], cx_config); } // Stitch gadget circuit and Clifford circuit together circ = gadget_circ >> clifford_circ; diff --git a/tket/src/Utils/PauliStrings.cpp b/tket/src/Utils/PauliStrings.cpp deleted file mode 100644 index c322dfcf11..0000000000 --- a/tket/src/Utils/PauliStrings.cpp +++ /dev/null @@ -1,580 +0,0 @@ -// Copyright 2019-2023 Cambridge Quantum Computing -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "tket/Utils/PauliStrings.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#include "tket/Utils/Constants.hpp" -#include "tket/Utils/EigenConfig.hpp" -#include "tket/Utils/Json.hpp" - -namespace tket { - -static const CmplxSpMat const_2x2_matrix( - Complex tl, Complex tr, Complex bl, Complex br) { - CmplxSpMat m(2, 2); - if (tl != czero) { - m.insert(0, 0) = tl; - } - if (tr != czero) { - m.insert(0, 1) = tr; - } - if (bl != czero) { - m.insert(1, 0) = bl; - } - if (br != czero) { - m.insert(1, 1) = br; - } - return m; -} - -static const CmplxSpMat &pauli_sparse_mat(Pauli p) { - static const CmplxSpMat I_mat = const_2x2_matrix(1, 0, 0, 1); - static const CmplxSpMat X_mat = const_2x2_matrix(0, 1, 1, 0); - static const CmplxSpMat Y_mat = const_2x2_matrix(0, -i_, i_, 0); - static const CmplxSpMat Z_mat = const_2x2_matrix(1, 0, 0, -1); - switch (p) { - case Pauli::X: - return X_mat; - case Pauli::Y: - return Y_mat; - case Pauli::Z: - return Z_mat; - default: - TKET_ASSERT(p == Pauli::I); - return I_mat; - } -} - -class StateNotPowerTwo : public std::logic_error { - public: - StateNotPowerTwo() - : std::logic_error("Statevector size is not a power of two.") {} -}; - -unsigned get_n_qb_from_statevector(const Eigen::VectorXcd &state) { - // allowing room for big states - unsigned long long n = state.size(); - if (!(n && (!(n & (n - 1))))) { - throw StateNotPowerTwo(); - } - - unsigned count = 0; - while (n) { - n >>= 1; - ++count; - } - return count - 1; -} - -CmplxSpMat QubitPauliString::to_sparse_matrix() const { - qubit_vector_t qubits(map.size()); - unsigned i = 0; - for (const std::pair &pair : map) { - qubits[i] = pair.first; - ++i; - } - return to_sparse_matrix(qubits); -} - -CmplxSpMat QubitPauliString::to_sparse_matrix(const unsigned n_qubits) const { - qubit_vector_t qubits(n_qubits); - for (unsigned i = 0; i < n_qubits; ++i) { - qubits[i] = Qubit(i); - } - return to_sparse_matrix(qubits); -} - -CmplxSpMat QubitPauliString::to_sparse_matrix( - const qubit_vector_t &qubits) const { - std::vector paulis(qubits.size(), Pauli::I); - std::map index_map; - unsigned index = 0; - for (const Qubit &q : qubits) { - index_map.insert({q, index}); - ++index; - } - if (index_map.size() != qubits.size()) - throw std::logic_error( - "Qubit list given to to_sparse_matrix contains repeats"); - for (const std::pair &pair : map) { - std::map::iterator found = index_map.find(pair.first); - if (found == index_map.end()) - throw std::logic_error( - "Qubit list given to to_sparse_matrix doesn't contain " + - pair.first.repr()); - paulis.at(found->second) = pair.second; - } - CmplxSpMat result(1, 1); - result.insert(0, 0) = 1; - for (Pauli p : paulis) { - const CmplxSpMat pauli_mat = pauli_sparse_mat(p); - result = Eigen::KroneckerProductSparse(result, pauli_mat).eval(); - } - return result; -} - -CmplxSpMat operator_tensor( - const OperatorSum &total_operator, unsigned n_qubits) { - qubit_vector_t qubits(n_qubits); - for (unsigned i = 0; i < n_qubits; ++i) { - qubits[i] = Qubit(i); - } - return operator_tensor(total_operator, qubits); -} - -CmplxSpMat operator_tensor( - const OperatorSum &total_operator, const qubit_vector_t &qubits) { - CmplxSpMat sum = total_operator[0].second * - total_operator[0].first.to_sparse_matrix(qubits); - for (unsigned j = 1; j < total_operator.size(); j++) { - sum += total_operator[j].second * - total_operator[j].first.to_sparse_matrix(qubits); - } - return sum; -} - -Eigen::VectorXcd QubitPauliString::dot_state( - const Eigen::VectorXcd &state) const { - const unsigned n_qubits = get_n_qb_from_statevector(state); - return (to_sparse_matrix(n_qubits) * state); -} - -Eigen::VectorXcd QubitPauliString::dot_state( - const Eigen::VectorXcd &state, const qubit_vector_t &qubits) const { - if (state.size() != 1 << qubits.size()) - throw std::logic_error( - "Size of statevector does not match number of qubits passed to " - "dot_state"); - return (to_sparse_matrix(qubits) * state); -} - -Complex QubitPauliString::state_expectation( - const Eigen::VectorXcd &state) const { - return state.dot(dot_state(state)); -} - -Complex QubitPauliString::state_expectation( - const Eigen::VectorXcd &state, const qubit_vector_t &qubits) const { - return state.dot(dot_state(state, qubits)); -} - -Complex operator_expectation( - const OperatorSum &total_operator, const Eigen::VectorXcd &state) { - Complex exp(0, 0); - - for (unsigned j = 0; j < total_operator.size(); j++) { - exp += total_operator[j].second * - total_operator[j].first.state_expectation(state); - } - - return exp; -} - -Complex operator_expectation( - const OperatorSum &total_operator, const Eigen::VectorXcd &state, - const qubit_vector_t &qubits) { - Complex exp(0, 0); - - for (unsigned j = 0; j < total_operator.size(); j++) { - exp += total_operator[j].second * - total_operator[j].first.state_expectation(state, qubits); - } - - return exp; -} - -QubitPauliString::QubitPauliString( - const std::initializer_list &_paulis) { - unsigned qb_i = 0; - for (Pauli p : _paulis) { - map[Qubit(qb_i)] = p; - ++qb_i; - } -} - -QubitPauliString::QubitPauliString(const std::list &_paulis) { - unsigned qb_i = 0; - for (Pauli p : _paulis) { - map[Qubit(qb_i)] = p; - ++qb_i; - } -} - -QubitPauliString::QubitPauliString(const std::vector &_paulis) { - unsigned qb_i = 0; - for (Pauli p : _paulis) { - map[Qubit(qb_i)] = p; - ++qb_i; - } -} - -QubitPauliString::QubitPauliString( - const std::list &qubits, const std::list &paulis) { - if (qubits.size() != paulis.size()) { - throw std::logic_error( - "Mismatch of Qubits and Paulis upon QubitPauliString " - "construction"); - } - std::list::const_iterator p_it = paulis.begin(); - for (const Qubit &qb : qubits) { - Pauli p = *p_it; - if (map.find(qb) != map.end()) { - throw std::logic_error( - "Non-unique Qubit inserted into QubitPauliString map"); - } - map[qb] = p; - ++p_it; - } -} - -bool QubitPauliString::operator==(const QubitPauliString &other) const { - return compare(other) == 0; -} - -bool QubitPauliString::operator!=(const QubitPauliString &other) const { - return !(*this == other); -} - -bool QubitPauliString::operator<(const QubitPauliString &other) const { - return compare(other) < 0; -} - -int QubitPauliString::compare(const QubitPauliString &other) const { - QubitPauliMap::const_iterator p1_it = this->map.begin(); - QubitPauliMap::const_iterator p2_it = other.map.begin(); - while (p1_it != this->map.end()) { - if (p1_it->second == Pauli::I) { - ++p1_it; - continue; - } - while (p2_it != other.map.end() && p2_it->second == Pauli::I) { - ++p2_it; - } - if (p2_it == other.map.end()) return 1; - // QubitPauliString order should reflect ILO - // i.e. IZ < ZI (Zq1 < Zq0) - // Hence we first order by reverse of leading qubit - if (p1_it->first < p2_it->first) return 1; - if (p2_it->first < p1_it->first) return -1; - // and then by increasing order of Pauli letter on the same qubit - if (p1_it->second < p2_it->second) return -1; - if (p1_it->second > p2_it->second) return 1; - ++p1_it; - ++p2_it; - } - while (p2_it != other.map.end() && p2_it->second == Pauli::I) { - ++p2_it; - } - return (p2_it == other.map.end()) ? 0 : -1; -} - -void QubitPauliString::compress() { - QubitPauliMap::iterator i = map.begin(); - while (i != map.end()) { - if (i->second == Pauli::I) { - i = map.erase(i); - } else { - ++i; - } - } -} - -bool QubitPauliString::commutes_with(const QubitPauliString &other) const { - return (conflicting_qubits(other).size() % 2) == 0; -} - -std::set QubitPauliString::common_qubits( - const QubitPauliString &other) const { - std::set common; - for (const std::pair &p : map) { - QubitPauliMap::const_iterator found = other.map.find(p.first); - if (p.second == Pauli::I) continue; - if (found != other.map.end() && found->second == p.second) { - common.insert(p.first); - } - } - return common; -} - -std::set QubitPauliString::own_qubits( - const QubitPauliString &other) const { - std::set own; - for (const std::pair &p : map) { - if (p.second == Pauli::I) continue; - QubitPauliMap::const_iterator found = other.map.find(p.first); - if (found == other.map.end() || found->second == Pauli::I) { - own.insert(p.first); - } - } - return own; -} - -std::set QubitPauliString::conflicting_qubits( - const QubitPauliString &other) const { - std::set conflicts; - for (const std::pair &p : map) { - if (p.second == Pauli::I) continue; - QubitPauliMap::const_iterator found = other.map.find(p.first); - if (found != other.map.end() && found->second != Pauli::I && - found->second != p.second) { - conflicts.insert(p.first); - } - } - return conflicts; -} - -std::string QubitPauliString::to_str() const { - std::stringstream d; - d << "("; - QubitPauliMap::const_iterator i = map.begin(); - while (i != map.end()) { - switch (i->second) { - case Pauli::I: { - d << "I"; - break; - } - case Pauli::X: { - d << "X"; - break; - } - case Pauli::Y: { - d << "Y"; - break; - } - case Pauli::Z: { - d << "Z"; - break; - } - } - d << i->first.repr(); - i++; - if (i != map.end()) d << ", "; - } - d << ")"; - return d.str(); -} - -Pauli QubitPauliString::get(const Qubit &q) const { - QubitPauliMap::const_iterator i = map.find(q); - if (i == map.end()) - return Pauli::I; - else - return i->second; -} - -void QubitPauliString::set(const Qubit &q, Pauli p) { - QubitPauliMap::iterator i = map.find(q); - if (i == map.end()) { - if (p != Pauli::I) map.insert({q, p}); - } else { - if (p == Pauli::I) - map.erase(i); - else - i->second = p; - } -} - -std::size_t hash_value(const QubitPauliString &qps) { - std::size_t seed = 0; - for (const std::pair &qb_p : qps.map) { - if (qb_p.second != Pauli::I) { - boost::hash_combine(seed, qb_p.first); - boost::hash_combine(seed, qb_p.second); - } - } - return seed; -} - -void to_json(nlohmann::json &j, const QubitPauliString &paulistr) { - j = nlohmann::json::array(); - for (const auto &[qb, pauli] : paulistr.map) { - j.push_back({qb, pauli}); - } -} - -void from_json(const nlohmann::json &j, QubitPauliString &paulistr) { - for (const auto &qb_pauli : j) { - paulistr.set(qb_pauli[0].get(), qb_pauli[1].get()); - } -} - -const QubitPauliTensor::Mult_Matrix &QubitPauliTensor::get_mult_matrix() { - static const Mult_Matrix mult_matrix{ - {{Pauli::I, Pauli::I}, {1., Pauli::I}}, - {{Pauli::I, Pauli::X}, {1., Pauli::X}}, - {{Pauli::I, Pauli::Y}, {1., Pauli::Y}}, - {{Pauli::I, Pauli::Z}, {1., Pauli::Z}}, - {{Pauli::X, Pauli::I}, {1., Pauli::X}}, - {{Pauli::X, Pauli::X}, {1., Pauli::I}}, - {{Pauli::X, Pauli::Y}, {i_, Pauli::Z}}, - {{Pauli::X, Pauli::Z}, {-i_, Pauli::Y}}, - {{Pauli::Y, Pauli::I}, {1., Pauli::Y}}, - {{Pauli::Y, Pauli::X}, {-i_, Pauli::Z}}, - {{Pauli::Y, Pauli::Y}, {1., Pauli::I}}, - {{Pauli::Y, Pauli::Z}, {i_, Pauli::X}}, - {{Pauli::Z, Pauli::I}, {1., Pauli::Z}}, - {{Pauli::Z, Pauli::X}, {i_, Pauli::Y}}, - {{Pauli::Z, Pauli::Y}, {-i_, Pauli::X}}, - {{Pauli::Z, Pauli::Z}, {1., Pauli::I}}}; - return mult_matrix; -} - -QubitPauliTensor QubitPauliTensor::operator*( - const QubitPauliTensor &other) const { - QubitPauliTensor result(this->coeff * other.coeff); - QubitPauliMap::const_iterator p1i = this->string.map.begin(); - QubitPauliMap::const_iterator p2i = other.string.map.begin(); - while (p1i != this->string.map.end()) { - while (p2i != other.string.map.end() && p2i->first < p1i->first) { - result.string.map.insert(*p2i); - p2i++; - } - if (p2i != other.string.map.end() && p2i->first == p1i->first) { - // Pauli in the same position, so need to multiply - const std::pair &prod = - QubitPauliTensor::get_mult_matrix().at({p1i->second, p2i->second}); - result.coeff *= prod.first; - if (prod.second != Pauli::I) { - result.string.map.insert({p1i->first, prod.second}); - } - p2i++; - } else { - result.string.map.insert(*p1i); - } - p1i++; - } - while (p2i != other.string.map.end()) { - result.string.map.insert(*p2i); - p2i++; - } - return result; -} - -void QubitPauliTensor::transpose() { - for (const std::pair &pair : string.map) { - if (pair.second == Pauli::Y) coeff *= -1.; - } -} - -bool QubitPauliTensor::operator==(const QubitPauliTensor &other) const { - if (this->coeff != other.coeff) return false; - return (this->string == other.string); -} - -bool QubitPauliTensor::operator!=(const QubitPauliTensor &other) const { - return !(*this == other); -} - -bool QubitPauliTensor::operator<(const QubitPauliTensor &other) const { - int comp = this->string.compare(other.string); - if (comp < 0) return true; - if (comp > 0) return false; - if (this->coeff.real() < other.coeff.real()) return true; - if (this->coeff.real() > other.coeff.real()) return false; - return (this->coeff.imag() < other.coeff.imag()); -} - -void QubitPauliTensor::compress() { string.compress(); } - -bool QubitPauliTensor::commutes_with(const QubitPauliTensor &other) const { - return (string.commutes_with(other.string)); -} - -std::set QubitPauliTensor::common_qubits( - const QubitPauliTensor &other) const { - return string.common_qubits(other.string); -} - -std::set QubitPauliTensor::own_qubits( - const QubitPauliTensor &other) const { - return string.own_qubits(other.string); -} - -std::set QubitPauliTensor::conflicting_qubits( - const QubitPauliTensor &other) const { - return (string.conflicting_qubits(other.string)); -} - -std::string QubitPauliTensor::to_str() const { - std::stringstream d; - if (coeff == -1.) { - d << "-"; - } else if (coeff != 1.) { - d << coeff << "*"; - } - d << string.to_str(); - return d.str(); -} - -std::size_t hash_value(const QubitPauliTensor &qpt) { - std::size_t seed = hash_value(qpt.string); - boost::hash_combine(seed, qpt.coeff); - return seed; -} - -QubitPauliTensor operator*(Complex a, const QubitPauliTensor &qpt) { - QubitPauliTensor result = qpt; - result.coeff *= a; - return result; -} - -PauliStabiliser::PauliStabiliser( - const std::vector string, const bool coeff) - : string(string), coeff(coeff) { - if (string.size() == 0) { - throw std::invalid_argument("Pauli stabiliser cannot be empty."); - } - if (std::adjacent_find(string.begin(), string.end(), std::not_equal_to<>()) == - string.end() && - string[0] == Pauli::I) { - throw std::invalid_argument("Pauli stabiliser cannot be identity."); - } -} - -bool PauliStabiliser::operator==(const PauliStabiliser &other) const { - return coeff == other.coeff && string == other.string; -} - -bool PauliStabiliser::operator!=(const PauliStabiliser &other) const { - return coeff != other.coeff || string != other.string; -} - -void to_json(nlohmann::json &j, const PauliStabiliser &pauli_stabiliser) { - j["string"] = pauli_stabiliser.string; - j["coeff"] = pauli_stabiliser.coeff; -} - -void from_json(const nlohmann::json &j, PauliStabiliser &pauli_stabiliser) { - pauli_stabiliser = PauliStabiliser( - j.at("string").get>(), j.at("coeff").get()); -} - -void to_json(nlohmann::json &j, const QubitPauliTensor &qubitPauliTensor) { - j["string"] = qubitPauliTensor.string; - j["coeff"] = qubitPauliTensor.coeff; -} - -void from_json(const nlohmann::json &j, QubitPauliTensor &qubitPauliTensor) { - qubitPauliTensor = QubitPauliTensor( - j.at("string").get(), j.at("coeff").get()); -} -} // namespace tket diff --git a/tket/src/Utils/PauliTensor.cpp b/tket/src/Utils/PauliTensor.cpp new file mode 100644 index 0000000000..680c178dd0 --- /dev/null +++ b/tket/src/Utils/PauliTensor.cpp @@ -0,0 +1,734 @@ +// Copyright 2019-2023 Cambridge Quantum Computing +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "tket/Utils/PauliTensor.hpp" + +#include + +namespace tket { + +void to_json(nlohmann::json &, const no_coeff_t &) {} +void from_json(const nlohmann::json &, no_coeff_t &) {} + +template <> +no_coeff_t default_coeff() { + return {}; +} +template <> +quarter_turns_t default_coeff() { + return 0; +} +template <> +Complex default_coeff() { + return 1.; +} +template <> +Expr default_coeff() { + return 1; +} + +template <> +QubitPauliMap cast_container( + const QubitPauliMap &cont) { + return cont; +} + +template <> +QubitPauliMap cast_container( + const DensePauliMap &cont) { + QubitPauliMap res; + for (unsigned i = 0; i < cont.size(); ++i) { + Pauli pi = cont.at(i); + if (pi != Pauli::I) res.insert({Qubit(i), cont.at(i)}); + } + return res; +} + +template <> +DensePauliMap cast_container( + const QubitPauliMap &cont) { + if (cont.empty()) return {}; + unsigned max_index = 0; + for (const std::pair &pair : cont) { + if (pair.first.reg_info() != register_info_t{UnitType::Qubit, 1} || + pair.first.reg_name() != q_default_reg()) + throw std::logic_error( + "Cannot cast a QubitPauliMap with non-default register qubits to a " + "DensePauliMap"); + unsigned i = pair.first.index().front(); + if (i > max_index) max_index = i; + } + DensePauliMap res(max_index + 1, Pauli::I); + for (const std::pair &pair : cont) { + res.at(pair.first.index().front()) = pair.second; + } + return res; +} + +template <> +DensePauliMap cast_container( + const DensePauliMap &cont) { + return cont; +} + +template <> +no_coeff_t cast_coeff(const no_coeff_t &) { + return {}; +} +template <> +quarter_turns_t cast_coeff(const no_coeff_t &) { + return 0; +} +template <> +Complex cast_coeff(const no_coeff_t &) { + return 1.; +} +template <> +Expr cast_coeff(const no_coeff_t &) { + return 1.; +} + +template <> +no_coeff_t cast_coeff(const quarter_turns_t &) { + return {}; +} +template <> +quarter_turns_t cast_coeff( + const quarter_turns_t &coeff) { + return coeff; +} +template <> +Complex cast_coeff(const quarter_turns_t &coeff) { + switch (coeff % 4) { + case 0: { + return 1.; + } + case 1: { + return i_; + } + case 2: { + return -1.; + } + default: { + return -i_; + } + } +} +template <> +Expr cast_coeff(const quarter_turns_t &coeff) { + switch (coeff % 4) { + case 0: { + return Expr(1); + } + case 1: { + return Expr(SymEngine::I); + } + case 2: { + return Expr(-1); + } + default: { + return -Expr(SymEngine::I); + } + } +} + +template <> +no_coeff_t cast_coeff(const Complex &) { + return {}; +} +template <> +quarter_turns_t cast_coeff(const Complex &coeff) { + if (std::abs(coeff - 1.) < EPS) + return 0; + else if (std::abs(coeff - i_) < EPS) + return 1; + else if (std::abs(coeff + 1.) < EPS) + return 2; + else if (std::abs(coeff + i_) < EPS) + return 3; + else + throw std::logic_error( + "Could not cast PauliTensor coefficient to quarter turns: not a power " + "of i."); +} +template <> +Complex cast_coeff(const Complex &coeff) { + return coeff; +} +template <> +Expr cast_coeff(const Complex &coeff) { + return Expr(SymEngine::make_rcp(coeff)); +} + +template <> +no_coeff_t cast_coeff(const Expr &) { + return {}; +} +template <> +quarter_turns_t cast_coeff(const Expr &coeff) { + std::optional ev = eval_expr_c(coeff); + if (ev) + return cast_coeff(*ev); + else + throw std::logic_error( + "Could not cast symbolic PauliTensor to quarter turns."); +} +template <> +Complex cast_coeff(const Expr &coeff) { + std::optional ev = eval_expr_c(coeff); + if (ev) + return *ev; + else + throw std::logic_error( + "Could not cast symbolic PauliTensor to complex coefficient."); +} +template <> +Expr cast_coeff(const Expr &coeff) { + return coeff; +} + +template <> +int compare_containers( + const QubitPauliMap &first, const QubitPauliMap &second) { + QubitPauliMap::const_iterator p1_it = first.begin(); + QubitPauliMap::const_iterator p2_it = second.begin(); + while (p1_it != first.end()) { + if (p1_it->second == Pauli::I) { + ++p1_it; + continue; + } + while (p2_it != second.end() && p2_it->second == Pauli::I) { + ++p2_it; + } + if (p2_it == second.end()) return 1; + // QubitPauliMap order should reflect ILO + // i.e. IZ < ZI (Zq1 < Zq0) + // Hence we first order by reverse of leading qubit + if (p1_it->first < p2_it->first) return 1; + if (p2_it->first < p1_it->first) return -1; + // and then by increasing order of Pauli letter on the same qubit + if (p1_it->second < p2_it->second) return -1; + if (p1_it->second > p2_it->second) return 1; + ++p1_it; + ++p2_it; + } + while (p2_it != second.end() && p2_it->second == Pauli::I) { + ++p2_it; + } + return (p2_it == second.end()) ? 0 : -1; +} + +template <> +int compare_containers( + const DensePauliMap &first, const DensePauliMap &second) { + DensePauliMap::const_iterator p1_it = first.begin(); + DensePauliMap::const_iterator p2_it = second.begin(); + while (p1_it != first.end() && p2_it != second.end()) { + if (*p1_it == Pauli::I) { + if (*p2_it != Pauli::I) return -1; + } else if (*p2_it == Pauli::I) + return 1; + else if (*p1_it < *p2_it) + return -1; + else if (*p1_it > *p2_it) + return 1; + ++p1_it; + ++p2_it; + } + while (p1_it != first.end() && *p1_it == Pauli::I) ++p1_it; + if (p1_it != first.end()) return 1; + while (p2_it != second.end() && *p2_it == Pauli::I) ++p2_it; + return (p2_it == second.end()) ? 0 : -1; +} + +template <> +int compare_coeffs(const no_coeff_t &, const no_coeff_t &) { + return 0; +} +template <> +int compare_coeffs( + const quarter_turns_t &first, const quarter_turns_t &second) { + if (first % 4 < second % 4) return -1; + return (first % 4 == second % 4) ? 0 : 1; +} +template <> +int compare_coeffs(const Complex &first, const Complex &second) { + if (first.real() < second.real()) return -1; + if (first.real() > second.real()) return 1; + if (first.imag() < second.imag()) return -1; + return (first.imag() == second.imag()) ? 0 : 1; +} +template <> +int compare_coeffs(const Expr &first, const Expr &second) { + // Comparison of SymEngine expressions will distinguish between e.g. integer + // 1, double 1., and complex 1., so only use for actual symbolic expressions + std::optional reduced_first = eval_expr_c(first); + std::optional reduced_second = eval_expr_c(second); + if (reduced_first && reduced_second) + return compare_coeffs(*reduced_first, *reduced_second); + else + return first.get_basic()->compare(second); +} + +std::set common_qubits( + const QubitPauliMap &first, const QubitPauliMap &second) { + std::set common; + for (const std::pair &p : first) { + if (p.second == Pauli::I) continue; + QubitPauliMap::const_iterator found = second.find(p.first); + if (found != second.end() && found->second == p.second) + common.insert(p.first); + } + return common; +} + +std::set common_indices( + const DensePauliMap &first, const DensePauliMap &second) { + std::set common; + unsigned min_size = std::min(first.size(), second.size()); + for (unsigned i = 0; i < min_size; ++i) { + Pauli p = first.at(i); + if (p != Pauli::I && p == second.at(i)) common.insert(i); + } + return common; +} + +std::set own_qubits( + const QubitPauliMap &first, const QubitPauliMap &second) { + std::set own; + for (const std::pair &p : first) { + if (p.second == Pauli::I) continue; + QubitPauliMap::const_iterator found = second.find(p.first); + if (found == second.end() || found->second == Pauli::I) own.insert(p.first); + } + return own; +} + +std::set own_indices( + const DensePauliMap &first, const DensePauliMap &second) { + std::set own; + unsigned min_size = std::min(first.size(), second.size()); + for (unsigned i = 0; i < min_size; ++i) { + if (first.at(i) != Pauli::I && second.at(i) == Pauli::I) own.insert(i); + } + for (unsigned i = min_size; i < first.size(); ++i) { + if (first.at(i) != Pauli::I) own.insert(i); + } + return own; +} + +std::set conflicting_qubits( + const QubitPauliMap &first, const QubitPauliMap &second) { + std::set conflicts; + for (const std::pair &p : first) { + if (p.second == Pauli::I) continue; + QubitPauliMap::const_iterator found = second.find(p.first); + if (found != second.end() && found->second != Pauli::I && + found->second != p.second) + conflicts.insert(p.first); + } + return conflicts; +} + +std::set conflicting_indices( + const DensePauliMap &first, const DensePauliMap &second) { + std::set conflicts; + unsigned min_size = std::min(first.size(), second.size()); + for (unsigned i = 0; i < min_size; ++i) { + Pauli p = first.at(i); + Pauli p2 = second.at(i); + if (p != Pauli::I && p2 != Pauli::I && p != p2) conflicts.insert(i); + } + return conflicts; +} + +template <> +bool commuting_containers( + const QubitPauliMap &first, const QubitPauliMap &second) { + return (conflicting_qubits(first, second).size() % 2) == 0; +} + +template <> +bool commuting_containers( + const DensePauliMap &first, const DensePauliMap &second) { + return (conflicting_indices(first, second).size() % 2) == 0; +} + +template <> +void print_paulis( + std::ostream &os, const QubitPauliMap &paulis) { + os << "("; + QubitPauliMap::const_iterator i = paulis.begin(); + while (i != paulis.end()) { + switch (i->second) { + case Pauli::I: { + os << "I"; + break; + } + case Pauli::X: { + os << "X"; + break; + } + case Pauli::Y: { + os << "Y"; + break; + } + case Pauli::Z: { + os << "Z"; + break; + } + } + os << i->first.repr(); + ++i; + if (i != paulis.end()) os << ", "; + } + os << ")"; +} + +template <> +void print_paulis( + std::ostream &os, const DensePauliMap &paulis) { + for (const Pauli &p : paulis) { + switch (p) { + case Pauli::I: { + os << "I"; + break; + } + case Pauli::X: { + os << "X"; + break; + } + case Pauli::Y: { + os << "Y"; + break; + } + case Pauli::Z: { + os << "Z"; + break; + } + } + } +} + +template <> +void print_coeff(std::ostream &, const no_coeff_t &) {} + +template <> +void print_coeff( + std::ostream &os, const quarter_turns_t &coeff) { + switch (coeff % 4) { + case 1: { + os << "i*"; + break; + } + case 2: { + os << "-"; + break; + } + case 3: { + os << "-i*"; + break; + } + default: { + break; + } + } +} + +template <> +void print_coeff(std::ostream &os, const Complex &coeff) { + if (coeff == -1.) { + os << "-"; + } else if (coeff != 1.) { + os << coeff << "*"; + } +} + +template <> +void print_coeff(std::ostream &os, const Expr &coeff) { + // Expressions distinguish integers from floating-points + if (coeff == -1. || coeff == -1) { + os << "-"; + } else if (coeff != 1. && coeff != 1) { + os << "(" << coeff << ")*"; + } +} + +template <> +void hash_combine_paulis( + std::size_t &seed, const QubitPauliMap &paulis) { + for (const std::pair &qp : paulis) { + if (qp.second != Pauli::I) { + boost::hash_combine(seed, qp.first); + boost::hash_combine(seed, qp.second); + } + } +} + +template <> +void hash_combine_paulis( + std::size_t &seed, const DensePauliMap &paulis) { + DensePauliMap::const_reverse_iterator i = paulis.rbegin(); + while (i != paulis.rend() && *i == Pauli::I) { + ++i; + } + while (i != paulis.rend()) { + boost::hash_combine(seed, *i); + ++i; + } +} + +template <> +void hash_combine_coeff(std::size_t &, const no_coeff_t &) {} + +template <> +void hash_combine_coeff( + std::size_t &seed, const quarter_turns_t &coeff) { + boost::hash_combine(seed, coeff % 4); +} + +template <> +void hash_combine_coeff(std::size_t &seed, const Complex &coeff) { + boost::hash_combine(seed, coeff); +} + +template <> +void hash_combine_coeff(std::size_t &seed, const Expr &coeff) { + boost::hash_combine(seed, coeff.get_basic()->hash()); +} + +template <> +unsigned n_ys(const QubitPauliMap &paulis) { + unsigned n = 0; + for (const std::pair &qp : paulis) { + if (qp.second == Pauli::Y) ++n; + } + return n; +} + +template <> +unsigned n_ys(const DensePauliMap &paulis) { + unsigned n = 0; + for (const Pauli &p : paulis) { + if (p == Pauli::Y) ++n; + } + return n; +} + +const std::map, std::pair> & +get_mult_matrix() { + static const std::map< + std::pair, std::pair> + mult_matrix{ + {{Pauli::I, Pauli::I}, {0, Pauli::I}}, + {{Pauli::I, Pauli::X}, {0, Pauli::X}}, + {{Pauli::I, Pauli::Y}, {0, Pauli::Y}}, + {{Pauli::I, Pauli::Z}, {0, Pauli::Z}}, + {{Pauli::X, Pauli::I}, {0, Pauli::X}}, + {{Pauli::X, Pauli::X}, {0, Pauli::I}}, + {{Pauli::X, Pauli::Y}, {1, Pauli::Z}}, + {{Pauli::X, Pauli::Z}, {3, Pauli::Y}}, + {{Pauli::Y, Pauli::I}, {0, Pauli::Y}}, + {{Pauli::Y, Pauli::X}, {3, Pauli::Z}}, + {{Pauli::Y, Pauli::Y}, {0, Pauli::I}}, + {{Pauli::Y, Pauli::Z}, {1, Pauli::X}}, + {{Pauli::Z, Pauli::I}, {0, Pauli::Z}}, + {{Pauli::Z, Pauli::X}, {1, Pauli::Y}}, + {{Pauli::Z, Pauli::Y}, {3, Pauli::X}}, + {{Pauli::Z, Pauli::Z}, {0, Pauli::I}}, + }; + return mult_matrix; +} + +template <> +std::pair multiply_strings( + const QubitPauliMap &first, const QubitPauliMap &second) { + quarter_turns_t total_turns = 0; + QubitPauliMap result; + QubitPauliMap::const_iterator fi = first.begin(); + QubitPauliMap::const_iterator si = second.begin(); + while (fi != first.end()) { + while (si != second.end() && si->first < fi->first) { + result.insert(*si); + ++si; + } + if (si != second.end() && si->first == fi->first) { + // Pauli in the same position, so need to multiply + const std::pair &prod = + get_mult_matrix().at({fi->second, si->second}); + total_turns += prod.first; + if (prod.second != Pauli::I) { + result.insert({fi->first, prod.second}); + } + ++si; + } else { + result.insert(*fi); + } + ++fi; + } + while (si != second.end()) { + result.insert(*si); + ++si; + } + return {total_turns, result}; +} + +template <> +std::pair multiply_strings( + const DensePauliMap &first, const DensePauliMap &second) { + quarter_turns_t total_turns = 0; + DensePauliMap result; + DensePauliMap::const_iterator fi = first.begin(); + DensePauliMap::const_iterator si = second.begin(); + while (fi != first.end() && si != second.end()) { + const std::pair &prod = + get_mult_matrix().at({*fi, *si}); + total_turns += prod.first; + result.push_back(prod.second); + ++fi; + ++si; + } + while (fi != first.end()) { + result.push_back(*fi); + ++fi; + } + while (si != second.end()) { + result.push_back(*si); + ++si; + } + return {total_turns, result}; +} + +template <> +no_coeff_t multiply_coeffs(const no_coeff_t &, const no_coeff_t &) { + return {}; +} + +template <> +quarter_turns_t multiply_coeffs( + const quarter_turns_t &first, const quarter_turns_t &second) { + return (first + second) % 4; +} + +template <> +Complex multiply_coeffs(const Complex &first, const Complex &second) { + return first * second; +} + +template <> +Expr multiply_coeffs(const Expr &first, const Expr &second) { + return first * second; +} + +static const CmplxSpMat const_2x2_matrix( + Complex tl, Complex tr, Complex bl, Complex br) { + CmplxSpMat m(2, 2); + if (tl != czero) { + m.insert(0, 0) = tl; + } + if (tr != czero) { + m.insert(0, 1) = tr; + } + if (bl != czero) { + m.insert(1, 0) = bl; + } + if (br != czero) { + m.insert(1, 1) = br; + } + return m; +} + +static const CmplxSpMat &pauli_sparse_mat(Pauli p) { + static const CmplxSpMat I_mat = const_2x2_matrix(1, 0, 0, 1); + static const CmplxSpMat X_mat = const_2x2_matrix(0, 1, 1, 0); + static const CmplxSpMat Y_mat = const_2x2_matrix(0, -i_, i_, 0); + static const CmplxSpMat Z_mat = const_2x2_matrix(1, 0, 0, -1); + switch (p) { + case Pauli::X: + return X_mat; + case Pauli::Y: + return Y_mat; + case Pauli::Z: + return Z_mat; + default: + TKET_ASSERT(p == Pauli::I); + return I_mat; + } +} + +template <> +CmplxSpMat to_sparse_matrix(const QubitPauliMap &paulis) { + DensePauliMap matrix_paulis; + for (const std::pair &pair : paulis) + matrix_paulis.push_back(pair.second); + return to_sparse_matrix(matrix_paulis); +} +template <> +CmplxSpMat to_sparse_matrix(const DensePauliMap &paulis) { + CmplxSpMat result = CmplxSpMat(1, 1); + result.insert(0, 0) = 1.; + for (Pauli p : paulis) { + const CmplxSpMat pauli_mat = pauli_sparse_mat(p); + result = Eigen::KroneckerProductSparse(result, pauli_mat).eval(); + } + return result; +} + +template <> +CmplxSpMat to_sparse_matrix( + const QubitPauliMap &paulis, unsigned n_qubits) { + qubit_vector_t qubits(n_qubits); + for (unsigned i = 0; i < n_qubits; ++i) qubits.at(i) = Qubit(i); + return to_sparse_matrix(paulis, qubits); +} +template <> +CmplxSpMat to_sparse_matrix( + const DensePauliMap &paulis, unsigned n_qubits) { + if (n_qubits < paulis.size()) + throw std::logic_error( + "Called to_sparse_matrix for fewer qubits than in the Pauli string."); + DensePauliMap matrix_paulis = paulis; + for (unsigned i = paulis.size(); i < n_qubits; ++i) + matrix_paulis.push_back(Pauli::I); + return to_sparse_matrix(matrix_paulis); +} + +template <> +CmplxSpMat to_sparse_matrix( + const QubitPauliMap &paulis, const qubit_vector_t &qubits) { + DensePauliMap matrix_paulis(qubits.size(), Pauli::I); + std::map index_map; + for (const Qubit &q : qubits) + index_map.insert({q, (unsigned)index_map.size()}); + if (index_map.size() != qubits.size()) + throw std::logic_error( + "Qubit list given to to_sparse_matrix contains repeats."); + for (const std::pair &pair : paulis) { + std::map::iterator found = index_map.find(pair.first); + if (found == index_map.end()) + throw std::logic_error( + "Qubit list given to to_sparse_matrix doesn't contain " + + pair.first.repr()); + matrix_paulis.at(found->second) = pair.second; + } + return to_sparse_matrix(matrix_paulis); +} +template <> +CmplxSpMat to_sparse_matrix( + const DensePauliMap &paulis, const qubit_vector_t &qubits) { + return to_sparse_matrix( + cast_container(paulis), qubits); +} + +} // namespace tket diff --git a/tket/test/CMakeLists.txt b/tket/test/CMakeLists.txt index 93ec203b6f..37b4eed2be 100644 --- a/tket/test/CMakeLists.txt +++ b/tket/test/CMakeLists.txt @@ -98,7 +98,7 @@ add_executable(test-tket src/TokenSwapping/test_SwapsFromQubitMapping.cpp src/TokenSwapping/test_VariousPartialTsa.cpp src/Architecture/test_SubgraphMonomorphisms.cpp - src/test_PauliString.cpp + src/test_PauliTensor.cpp src/Ops/test_ClassicalOps.cpp src/Ops/test_Expression.cpp src/Ops/test_Ops.cpp diff --git a/tket/test/src/Circuit/test_Boxes.cpp b/tket/test/src/Circuit/test_Boxes.cpp index a5cde9a26f..e2c8afd1ac 100644 --- a/tket/test/src/Circuit/test_Boxes.cpp +++ b/tket/test/src/Circuit/test_Boxes.cpp @@ -256,7 +256,7 @@ SCENARIO("box daggers", "[boxes]") { 4., 2. + 3. * i_, 5.; ExpBox ebox(A, -0.5); // PauliExpBox - PauliExpBox pbox({Pauli::X, Pauli::Y, Pauli::Z}, 0.8); + PauliExpBox pbox(SymPauliTensor({Pauli::X, Pauli::Y, Pauli::Z}, 0.8)); // Put all these boxes into a circuit Circuit w(3); @@ -1069,10 +1069,10 @@ SCENARIO("Checking equality", "[boxes]") { } GIVEN("Pauli gadgets") { double t = 1.687029013593215; - PauliExpBox pbox({Pauli::X}, t); + PauliExpBox pbox(SymPauliTensor({Pauli::X}, t)); WHEN("both arguments are equal") { REQUIRE(pbox == pbox); } WHEN("both arguments are different") { - PauliExpBox pbox2({Pauli::Y}, t); + PauliExpBox pbox2(SymPauliTensor({Pauli::Y}, t)); REQUIRE(pbox != pbox2); } } @@ -1163,9 +1163,9 @@ SCENARIO("Checking equality", "[boxes]") { } } GIVEN("StabiliserAssertionBox") { - PauliStabiliser p1 = {{Pauli::X, Pauli::X}, true}; - PauliStabiliser p2 = {{Pauli::Z, Pauli::Z}, true}; - PauliStabiliser p3 = {{Pauli::Z, Pauli::Z}, false}; + PauliStabiliser p1 = {{Pauli::X, Pauli::X}, 0}; + PauliStabiliser p2 = {{Pauli::Z, Pauli::Z}, 0}; + PauliStabiliser p3 = {{Pauli::Z, Pauli::Z}, 2}; StabiliserAssertionBox box({p1, p2}); WHEN("both arguments are equal") { REQUIRE(box == box); } WHEN("different ids but equivalent stabilisers") { @@ -1302,52 +1302,72 @@ SCENARIO("Checking equality", "[boxes]") { } } GIVEN("PauliExpBox") { - PauliExpBox pbox({Pauli::X, Pauli::Y, Pauli::Z}, 0.8); + PauliExpBox pbox(SymPauliTensor({Pauli::X, Pauli::Y, Pauli::Z}, 0.8)); WHEN("both arguments are equal") { REQUIRE(pbox == pbox); } WHEN("different ids but same arguments") { - REQUIRE(pbox == PauliExpBox({Pauli::X, Pauli::Y, Pauli::Z}, 0.8)); + REQUIRE( + pbox == + PauliExpBox(SymPauliTensor({Pauli::X, Pauli::Y, Pauli::Z}, 0.8))); } WHEN("different ids, equivalent angle") { - REQUIRE(pbox == PauliExpBox({Pauli::X, Pauli::Y, Pauli::Z}, 4.8)); + REQUIRE( + pbox == + PauliExpBox(SymPauliTensor({Pauli::X, Pauli::Y, Pauli::Z}, 4.8))); } WHEN("different arguments") { - REQUIRE(pbox != PauliExpBox({Pauli::X, Pauli::Y, Pauli::Z}, 0.9)); + REQUIRE( + pbox != + PauliExpBox(SymPauliTensor({Pauli::X, Pauli::Y, Pauli::Z}, 0.9))); } } GIVEN("PauliExpPairBox") { - PauliExpPairBox pbox({Pauli::X}, 1.0, {Pauli::I}, 0.0); + PauliExpPairBox pbox( + SymPauliTensor({Pauli::X}, 1.0), SymPauliTensor({Pauli::I}, 0.0)); WHEN("both arguments are equal") { REQUIRE(pbox == pbox); } WHEN("different ids but same arguments") { - REQUIRE(pbox == PauliExpPairBox({Pauli::X}, 1.0, {Pauli::I}, 0.0)); + REQUIRE( + pbox == PauliExpPairBox( + SymPauliTensor({Pauli::X}, 1.0), + SymPauliTensor({Pauli::I}, 0.0))); } WHEN("different ids, equivalent angle") { - REQUIRE(pbox == PauliExpPairBox({Pauli::X}, 1.0, {Pauli::I}, 4.0)); + REQUIRE( + pbox == PauliExpPairBox( + SymPauliTensor({Pauli::X}, 1.0), + SymPauliTensor({Pauli::I}, 4.0))); } WHEN("different arguments") { - REQUIRE(pbox != PauliExpPairBox({Pauli::X}, -1.0, {Pauli::I}, 0.0)); + REQUIRE( + pbox != PauliExpPairBox( + SymPauliTensor({Pauli::X}, -1.0), + SymPauliTensor({Pauli::I}, 0.0))); } } GIVEN("PauliExpCommutingSetBox") { PauliExpCommutingSetBox pbox( - {{{Pauli::X}, 1.0}, {{Pauli::I}, 1.2}, {{Pauli::I}, -0.5}}); + {SymPauliTensor({Pauli::X}, 1.0), SymPauliTensor({Pauli::I}, 1.2), + SymPauliTensor({Pauli::I}, -0.5)}); WHEN("both arguments are equal") { REQUIRE(pbox == pbox); } WHEN("different ids but same arguments") { REQUIRE( pbox == PauliExpCommutingSetBox( - {{{Pauli::X}, 1.0}, {{Pauli::I}, 1.2}, {{Pauli::I}, -0.5}})); + {SymPauliTensor({Pauli::X}, 1.0), SymPauliTensor({Pauli::I}, 1.2), + SymPauliTensor({Pauli::I}, -0.5)})); } WHEN("different ids, equivalent angles") { REQUIRE( - pbox == - PauliExpCommutingSetBox( - {{{Pauli::X}, -3.0}, {{Pauli::I}, 5.2}, {{Pauli::I}, -0.5}})); + pbox == PauliExpCommutingSetBox( + {SymPauliTensor({Pauli::X}, -3.0), + SymPauliTensor({Pauli::I}, 5.2), + SymPauliTensor({Pauli::I}, -0.5)})); } WHEN("different arguments") { REQUIRE( pbox != PauliExpCommutingSetBox( - {{{Pauli::Y}, 1.0}, {{Pauli::I}, 1.2}, {{Pauli::I}, -0.5}})); + {SymPauliTensor({Pauli::Y}, 1.0), SymPauliTensor({Pauli::I}, 1.2), + SymPauliTensor({Pauli::I}, -0.5)})); } } GIVEN("ConjugationBox") { diff --git a/tket/test/src/Circuit/test_Circ.cpp b/tket/test/src/Circuit/test_Circ.cpp index 98ff1ccf10..a2536bf2e4 100644 --- a/tket/test/src/Circuit/test_Circ.cpp +++ b/tket/test/src/Circuit/test_Circ.cpp @@ -37,7 +37,6 @@ #include "tket/Transformations/Replacement.hpp" #include "tket/Transformations/Transform.hpp" #include "tket/Utils/MatrixAnalysis.hpp" -#include "tket/Utils/PauliStrings.hpp" namespace tket { namespace test_Circ { @@ -2067,8 +2066,7 @@ SCENARIO("Decomposing a multi-qubit operation into CXs") { WHEN("ZX circuit replacement") { rep = CX_ZX_circ_from_op(op); } const auto u = tket_sim::get_unitary(rep); - const QubitPauliString pauliop( - {{Qubit(0), Pauli::Z}, {Qubit(1), Pauli::Z}}); + const PauliString pauliop({Pauli::Z, Pauli::Z}); Eigen::MatrixXcd exponent = -pauliop.to_sparse_matrix(2) * 0.15 * PI * i_; Eigen::MatrixXcd correct = exponent.exp(); REQUIRE((u - correct).cwiseAbs().sum() < ERR_EPS); @@ -2122,8 +2120,7 @@ SCENARIO("Decomposing a multi-qubit operation into CXs") { WHEN("ZX circuit replacement") { rep = CX_ZX_circ_from_op(op); } const auto u = tket_sim::get_unitary(rep); - const QubitPauliString pauliop( - {{Qubit(0), Pauli::X}, {Qubit(1), Pauli::X}}); + const PauliString pauliop({Pauli::X, Pauli::X}); Eigen::MatrixXcd exponent = -pauliop.to_sparse_matrix(2) * 0.25 * PI * i_; Eigen::MatrixXcd correct = exponent.exp(); REQUIRE((u - correct).cwiseAbs().sum() < ERR_EPS); @@ -2138,12 +2135,9 @@ SCENARIO("Decomposing a multi-qubit operation into CXs") { WHEN("ZX circuit replacement") { rep = CX_ZX_circ_from_op(op); } const auto u = tket_sim::get_unitary(rep); - const QubitPauliString pauliop01( - {{Qubit(0), Pauli::X}, {Qubit(1), Pauli::X}, {Qubit(2), Pauli::I}}); - const QubitPauliString pauliop12( - {{Qubit(0), Pauli::I}, {Qubit(1), Pauli::X}, {Qubit(2), Pauli::X}}); - const QubitPauliString pauliop02( - {{Qubit(0), Pauli::X}, {Qubit(1), Pauli::I}, {Qubit(2), Pauli::X}}); + const PauliString pauliop01({Pauli::X, Pauli::X, Pauli::I}); + const PauliString pauliop12({Pauli::I, Pauli::X, Pauli::X}); + const PauliString pauliop02({Pauli::X, Pauli::I, Pauli::X}); Eigen::MatrixXcd exponent = -(pauliop01.to_sparse_matrix(3) + pauliop12.to_sparse_matrix(3) + pauliop02.to_sparse_matrix(3)) * @@ -2162,8 +2156,7 @@ SCENARIO("Decomposing a multi-qubit operation into CXs") { WHEN("ZX circuit replacement") { rep = CX_ZX_circ_from_op(op); } const auto u = tket_sim::get_unitary(rep); - const QubitPauliString pauliop( - {{Qubit(0), Pauli::Z}, {Qubit(1), Pauli::Z}}); + const PauliString pauliop({Pauli::Z, Pauli::Z}); Eigen::MatrixXcd exponent = -pauliop.to_sparse_matrix(2) * 0.25 * PI * i_; Eigen::MatrixXcd correct = exponent.exp(); REQUIRE((u - correct).cwiseAbs().sum() < ERR_EPS); @@ -2430,7 +2423,7 @@ SCENARIO("Decomposing a single qubit gate") { WHEN("ZX circuit replacement") { rep = CX_ZX_circ_from_op(op); } const auto u = tket_sim::get_unitary(rep); - const QubitPauliString pauliop({{Qubit(0), Pauli::X}}); + const PauliString pauliop({Pauli::X}); Eigen::MatrixXcd exponent = -pauliop.to_sparse_matrix(1) * 0.15 * PI * i_; Eigen::MatrixXcd correct = exponent.exp(); REQUIRE((u - correct).cwiseAbs().sum() < ERR_EPS); @@ -2449,7 +2442,7 @@ SCENARIO("Decomposing a single qubit gate") { WHEN("ZX circuit replacement") { rep = CX_ZX_circ_from_op(op); } const auto u = tket_sim::get_unitary(rep); - const QubitPauliString pauliop({{Qubit(0), Pauli::Y}}); + const PauliString pauliop({Pauli::Y}); Eigen::MatrixXcd exponent = -pauliop.to_sparse_matrix(1) * 0.2 * PI * i_; Eigen::MatrixXcd correct = exponent.exp(); REQUIRE((u - correct).cwiseAbs().sum() < ERR_EPS); @@ -2468,7 +2461,7 @@ SCENARIO("Decomposing a single qubit gate") { WHEN("ZX circuit replacement") { rep = CX_ZX_circ_from_op(op); } const auto u = tket_sim::get_unitary(rep); - const QubitPauliString pauliop({{Qubit(0), Pauli::Z}}); + const PauliString pauliop({Pauli::Z}); Eigen::MatrixXcd exponent = -pauliop.to_sparse_matrix(1) * 0.35 * PI * i_; Eigen::MatrixXcd correct = exponent.exp(); REQUIRE((u - correct).cwiseAbs().sum() < ERR_EPS); @@ -2507,11 +2500,10 @@ SCENARIO("Decomposing a single qubit gate") { WHEN("ZX circuit replacement") { rep = CX_ZX_circ_from_op(op); } const auto u = tket_sim::get_unitary(rep); - const QubitPauliString pauliop({{Qubit(0), Pauli::Z}}); + const PauliString pauliop({Pauli::Z}); Eigen::MatrixXcd exponent = -pauliop.to_sparse_matrix(1) * 0.65 * PI * i_; Eigen::MatrixXcd phaser = exponent.exp(); - exponent = -QubitPauliString({{Qubit(0), Pauli::X}}).to_sparse_matrix(1) * - 0.3 * PI * i_; + exponent = -PauliString({Pauli::X}).to_sparse_matrix(1) * 0.3 * PI * i_; Eigen::MatrixXcd correct = phaser * exponent.exp() * phaser.adjoint(); REQUIRE((u - correct).cwiseAbs().sum() < ERR_EPS); } diff --git a/tket/test/src/Circuit/test_PauliExpBoxes.cpp b/tket/test/src/Circuit/test_PauliExpBoxes.cpp index a0af652a06..96b3f368da 100644 --- a/tket/test/src/Circuit/test_PauliExpBoxes.cpp +++ b/tket/test/src/Circuit/test_PauliExpBoxes.cpp @@ -27,7 +27,7 @@ namespace test_PauliExpBoxes { SCENARIO("Pauli gadgets", "[boxes]") { GIVEN("Basis Circuit check") { - PauliExpBox pbox({Pauli::X}, 1.0); + PauliExpBox pbox(SymPauliTensor({Pauli::X}, 1.0)); auto circ = pbox.to_circuit(); circ->decompose_boxes_recursively(); Circuit comp(1); @@ -46,8 +46,8 @@ SCENARIO("Pauli gadgets", "[boxes]") { // ---PauliExpBox([X], t)----Rx(-t)--- should be the identity double t = 1.687029013593215; Circuit c(1); - std::vector pauli_x = {Pauli::X}; - PauliExpBox pbox(pauli_x, t); + DensePauliMap pauli_x = {Pauli::X}; + PauliExpBox pbox(SymPauliTensor(pauli_x, t)); REQUIRE(pbox.get_paulis() == pauli_x); c.add_box(pbox, uvec{0}); c.add_op(OpType::Rx, -t, {0}); @@ -58,7 +58,7 @@ SCENARIO("Pauli gadgets", "[boxes]") { // ---PauliExpBox([Y], t)----Ry(-t)--- should be the identity double t = 1.6791969622440162; Circuit c(1); - PauliExpBox pbox({Pauli::Y}, t); + PauliExpBox pbox(SymPauliTensor({Pauli::Y}, t)); c.add_box(pbox, uvec{0}); c.add_op(OpType::Ry, -t, {0}); Eigen::Matrix2Xcd u = tket_sim::get_unitary(c); @@ -68,7 +68,7 @@ SCENARIO("Pauli gadgets", "[boxes]") { // ---PauliExpBox([Z], t)----Rz(-t)--- should be the identity double t = 1.7811410013115163; Circuit c(1); - PauliExpBox pbox({Pauli::Z}, t); + PauliExpBox pbox(SymPauliTensor({Pauli::Z}, t)); c.add_box(pbox, uvec{0}); c.add_op(OpType::Rz, -t, {0}); Eigen::Matrix2Xcd u = tket_sim::get_unitary(c); @@ -81,7 +81,7 @@ SCENARIO("Pauli gadgets", "[boxes]") { ExpBox ebox(a, +0.5 * PI * t); Circuit c(2); c.add_box(ebox, {0, 1}); - PauliExpBox pbox({Pauli::I, Pauli::I}, t); + PauliExpBox pbox(SymPauliTensor({Pauli::I, Pauli::I}, t)); c.add_box(pbox, {0, 1}); Eigen::MatrixXcd u = tket_sim::get_unitary(c); REQUIRE((u - Eigen::Matrix4cd::Identity()).cwiseAbs().sum() < ERR_EPS); @@ -93,7 +93,7 @@ SCENARIO("Pauli gadgets", "[boxes]") { ExpBox ebox(a, +0.5 * PI * t); Circuit c(2); c.add_box(ebox, {0, 1}); - PauliExpBox pbox({Pauli::I, Pauli::X}, t); + PauliExpBox pbox(SymPauliTensor({Pauli::I, Pauli::X}, t)); c.add_box(pbox, {0, 1}); Eigen::MatrixXcd u = tket_sim::get_unitary(c); REQUIRE((u - Eigen::Matrix4cd::Identity()).cwiseAbs().sum() < ERR_EPS); @@ -105,7 +105,7 @@ SCENARIO("Pauli gadgets", "[boxes]") { ExpBox ebox(a, +0.5 * PI * t); Circuit c(2); c.add_box(ebox, {0, 1}); - PauliExpBox pbox({Pauli::I, Pauli::Y}, t); + PauliExpBox pbox(SymPauliTensor({Pauli::I, Pauli::Y}, t)); c.add_box(pbox, {0, 1}); Eigen::MatrixXcd u = tket_sim::get_unitary(c); REQUIRE((u - Eigen::Matrix4cd::Identity()).cwiseAbs().sum() < ERR_EPS); @@ -117,7 +117,7 @@ SCENARIO("Pauli gadgets", "[boxes]") { ExpBox ebox(a, +0.5 * PI * t); Circuit c(2); c.add_box(ebox, {0, 1}); - PauliExpBox pbox({Pauli::I, Pauli::Z}, t); + PauliExpBox pbox(SymPauliTensor({Pauli::I, Pauli::Z}, t)); c.add_box(pbox, {0, 1}); Eigen::MatrixXcd u = tket_sim::get_unitary(c); REQUIRE((u - Eigen::Matrix4cd::Identity()).cwiseAbs().sum() < ERR_EPS); @@ -129,7 +129,7 @@ SCENARIO("Pauli gadgets", "[boxes]") { ExpBox ebox(a, +0.5 * PI * t); Circuit c(2); c.add_box(ebox, {0, 1}); - PauliExpBox pbox({Pauli::X, Pauli::I}, t); + PauliExpBox pbox(SymPauliTensor({Pauli::X, Pauli::I}, t)); c.add_box(pbox, {0, 1}); Eigen::MatrixXcd u = tket_sim::get_unitary(c); REQUIRE((u - Eigen::Matrix4cd::Identity()).cwiseAbs().sum() < ERR_EPS); @@ -141,7 +141,7 @@ SCENARIO("Pauli gadgets", "[boxes]") { ExpBox ebox(a, +0.5 * PI * t); Circuit c(2); c.add_box(ebox, {0, 1}); - PauliExpBox pbox({Pauli::X, Pauli::X}, t); + PauliExpBox pbox(SymPauliTensor({Pauli::X, Pauli::X}, t)); c.add_box(pbox, {0, 1}); Eigen::MatrixXcd u = tket_sim::get_unitary(c); REQUIRE((u - Eigen::Matrix4cd::Identity()).cwiseAbs().sum() < ERR_EPS); @@ -153,7 +153,7 @@ SCENARIO("Pauli gadgets", "[boxes]") { ExpBox ebox(a, +0.5 * PI * t); Circuit c(2); c.add_box(ebox, {0, 1}); - PauliExpBox pbox({Pauli::X, Pauli::Y}, t); + PauliExpBox pbox(SymPauliTensor({Pauli::X, Pauli::Y}, t)); c.add_box(pbox, {0, 1}); Eigen::MatrixXcd u = tket_sim::get_unitary(c); REQUIRE((u - Eigen::Matrix4cd::Identity()).cwiseAbs().sum() < ERR_EPS); @@ -165,7 +165,7 @@ SCENARIO("Pauli gadgets", "[boxes]") { ExpBox ebox(a, +0.5 * PI * t); Circuit c(2); c.add_box(ebox, {0, 1}); - PauliExpBox pbox({Pauli::X, Pauli::Z}, t); + PauliExpBox pbox(SymPauliTensor({Pauli::X, Pauli::Z}, t)); c.add_box(pbox, {0, 1}); Eigen::MatrixXcd u = tket_sim::get_unitary(c); REQUIRE((u - Eigen::Matrix4cd::Identity()).cwiseAbs().sum() < ERR_EPS); @@ -177,7 +177,7 @@ SCENARIO("Pauli gadgets", "[boxes]") { ExpBox ebox(a, +0.5 * PI * t); Circuit c(2); c.add_box(ebox, {0, 1}); - PauliExpBox pbox({Pauli::Y, Pauli::I}, t); + PauliExpBox pbox(SymPauliTensor({Pauli::Y, Pauli::I}, t)); c.add_box(pbox, {0, 1}); Eigen::MatrixXcd u = tket_sim::get_unitary(c); REQUIRE((u - Eigen::Matrix4cd::Identity()).cwiseAbs().sum() < ERR_EPS); @@ -189,7 +189,7 @@ SCENARIO("Pauli gadgets", "[boxes]") { ExpBox ebox(a, +0.5 * PI * t); Circuit c(2); c.add_box(ebox, {0, 1}); - PauliExpBox pbox({Pauli::Y, Pauli::X}, t); + PauliExpBox pbox(SymPauliTensor({Pauli::Y, Pauli::X}, t)); c.add_box(pbox, {0, 1}); Eigen::MatrixXcd u = tket_sim::get_unitary(c); REQUIRE((u - Eigen::Matrix4cd::Identity()).cwiseAbs().sum() < ERR_EPS); @@ -201,7 +201,7 @@ SCENARIO("Pauli gadgets", "[boxes]") { ExpBox ebox(a, +0.5 * PI * t); Circuit c(2); c.add_box(ebox, {0, 1}); - PauliExpBox pbox({Pauli::Y, Pauli::Y}, t); + PauliExpBox pbox(SymPauliTensor({Pauli::Y, Pauli::Y}, t)); c.add_box(pbox, {0, 1}); Eigen::MatrixXcd u = tket_sim::get_unitary(c); REQUIRE((u - Eigen::Matrix4cd::Identity()).cwiseAbs().sum() < ERR_EPS); @@ -213,7 +213,7 @@ SCENARIO("Pauli gadgets", "[boxes]") { ExpBox ebox(a, +0.5 * PI * t); Circuit c(2); c.add_box(ebox, {0, 1}); - PauliExpBox pbox({Pauli::Y, Pauli::Z}, t); + PauliExpBox pbox(SymPauliTensor({Pauli::Y, Pauli::Z}, t)); c.add_box(pbox, {0, 1}); Eigen::MatrixXcd u = tket_sim::get_unitary(c); REQUIRE((u - Eigen::Matrix4cd::Identity()).cwiseAbs().sum() < ERR_EPS); @@ -225,7 +225,7 @@ SCENARIO("Pauli gadgets", "[boxes]") { ExpBox ebox(a, +0.5 * PI * t); Circuit c(2); c.add_box(ebox, {0, 1}); - PauliExpBox pbox({Pauli::Z, Pauli::I}, t); + PauliExpBox pbox(SymPauliTensor({Pauli::Z, Pauli::I}, t)); c.add_box(pbox, {0, 1}); Eigen::MatrixXcd u = tket_sim::get_unitary(c); REQUIRE((u - Eigen::Matrix4cd::Identity()).cwiseAbs().sum() < ERR_EPS); @@ -237,7 +237,7 @@ SCENARIO("Pauli gadgets", "[boxes]") { ExpBox ebox(a, +0.5 * PI * t); Circuit c(2); c.add_box(ebox, {0, 1}); - PauliExpBox pbox({Pauli::Z, Pauli::X}, t); + PauliExpBox pbox(SymPauliTensor({Pauli::Z, Pauli::X}, t)); c.add_box(pbox, {0, 1}); Eigen::MatrixXcd u = tket_sim::get_unitary(c); REQUIRE((u - Eigen::Matrix4cd::Identity()).cwiseAbs().sum() < ERR_EPS); @@ -249,7 +249,7 @@ SCENARIO("Pauli gadgets", "[boxes]") { ExpBox ebox(a, +0.5 * PI * t); Circuit c(2); c.add_box(ebox, {0, 1}); - PauliExpBox pbox({Pauli::Z, Pauli::Y}, t); + PauliExpBox pbox(SymPauliTensor({Pauli::Z, Pauli::Y}, t)); c.add_box(pbox, {0, 1}); Eigen::MatrixXcd u = tket_sim::get_unitary(c); REQUIRE((u - Eigen::Matrix4cd::Identity()).cwiseAbs().sum() < ERR_EPS); @@ -261,21 +261,22 @@ SCENARIO("Pauli gadgets", "[boxes]") { ExpBox ebox(a, +0.5 * PI * t); Circuit c(2); c.add_box(ebox, {0, 1}); - PauliExpBox pbox({Pauli::Z, Pauli::Z}, t); + PauliExpBox pbox(SymPauliTensor({Pauli::Z, Pauli::Z}, t)); c.add_box(pbox, {0, 1}); Eigen::MatrixXcd u = tket_sim::get_unitary(c); REQUIRE((u - Eigen::Matrix4cd::Identity()).cwiseAbs().sum() < ERR_EPS); } GIVEN("complex coefficient") { Expr ei{SymEngine::I}; - PauliExpBox pebox({Pauli::Z}, ei); + PauliExpBox pebox(SymPauliTensor({Pauli::Z}, ei)); Expr p = pebox.get_phase(); REQUIRE(p == ei); } } SCENARIO("Pauli gadget pairs", "[boxes]") { GIVEN("Basis Circuit check") { - PauliExpPairBox pbox({Pauli::X}, 1.0, {Pauli::I}, 0.0); + PauliExpPairBox pbox( + SymPauliTensor({Pauli::X}, 1.0), SymPauliTensor({Pauli::I}, 0.0)); auto circ = pbox.to_circuit(); circ->decompose_boxes_recursively(); Circuit comp(1); @@ -292,15 +293,18 @@ SCENARIO("Pauli gadget pairs", "[boxes]") { REQUIRE(*empty_pbox_circuit == empty_circuit); } GIVEN("Construction with two pauli strings of different length throws") { - auto pauli_string0 = std::vector({Pauli::X, Pauli::Z}); - auto pauli_string1 = std::vector({Pauli::X, Pauli::Z, Pauli::I}); + DensePauliMap pauli_string0{Pauli::X, Pauli::Z}; + DensePauliMap pauli_string1{Pauli::X, Pauli::Z, Pauli::I}; REQUIRE_THROWS_AS( - PauliExpPairBox(pauli_string0, 1.0, pauli_string1, 1.0), + PauliExpPairBox( + SymPauliTensor(pauli_string0, 1.0), + SymPauliTensor(pauli_string1, 1.0)), PauliExpBoxInvalidity); } GIVEN("is_clifford test cases") { SECTION("Empty Paulis") { - REQUIRE(PauliExpPairBox({}, 1.2, {}, 0.1).is_clifford()); + REQUIRE(PauliExpPairBox(SymPauliTensor({}, 1.2), SymPauliTensor({}, 0.1)) + .is_clifford()); } SECTION("Various phases") { auto phase_case = GENERATE( @@ -312,8 +316,8 @@ SCENARIO("Pauli gadget pairs", "[boxes]") { std::make_tuple(0.5, 2.0, true), std::make_tuple(0.0, 0.3, false), std::make_tuple(0.1, 0.3, false), std::make_tuple(1.1, 2.0, false)); auto pbox = PauliExpPairBox( - {Pauli::X, Pauli::Y}, get<0>(phase_case), {Pauli::X, Pauli::Y}, - get<1>(phase_case)); + SymPauliTensor({Pauli::X, Pauli::Y}, get<0>(phase_case)), + SymPauliTensor({Pauli::X, Pauli::Y}, get<1>(phase_case))); REQUIRE(pbox.is_clifford() == get<2>(phase_case)); } } @@ -322,23 +326,32 @@ SCENARIO("Pauli gadget pairs", "[boxes]") { auto b = SymTable::fresh_symbol("b"); auto ea = Expr(a); auto eb = Expr(b); - auto paulis0 = std::vector{Pauli::X}; - auto paulis1 = std::vector{Pauli::Z}; - REQUIRE(PauliExpPairBox(paulis0, 0.2, paulis1, 0.4).free_symbols().empty()); + DensePauliMap paulis0{Pauli::X}; + DensePauliMap paulis1{Pauli::Z}; + REQUIRE(PauliExpPairBox( + SymPauliTensor(paulis0, 0.2), SymPauliTensor(paulis1, 0.4)) + .free_symbols() + .empty()); REQUIRE( - PauliExpPairBox(paulis0, ea, paulis1, 0.4).free_symbols() == SymSet{a}); + PauliExpPairBox( + SymPauliTensor(paulis0, ea), SymPauliTensor(paulis1, 0.4)) + .free_symbols() == SymSet{a}); REQUIRE( - PauliExpPairBox(paulis0, 1.0, paulis1, eb).free_symbols() == SymSet{b}); + PauliExpPairBox( + SymPauliTensor(paulis0, 1.0), SymPauliTensor(paulis1, eb)) + .free_symbols() == SymSet{b}); REQUIRE( - PauliExpPairBox(paulis0, ea, paulis1, eb).free_symbols() == - SymSet{a, b}); + PauliExpPairBox( + SymPauliTensor(paulis0, ea), SymPauliTensor(paulis1, eb)) + .free_symbols() == SymSet{a, b}); } GIVEN("dagger") { auto ea = Expr(SymTable::fresh_symbol("a")); - auto paulis0 = std::vector{Pauli::X}; - auto paulis1 = std::vector{Pauli::Z}; + DensePauliMap paulis0{Pauli::X}; + DensePauliMap paulis1{Pauli::Z}; auto cx_config = CXConfigType::Star; - auto box = PauliExpPairBox(paulis0, ea, paulis1, 0.4, cx_config); + auto box = PauliExpPairBox( + SymPauliTensor(paulis0, ea), SymPauliTensor(paulis1, 0.4), cx_config); auto dagger_box = std::dynamic_pointer_cast(box.dagger()); @@ -352,14 +365,13 @@ SCENARIO("Pauli gadget pairs", "[boxes]") { } GIVEN("transpose") { auto ea = Expr(SymTable::fresh_symbol("a")); - auto paulis0 = - std::vector{Pauli::X, Pauli::Y, Pauli::Z, Pauli::I, Pauli::Y}; - auto paulis1 = - std::vector{Pauli::Y, Pauli::Y, Pauli::Z, Pauli::I, Pauli::Y}; + DensePauliMap paulis0{Pauli::X, Pauli::Y, Pauli::Z, Pauli::I, Pauli::Y}; + DensePauliMap paulis1{Pauli::Y, Pauli::Y, Pauli::Z, Pauli::I, Pauli::Y}; auto cx_config = CXConfigType::MultiQGate; WHEN("paulis1 contains odd number of Y") { - auto box = PauliExpPairBox(paulis0, ea, paulis1, 0.4, cx_config); + auto box = PauliExpPairBox( + SymPauliTensor(paulis0, ea), SymPauliTensor(paulis1, 0.4), cx_config); auto transpose_box = std::dynamic_pointer_cast(box.transpose()); const auto [actual_paulis0, actual_paulis1] = @@ -375,7 +387,8 @@ SCENARIO("Pauli gadget pairs", "[boxes]") { std::swap(paulis0, paulis1); WHEN("paulis0 contains odd number of Y") { - auto box = PauliExpPairBox(paulis0, ea, paulis1, 0.4, cx_config); + auto box = PauliExpPairBox( + SymPauliTensor(paulis0, ea), SymPauliTensor(paulis1, 0.4), cx_config); auto transpose_box = std::dynamic_pointer_cast(box.transpose()); const auto [actual_paulis0, actual_paulis1] = @@ -394,10 +407,11 @@ SCENARIO("Pauli gadget pairs", "[boxes]") { auto b = SymTable::fresh_symbol("b"); auto ea = Expr(a); auto eb = Expr(b); - auto paulis0 = std::vector{Pauli::X}; - auto paulis1 = std::vector{Pauli::Z}; + DensePauliMap paulis0{Pauli::X}; + DensePauliMap paulis1{Pauli::Z}; - auto box = PauliExpPairBox(paulis0, ea, paulis1, eb); + auto box = PauliExpPairBox( + SymPauliTensor(paulis0, ea), SymPauliTensor(paulis1, eb)); WHEN("only first phase is substituted") { SymEngine::map_basic_basic sub_map{std::make_pair(a, Expr(0.8))}; @@ -450,26 +464,25 @@ SCENARIO("Pauli gadget commuting sets", "[boxes]") { } GIVEN("Construction with no gadgets throws") { REQUIRE_THROWS_AS( - PauliExpCommutingSetBox( - std::vector, Expr>>{}), + PauliExpCommutingSetBox(std::vector{}), PauliExpBoxInvalidity); } GIVEN("Construction with pauli strings of different length throws") { - auto pauli_string0 = std::vector({Pauli::X, Pauli::Z}); - auto pauli_string1 = std::vector({Pauli::X, Pauli::I}); - auto pauli_string2 = std::vector({Pauli::X, Pauli::Z, Pauli::I}); + DensePauliMap pauli_string0{Pauli::X, Pauli::Z}; + DensePauliMap pauli_string1{Pauli::X, Pauli::I}; + DensePauliMap pauli_string2{Pauli::X, Pauli::Z, Pauli::I}; REQUIRE_THROWS_AS( PauliExpCommutingSetBox({ - {pauli_string0, 1.0}, - {pauli_string1, 1.0}, - {pauli_string2, 1.0}, + SymPauliTensor(pauli_string0, 1.0), + SymPauliTensor(pauli_string1, 1.0), + SymPauliTensor(pauli_string2, 1.0), }), PauliExpBoxInvalidity); } GIVEN("Construction with non-commuting pauli strings throws") { - auto pauli_string0 = std::vector({Pauli::X, Pauli::Z}); - auto pauli_string1 = std::vector({Pauli::Z, Pauli::I}); - auto pauli_string2 = std::vector({Pauli::X, Pauli::Z}); + DensePauliMap pauli_string0{Pauli::X, Pauli::Z}; + DensePauliMap pauli_string1{Pauli::Z, Pauli::I}; + DensePauliMap pauli_string2{Pauli::X, Pauli::Z}; REQUIRE_THROWS_AS( PauliExpCommutingSetBox({ {pauli_string0, 1.0}, @@ -562,12 +575,12 @@ SCENARIO("Pauli gadget commuting sets", "[boxes]") { auto pauli_gadgets = dagger_box->get_pauli_gadgets(); REQUIRE(pauli_gadgets.size() == 3); - REQUIRE(pauli_gadgets[0].first == paulis0); - REQUIRE(pauli_gadgets[0].second == -phase0); - REQUIRE(pauli_gadgets[1].first == paulis1); - REQUIRE(pauli_gadgets[1].second == -phase1); - REQUIRE(pauli_gadgets[2].first == paulis2); - REQUIRE(pauli_gadgets[2].second == -phase2); + REQUIRE(pauli_gadgets[0].string == paulis0); + REQUIRE(pauli_gadgets[0].coeff == -phase0); + REQUIRE(pauli_gadgets[1].string == paulis1); + REQUIRE(pauli_gadgets[1].coeff == -phase1); + REQUIRE(pauli_gadgets[2].string == paulis2); + REQUIRE(pauli_gadgets[2].coeff == -phase2); REQUIRE(dagger_box->get_cx_config() == cx_config); } GIVEN("transpose") { @@ -593,14 +606,14 @@ SCENARIO("Pauli gadget commuting sets", "[boxes]") { auto pauli_gadgets = transpose_box->get_pauli_gadgets(); REQUIRE(pauli_gadgets.size() == 4); - REQUIRE(pauli_gadgets[0].first == paulis0); - REQUIRE(pauli_gadgets[0].second == phase0); - REQUIRE(pauli_gadgets[1].first == paulis1); - REQUIRE(pauli_gadgets[1].second == -phase1); - REQUIRE(pauli_gadgets[2].first == paulis2); - REQUIRE(pauli_gadgets[2].second == phase2); - REQUIRE(pauli_gadgets[3].first == paulis3); - REQUIRE(pauli_gadgets[3].second == -phase3); + REQUIRE(pauli_gadgets[0].string == paulis0); + REQUIRE(pauli_gadgets[0].coeff == phase0); + REQUIRE(pauli_gadgets[1].string == paulis1); + REQUIRE(pauli_gadgets[1].coeff == -phase1); + REQUIRE(pauli_gadgets[2].string == paulis2); + REQUIRE(pauli_gadgets[2].coeff == phase2); + REQUIRE(pauli_gadgets[3].string == paulis3); + REQUIRE(pauli_gadgets[3].coeff == -phase3); REQUIRE(transpose_box->get_cx_config() == cx_config); } GIVEN("symbol_substitution") { @@ -624,34 +637,34 @@ SCENARIO("Pauli gadget commuting sets", "[boxes]") { auto sub_box1 = std::dynamic_pointer_cast( box.symbol_substitution(sub_map1)); auto pauli_gadgets1 = sub_box1->get_pauli_gadgets(); - REQUIRE(pauli_gadgets1[0].second == sub_a); - REQUIRE(pauli_gadgets1[1].second == eb); - REQUIRE(pauli_gadgets1[2].second == ec); + REQUIRE(pauli_gadgets1[0].coeff == sub_a); + REQUIRE(pauli_gadgets1[1].coeff == eb); + REQUIRE(pauli_gadgets1[2].coeff == ec); SymEngine::map_basic_basic sub_map2{std::make_pair(b, sub_b)}; auto sub_box2 = std::dynamic_pointer_cast( box.symbol_substitution(sub_map2)); auto pauli_gadgets2 = sub_box2->get_pauli_gadgets(); - REQUIRE(pauli_gadgets2[0].second == ea); - REQUIRE(pauli_gadgets2[1].second == sub_b); - REQUIRE(pauli_gadgets2[2].second == ec); + REQUIRE(pauli_gadgets2[0].coeff == ea); + REQUIRE(pauli_gadgets2[1].coeff == sub_b); + REQUIRE(pauli_gadgets2[2].coeff == ec); SymEngine::map_basic_basic sub_map3{std::make_pair(c, sub_c)}; auto sub_box3 = std::dynamic_pointer_cast( box.symbol_substitution(sub_map3)); auto pauli_gadgets3 = sub_box3->get_pauli_gadgets(); - REQUIRE(pauli_gadgets3[0].second == ea); - REQUIRE(pauli_gadgets3[1].second == eb); - REQUIRE(pauli_gadgets3[2].second == sub_c); + REQUIRE(pauli_gadgets3[0].coeff == ea); + REQUIRE(pauli_gadgets3[1].coeff == eb); + REQUIRE(pauli_gadgets3[2].coeff == sub_c); sub_map1.merge(sub_map2); sub_map1.merge(sub_map3); auto sub_box4 = std::dynamic_pointer_cast( box.symbol_substitution(sub_map1)); auto pauli_gadgets4 = sub_box4->get_pauli_gadgets(); - REQUIRE(pauli_gadgets4[0].second == sub_a); - REQUIRE(pauli_gadgets4[1].second == sub_b); - REQUIRE(pauli_gadgets4[2].second == sub_c); + REQUIRE(pauli_gadgets4[0].coeff == sub_a); + REQUIRE(pauli_gadgets4[1].coeff == sub_b); + REQUIRE(pauli_gadgets4[2].coeff == sub_c); } } diff --git a/tket/test/src/Ops/test_Ops.cpp b/tket/test/src/Ops/test_Ops.cpp index 6bf741f9d5..786f76cf67 100644 --- a/tket/test/src/Ops/test_Ops.cpp +++ b/tket/test/src/Ops/test_Ops.cpp @@ -242,7 +242,7 @@ SCENARIO("Check op retrieval overloads are working correctly.", "[ops]") { double t = 0.5; // Construct a Pauli box with an even number of Y-gates const PauliExpBox pbox_e( - {Pauli::X, Pauli::Y, Pauli::Z, Pauli::Y, Pauli::X}, t); + SymPauliTensor({Pauli::X, Pauli::Y, Pauli::Z, Pauli::Y, Pauli::X}, t)); const Op_ptr pbox_t_ptr = pbox_e.transpose(); // Casting the PauliExpBox type std::shared_ptr pbox_t = @@ -251,7 +251,8 @@ SCENARIO("Check op retrieval overloads are working correctly.", "[ops]") { REQUIRE(pbox_t->get_phase() == t); // Construct a Pauli box with an odd number of Y-gates - const PauliExpBox pbox_o({Pauli::X, Pauli::Y, Pauli::Z, Pauli::X}, t); + const PauliExpBox pbox_o( + SymPauliTensor({Pauli::X, Pauli::Y, Pauli::Z, Pauli::X}, t)); const Op_ptr pbox_o_t_ptr = pbox_o.transpose(); // Casting the PauliExpBox type std::shared_ptr pbox_o_t = diff --git a/tket/test/src/Simulation/test_PauliExpBoxUnitaryCalculator.cpp b/tket/test/src/Simulation/test_PauliExpBoxUnitaryCalculator.cpp index ec9b126ca3..c4686dc3d9 100644 --- a/tket/test/src/Simulation/test_PauliExpBoxUnitaryCalculator.cpp +++ b/tket/test/src/Simulation/test_PauliExpBoxUnitaryCalculator.cpp @@ -43,7 +43,7 @@ struct GroupPropertyPauliTester { const auto matrix_size = get_matrix_size(pauli_string.size()); for (unsigned ii = 0; ii < phases.size(); ++ii) { - PauliExpBox pe_box(pauli_string, phases[ii]); + PauliExpBox pe_box(SymPauliTensor(pauli_string, phases[ii])); const auto triplets = tket_sim::internal::get_triplets(pe_box); sparse_matrices[ii] = get_sparse_square_matrix(triplets, matrix_size); } @@ -58,7 +58,7 @@ struct GroupPropertyPauliTester { CHECK(dense_matr1.isApprox(dense_matr2)); // Inverse. - PauliExpBox pe_box(pauli_string, -phases[0]); + PauliExpBox pe_box(SymPauliTensor(pauli_string, -phases[0])); const auto triplets = tket_sim::internal::get_triplets(pe_box); sparse_matrices[1] = get_sparse_square_matrix(triplets, matrix_size); dense_matr1 = sparse_matrices[0] * sparse_matrices[1]; @@ -106,7 +106,7 @@ struct DirectTensorProductTester { void test(const std::vector& pauli_string) { const auto matrix_size = get_matrix_size(pauli_string.size()); - PauliExpBox pe_box(pauli_string, phase); + PauliExpBox pe_box(SymPauliTensor(pauli_string, phase)); const auto triplets = tket_sim::internal::get_triplets(pe_box); sparse_matrix = get_sparse_square_matrix(triplets, matrix_size); dense_matrix = sparse_matrix; @@ -181,7 +181,7 @@ struct CompareWithSimulatorPauliTester { REQUIRE(simulator_result.rows() == matr_size); REQUIRE(simulator_result.cols() == matr_size); - PauliExpBox pe_box(paulis, phase[0]); + PauliExpBox pe_box(SymPauliTensor(paulis, phase[0])); const auto triplets = tket_sim::internal::get_triplets(pe_box); result = get_sparse_square_matrix(triplets, matr_size); dense_result = result; diff --git a/tket/test/src/test_Assertion.cpp b/tket/test/src/test_Assertion.cpp index 4ad958f183..8467b2ab62 100644 --- a/tket/test/src/test_Assertion.cpp +++ b/tket/test/src/test_Assertion.cpp @@ -21,7 +21,6 @@ #include "tket/Predicates/CompilationUnit.hpp" #include "tket/Predicates/PassLibrary.hpp" #include "tket/Utils/MatrixAnalysis.hpp" -#include "tket/Utils/PauliStrings.hpp" namespace tket { SCENARIO("Testing projector based assertion synthesis") { @@ -168,10 +167,10 @@ SCENARIO("Testing stabiliser based assertion") { Circuit circ(3); circ.add_op(OpType::Rz, 1.5, {0}); circ.add_op(OpType::CX, {1, 0}); - PauliStabiliser pauli1 = {{Pauli::X, Pauli::X}, true}; - PauliStabiliser pauli2 = {{Pauli::Z, Pauli::Z}, true}; - PauliStabiliser pauli3 = {{Pauli::Z, Pauli::Z}, false}; - PauliStabiliserList stabilisers = {pauli1, pauli2, pauli3}; + PauliStabiliser pauli1 = {{Pauli::X, Pauli::X}, 0}; + PauliStabiliser pauli2 = {{Pauli::Z, Pauli::Z}, 0}; + PauliStabiliser pauli3 = {{Pauli::Z, Pauli::Z}, 2}; + PauliStabiliserVec stabilisers = {pauli1, pauli2, pauli3}; StabiliserAssertionBox box(stabilisers); circ.add_assertion( box, {Qubit(0), Qubit(2)}, Qubit(1), "random stabiliser"); @@ -214,9 +213,9 @@ SCENARIO("Testing stabiliser based assertion") { Circuit circ(3); circ.add_op(OpType::Rz, 1.5, {0}); circ.add_op(OpType::CX, {1, 0}); - PauliStabiliser pauli1 = {{Pauli::X}, true}; - PauliStabiliser pauli2 = {{Pauli::Z}, true}; - PauliStabiliserList stabilisers = {pauli1, pauli2}; + PauliStabiliser pauli1 = {{Pauli::X}, 0}; + PauliStabiliser pauli2 = {{Pauli::Z}, 0}; + PauliStabiliserVec stabilisers = {pauli1, pauli2}; StabiliserAssertionBox box(stabilisers); REQUIRE_THROWS(circ.add_assertion( box, {Qubit(0), Qubit(2)}, Qubit(1), "random stabiliser")); @@ -224,21 +223,21 @@ SCENARIO("Testing stabiliser based assertion") { GIVEN("Invalid input") { WHEN("Empty input") { - PauliStabiliserList stabilisers = {}; + PauliStabiliserVec stabilisers = {}; REQUIRE_THROWS_AS(StabiliserAssertionBox(stabilisers), CircuitInvalidity); } WHEN("Unequal lengths") { - PauliStabiliser pauli1 = {{Pauli::X}, true}; - PauliStabiliser pauli2 = {{Pauli::Z, Pauli::Z}, true}; - PauliStabiliserList stabilisers = {pauli1, pauli2}; + PauliStabiliser pauli1 = {{Pauli::X}, 0}; + PauliStabiliser pauli2 = {{Pauli::Z, Pauli::Z}, 0}; + PauliStabiliserVec stabilisers = {pauli1, pauli2}; REQUIRE_THROWS_AS(StabiliserAssertionBox(stabilisers), CircuitInvalidity); } WHEN("Identity") { REQUIRE_THROWS_AS( - PauliStabiliser({Pauli::I, Pauli::I, Pauli::I}, true), + StabiliserAssertionBox({{{Pauli::I, Pauli::I, Pauli::I}, 0}}), std::invalid_argument); REQUIRE_THROWS_AS( - PauliStabiliser({Pauli::I, Pauli::I, Pauli::I}, false), + StabiliserAssertionBox({{{Pauli::I, Pauli::I, Pauli::I}, 1}}), std::invalid_argument); } } @@ -246,14 +245,14 @@ SCENARIO("Testing stabiliser based assertion") { SCENARIO("Testing stibiliser based assertion serialization") { GIVEN("Serialise a stabiliser box") { - PauliStabiliser pauli1 = {{Pauli::X, Pauli::X}, true}; - PauliStabiliser pauli2 = {{Pauli::Z, Pauli::Z}, true}; + PauliStabiliser pauli1 = {{Pauli::X, Pauli::X}, 0}; + PauliStabiliser pauli2 = {{Pauli::Z, Pauli::Z}, 0}; nlohmann::json j_pauli1 = pauli1; PauliStabiliser new_pauli1 = j_pauli1.get(); REQUIRE(new_pauli1 == pauli1); - PauliStabiliserList bell = {pauli1, pauli2}; + PauliStabiliserVec bell = {pauli1, pauli2}; nlohmann::json j_bell = bell; - PauliStabiliserList new_bell = j_bell.get(); + PauliStabiliserVec new_bell = j_bell.get(); REQUIRE(new_bell == bell); StabiliserAssertionBox bell_box(new_bell); Circuit circ(3); diff --git a/tket/test/src/test_ChoiMixTableau.cpp b/tket/test/src/test_ChoiMixTableau.cpp index f8c10eb5ea..c6b87d72ef 100644 --- a/tket/test/src/test_ChoiMixTableau.cpp +++ b/tket/test/src/test_ChoiMixTableau.cpp @@ -76,20 +76,20 @@ SCENARIO("Correct creation of ChoiMixTableau") { tab.gaussian_form(); REQUIRE( tab.get_row(0) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(0), Pauli::X), - QubitPauliTensor(Qubit(0), Pauli::X)}); + SpPauliStabiliser(Qubit(0), Pauli::X), + SpPauliStabiliser(Qubit(0), Pauli::X)}); REQUIRE( tab.get_row(1) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(0), Pauli::Z), - QubitPauliTensor(Qubit(0), Pauli::Z)}); + SpPauliStabiliser(Qubit(0), Pauli::Z), + SpPauliStabiliser(Qubit(0), Pauli::Z)}); REQUIRE( - tab.get_row(2) == - ChoiMixTableau::row_tensor_t{{}, QubitPauliTensor(Qubit(2), Pauli::Z)}); + tab.get_row(2) == ChoiMixTableau::row_tensor_t{ + {}, SpPauliStabiliser(Qubit(2), Pauli::Z)}); REQUIRE( tab.get_row_product({0, 1}) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(0), Pauli::Y), - QubitPauliTensor(Qubit(0), Pauli::Y, -1.)}); + SpPauliStabiliser(Qubit(0), Pauli::Y), + SpPauliStabiliser(Qubit(0), Pauli::Y, 2)}); THEN("Serialize and deserialize") { nlohmann::json j_tab = tab; ChoiMixTableau tab2{{}}; @@ -112,46 +112,46 @@ SCENARIO("Correct creation of ChoiMixTableau") { // e^{-i Z pi/4} X id X = (-iZX) e^{-i Z pi/4} X = +Y S X REQUIRE( tab.get_row(0) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(0), Pauli::X), - QubitPauliTensor(Qubit(0), Pauli::Y)}); + SpPauliStabiliser(Qubit(0), Pauli::X), + SpPauliStabiliser(Qubit(0), Pauli::Y)}); REQUIRE( tab.get_row(1) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(0), Pauli::Z), - QubitPauliTensor(Qubit(0), Pauli::Z)}); + SpPauliStabiliser(Qubit(0), Pauli::Z), + SpPauliStabiliser(Qubit(0), Pauli::Z)}); REQUIRE( - tab.get_row(2) == - ChoiMixTableau::row_tensor_t{QubitPauliTensor(Qubit(1), Pauli::Z), {}}); + tab.get_row(2) == ChoiMixTableau::row_tensor_t{ + SpPauliStabiliser(Qubit(1), Pauli::Z), {}}); REQUIRE( - tab.get_row(3) == - ChoiMixTableau::row_tensor_t{{}, QubitPauliTensor(Qubit(2), Pauli::Z)}); + tab.get_row(3) == ChoiMixTableau::row_tensor_t{ + {}, SpPauliStabiliser(Qubit(2), Pauli::Z)}); // Applying an S at the input end adds up to a net Z tab.apply_S(Qubit(0), ChoiMixTableau::TableauSegment::Input); tab.canonical_column_order(); tab.gaussian_form(); REQUIRE( tab.get_row(0) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(0), Pauli::X), - QubitPauliTensor(Qubit(0), Pauli::X, -1.)}); + SpPauliStabiliser(Qubit(0), Pauli::X), + SpPauliStabiliser(Qubit(0), Pauli::X, 2)}); REQUIRE( tab.get_row(1) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(0), Pauli::Z), - QubitPauliTensor(Qubit(0), Pauli::Z)}); + SpPauliStabiliser(Qubit(0), Pauli::Z), + SpPauliStabiliser(Qubit(0), Pauli::Z)}); REQUIRE( - tab.get_row(2) == - ChoiMixTableau::row_tensor_t{QubitPauliTensor(Qubit(1), Pauli::Z), {}}); + tab.get_row(2) == ChoiMixTableau::row_tensor_t{ + SpPauliStabiliser(Qubit(1), Pauli::Z), {}}); REQUIRE( - tab.get_row(3) == - ChoiMixTableau::row_tensor_t{{}, QubitPauliTensor(Qubit(2), Pauli::Z)}); + tab.get_row(3) == ChoiMixTableau::row_tensor_t{ + {}, SpPauliStabiliser(Qubit(2), Pauli::Z)}); THEN("Compare to explicitly generated tableau") { std::list rows; rows.push_back( - {QubitPauliTensor(Qubit(0), Pauli::X), - QubitPauliTensor(Qubit(0), Pauli::X, -1.)}); + {SpPauliStabiliser(Qubit(0), Pauli::X), + SpPauliStabiliser(Qubit(0), Pauli::X, 2)}); rows.push_back( - {QubitPauliTensor(Qubit(0), Pauli::Z), - QubitPauliTensor(Qubit(0), Pauli::Z)}); - rows.push_back({QubitPauliTensor(Qubit(1), Pauli::Z), {}}); - rows.push_back({{}, QubitPauliTensor(Qubit(2), Pauli::Z)}); + {SpPauliStabiliser(Qubit(0), Pauli::Z), + SpPauliStabiliser(Qubit(0), Pauli::Z)}); + rows.push_back({SpPauliStabiliser(Qubit(1), Pauli::Z), {}}); + rows.push_back({{}, SpPauliStabiliser(Qubit(2), Pauli::Z)}); ChoiMixTableau tab2(rows); tab2.canonical_column_order(); REQUIRE(tab == tab2); @@ -167,60 +167,60 @@ SCENARIO("Correct creation of ChoiMixTableau") { tab.gaussian_form(); REQUIRE( tab.get_row(0) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(0), Pauli::X), - QubitPauliTensor(Qubit(0), Pauli::X)}); + SpPauliStabiliser(Qubit(0), Pauli::X), + SpPauliStabiliser(Qubit(0), Pauli::X)}); REQUIRE( tab.get_row(1) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(0), Pauli::Z), - QubitPauliTensor(Qubit(0), Pauli::Z)}); + SpPauliStabiliser(Qubit(0), Pauli::Z), + SpPauliStabiliser(Qubit(0), Pauli::Z)}); // Affecting the input segment should give the same effect as for // UnitaryRevTableau (since lhs is transposed, +Y is flipped to -Y, and // phase is returned on rhs) REQUIRE( tab.get_row(2) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(1), Pauli::Y), QubitPauliTensor(-1.)}); + SpPauliStabiliser(Qubit(1), Pauli::Y), SpPauliStabiliser({}, 2)}); // Affecting the output segment should give the same effect as for // UnitaryTableau REQUIRE( tab.get_row(3) == ChoiMixTableau::row_tensor_t{ - {}, QubitPauliTensor(Qubit(2), Pauli::Y, -1.)}); + {}, SpPauliStabiliser(Qubit(2), Pauli::Y, 2)}); // Check V on identity tab.apply_V(Qubit(0), ChoiMixTableau::TableauSegment::Output); REQUIRE( tab.get_row(0) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(0), Pauli::X), - QubitPauliTensor(Qubit(0), Pauli::X)}); + SpPauliStabiliser(Qubit(0), Pauli::X), + SpPauliStabiliser(Qubit(0), Pauli::X)}); // e^{-i X pi/4} Z C Z = (-iXZ) e^{-i X pi/4} C Z = -Y V C Z REQUIRE( tab.get_row(1) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(0), Pauli::Z), - QubitPauliTensor(Qubit(0), Pauli::Y, -1.)}); + SpPauliStabiliser(Qubit(0), Pauli::Z), + SpPauliStabiliser(Qubit(0), Pauli::Y, 2)}); REQUIRE( tab.get_row(2) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(1), Pauli::Y), QubitPauliTensor(-1.)}); + SpPauliStabiliser(Qubit(1), Pauli::Y), SpPauliStabiliser({}, 2)}); REQUIRE( tab.get_row(3) == ChoiMixTableau::row_tensor_t{ - {}, QubitPauliTensor(Qubit(2), Pauli::Y, -1.)}); + {}, SpPauliStabiliser(Qubit(2), Pauli::Y, 2)}); // Applying a V at the input end adds up to a net X tab.apply_V(Qubit(0), ChoiMixTableau::TableauSegment::Input); tab.gaussian_form(); REQUIRE( tab.get_row(0) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(0), Pauli::X), - QubitPauliTensor(Qubit(0), Pauli::X)}); + SpPauliStabiliser(Qubit(0), Pauli::X), + SpPauliStabiliser(Qubit(0), Pauli::X)}); REQUIRE( tab.get_row(1) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(0), Pauli::Z), - QubitPauliTensor(Qubit(0), Pauli::Z, -1.)}); + SpPauliStabiliser(Qubit(0), Pauli::Z), + SpPauliStabiliser(Qubit(0), Pauli::Z, 2)}); REQUIRE( tab.get_row(2) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(1), Pauli::Y), QubitPauliTensor(-1.)}); + SpPauliStabiliser(Qubit(1), Pauli::Y), SpPauliStabiliser({}, 2)}); REQUIRE( tab.get_row(3) == ChoiMixTableau::row_tensor_t{ - {}, QubitPauliTensor(Qubit(2), Pauli::Y, -1.)}); + {}, SpPauliStabiliser(Qubit(2), Pauli::Y, 2)}); } GIVEN("Applying CX gates") { ChoiMixTableau tab(4); @@ -237,27 +237,27 @@ SCENARIO("Correct creation of ChoiMixTableau") { REQUIRE( tab.get_row(0) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(0), Pauli::X), - QubitPauliTensor({{Qubit(0), Pauli::X}, {Qubit(1), Pauli::X}})}); + SpPauliStabiliser(Qubit(0), Pauli::X), + SpPauliStabiliser({{Qubit(0), Pauli::X}, {Qubit(1), Pauli::X}})}); REQUIRE( tab.get_row(1) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(0), Pauli::Z), - QubitPauliTensor(Qubit(0), Pauli::Z)}); + SpPauliStabiliser(Qubit(0), Pauli::Z), + SpPauliStabiliser(Qubit(0), Pauli::Z)}); REQUIRE( tab.get_row(2) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(1), Pauli::X), - QubitPauliTensor(Qubit(1), Pauli::X)}); + SpPauliStabiliser(Qubit(1), Pauli::X), + SpPauliStabiliser(Qubit(1), Pauli::X)}); REQUIRE( tab.get_row(3) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(1), Pauli::Z), - QubitPauliTensor({{Qubit(0), Pauli::Z}, {Qubit(1), Pauli::Z}})}); + SpPauliStabiliser(Qubit(1), Pauli::Z), + SpPauliStabiliser({{Qubit(0), Pauli::Z}, {Qubit(1), Pauli::Z}})}); REQUIRE( - tab.get_row(4) == - ChoiMixTableau::row_tensor_t{QubitPauliTensor(Qubit(2), Pauli::Z), {}}); + tab.get_row(4) == ChoiMixTableau::row_tensor_t{ + SpPauliStabiliser(Qubit(2), Pauli::Z), {}}); REQUIRE( - tab.get_row(5) == - ChoiMixTableau::row_tensor_t{{}, QubitPauliTensor(Qubit(3), Pauli::Z)}); + tab.get_row(5) == ChoiMixTableau::row_tensor_t{ + {}, SpPauliStabiliser(Qubit(3), Pauli::Z)}); // CX on input cancels back to original tab.apply_CX(Qubit(0), Qubit(1), ChoiMixTableau::TableauSegment::Input); tab.gaussian_form(); @@ -269,30 +269,30 @@ SCENARIO("Correct creation of ChoiMixTableau") { REQUIRE( tab.get_row(0) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor({{Qubit(0), Pauli::X}, {Qubit(2), Pauli::X}}), - QubitPauliTensor(Qubit(0), Pauli::X)}); + SpPauliStabiliser({{Qubit(0), Pauli::X}, {Qubit(2), Pauli::X}}), + SpPauliStabiliser(Qubit(0), Pauli::X)}); REQUIRE( tab.get_row(1) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(0), Pauli::Z), - QubitPauliTensor(Qubit(0), Pauli::Z)}); + SpPauliStabiliser(Qubit(0), Pauli::Z), + SpPauliStabiliser(Qubit(0), Pauli::Z)}); REQUIRE( tab.get_row(2) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(1), Pauli::X), - QubitPauliTensor({{Qubit(1), Pauli::X}, {Qubit(3), Pauli::X}})}); + SpPauliStabiliser(Qubit(1), Pauli::X), + SpPauliStabiliser({{Qubit(1), Pauli::X}, {Qubit(3), Pauli::X}})}); REQUIRE( tab.get_row(3) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(1), Pauli::Z), - QubitPauliTensor(Qubit(1), Pauli::Z)}); + SpPauliStabiliser(Qubit(1), Pauli::Z), + SpPauliStabiliser(Qubit(1), Pauli::Z)}); REQUIRE( tab.get_row(4) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(2), Pauli::Z), - QubitPauliTensor(Qubit(0), Pauli::Z)}); + SpPauliStabiliser(Qubit(2), Pauli::Z), + SpPauliStabiliser(Qubit(0), Pauli::Z)}); REQUIRE( tab.get_row(5) == ChoiMixTableau::row_tensor_t{ {}, - QubitPauliTensor({{Qubit(1), Pauli::Z}, {Qubit(3), Pauli::Z}})}); + SpPauliStabiliser({{Qubit(1), Pauli::Z}, {Qubit(3), Pauli::Z}})}); } GIVEN("A full circuit") { Circuit circ = get_test_circ(); @@ -305,7 +305,7 @@ SCENARIO("Correct creation of ChoiMixTableau") { GIVEN("A PI/2 rotation at end") { Circuit circ = get_test_circ(); ChoiMixTableau tab = circuit_to_cm_tableau(circ); - QubitPauliTensor pauli{ + SpPauliStabiliser pauli{ {{Qubit(0), Pauli::X}, {Qubit(1), Pauli::Y}, {Qubit(2), Pauli::Z}}}; tab.apply_pauli(pauli, 3); tab.gaussian_form(); @@ -317,10 +317,7 @@ SCENARIO("Correct creation of ChoiMixTableau") { } GIVEN("A PI/2 rotation at front") { ChoiMixTableau tab = get_tableau_with_gates_applied_at_front(); - QubitPauliTensor pauli = - QubitPauliTensor(Qubit(q_default_reg(), 0), Pauli::X) * - QubitPauliTensor(Qubit(q_default_reg(), 1), Pauli::Y) * - QubitPauliTensor(Qubit(q_default_reg(), 2), Pauli::Z); + SpPauliStabiliser pauli({Pauli::X, Pauli::Y, Pauli::Z}); tab.apply_pauli(pauli, 1, ChoiMixTableau::TableauSegment::Input); tab.gaussian_form(); @@ -370,26 +367,26 @@ SCENARIO("Correct creation of ChoiMixTableau") { REQUIRE( tab.get_row(0) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(1), Pauli::X), - QubitPauliTensor( - {{Qubit(0), Pauli::X}, {Qubit(2), Pauli::X}}, -1.)}); + SpPauliStabiliser(Qubit(1), Pauli::X), + SpPauliStabiliser( + {{Qubit(0), Pauli::X}, {Qubit(2), Pauli::X}}, 2)}); REQUIRE( tab.get_row(1) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(1), Pauli::Z), - QubitPauliTensor(Qubit(0), Pauli::Z, -1.)}); + SpPauliStabiliser(Qubit(1), Pauli::Z), + SpPauliStabiliser(Qubit(0), Pauli::Z, 2)}); REQUIRE( tab.get_row(2) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(2), Pauli::X), - QubitPauliTensor(Qubit(2), Pauli::X)}); + SpPauliStabiliser(Qubit(2), Pauli::X), + SpPauliStabiliser(Qubit(2), Pauli::X)}); REQUIRE( tab.get_row(3) == ChoiMixTableau::row_tensor_t{ - QubitPauliTensor(Qubit(2), Pauli::Z), - QubitPauliTensor( - {{Qubit(0), Pauli::Z}, {Qubit(2), Pauli::Z}}, -1.)}); + SpPauliStabiliser(Qubit(2), Pauli::Z), + SpPauliStabiliser( + {{Qubit(0), Pauli::Z}, {Qubit(2), Pauli::Z}}, 2)}); REQUIRE( - tab.get_row(4) == - ChoiMixTableau::row_tensor_t{{}, QubitPauliTensor(Qubit(1), Pauli::Z)}); + tab.get_row(4) == ChoiMixTableau::row_tensor_t{ + {}, SpPauliStabiliser(Qubit(1), Pauli::Z)}); } GIVEN("Combining post-selections and discarding") { ChoiMixTableau tab(5); diff --git a/tket/test/src/test_Clifford.cpp b/tket/test/src/test_Clifford.cpp index cb76b9a4d1..cb1d332400 100644 --- a/tket/test/src/test_Clifford.cpp +++ b/tket/test/src/test_Clifford.cpp @@ -27,7 +27,6 @@ #include "tket/Transformations/OptimisationPass.hpp" #include "tket/Transformations/Rebase.hpp" #include "tket/Transformations/Transform.hpp" -#include "tket/Utils/PauliStrings.hpp" namespace tket { namespace test_Clifford { diff --git a/tket/test/src/test_CompilerPass.cpp b/tket/test/src/test_CompilerPass.cpp index ef546dfad5..dc1540613c 100644 --- a/tket/test/src/test_CompilerPass.cpp +++ b/tket/test/src/test_CompilerPass.cpp @@ -1171,9 +1171,9 @@ SCENARIO("Test Pauli Graph Synthesis Pass") { Transforms::PauliSynthStrat::Sets, CXConfigType::Star); GIVEN("Two PauliExpBoxes") { Circuit circ(3, "test"); - PauliExpBox peb({Pauli::Z, Pauli::X, Pauli::Z}, 0.333); + PauliExpBox peb({{Pauli::Z, Pauli::X, Pauli::Z}, 0.333}); circ.add_box(peb, {0, 1, 2}); - PauliExpBox peb2({Pauli::Y, Pauli::X, Pauli::X}, 0.174); + PauliExpBox peb2({{Pauli::Y, Pauli::X, Pauli::X}, 0.174}); circ.add_box(peb2, {0, 1, 2}); CompilationUnit cu(circ); diff --git a/tket/test/src/test_MeasurementReduction.cpp b/tket/test/src/test_MeasurementReduction.cpp index 70f1e1f3cb..fa05084ef2 100644 --- a/tket/test/src/test_MeasurementReduction.cpp +++ b/tket/test/src/test_MeasurementReduction.cpp @@ -21,11 +21,11 @@ namespace test_MeasurementReduction { SCENARIO("Some QubitOperators") { GIVEN("4 string QubitOperator") { - QubitPauliString qp_map0(Qubit(0), Pauli::I); - QubitPauliString qp_map1(Qubit(0), Pauli::X); - QubitPauliString qp_map2(Qubit(0), Pauli::Y); - QubitPauliString qp_map3(Qubit(0), Pauli::Z); - std::list pts{qp_map0, qp_map1, qp_map2, qp_map3}; + SpPauliString qp_map0(Qubit(0), Pauli::I); + SpPauliString qp_map1(Qubit(0), Pauli::X); + SpPauliString qp_map2(Qubit(0), Pauli::Y); + SpPauliString qp_map3(Qubit(0), Pauli::Z); + std::list pts{qp_map0, qp_map1, qp_map2, qp_map3}; WHEN("Commuting sets") { MeasurementSetup measurements = @@ -44,15 +44,15 @@ SCENARIO("Some QubitOperators") { } } GIVEN("7 string QubitOperator") { - QubitPauliString qp_map0(Qubit(0), Pauli::Z); - QubitPauliString qp_map1(Qubit(1), Pauli::Z); - QubitPauliString qp_map2(Qubit(2), Pauli::Z); - QubitPauliString qp_map3(Qubit(3), Pauli::Z); - QubitPauliString qp_map4({Pauli::Z, Pauli::Z, Pauli::Z, Pauli::Z}); - QubitPauliString qp_map5({Pauli::X, Pauli::X, Pauli::Y, Pauli::Y}); - QubitPauliString qp_map6({Pauli::Y, Pauli::Y, Pauli::X, Pauli::X}); - std::list pts{qp_map0, qp_map1, qp_map2, qp_map3, - qp_map4, qp_map5, qp_map6}; + SpPauliString qp_map0(Qubit(0), Pauli::Z); + SpPauliString qp_map1(Qubit(1), Pauli::Z); + SpPauliString qp_map2(Qubit(2), Pauli::Z); + SpPauliString qp_map3(Qubit(3), Pauli::Z); + SpPauliString qp_map4({Pauli::Z, Pauli::Z, Pauli::Z, Pauli::Z}); + SpPauliString qp_map5({Pauli::X, Pauli::X, Pauli::Y, Pauli::Y}); + SpPauliString qp_map6({Pauli::Y, Pauli::Y, Pauli::X, Pauli::X}); + std::list pts{qp_map0, qp_map1, qp_map2, qp_map3, + qp_map4, qp_map5, qp_map6}; WHEN("Commuting sets") { MeasurementSetup measurements = @@ -68,17 +68,17 @@ SCENARIO("Some QubitOperators") { } } GIVEN("8 strings over 4 qubits") { - QubitPauliString qp_map0({Pauli::X, Pauli::X, Pauli::X, Pauli::Y}); - QubitPauliString qp_map1({Pauli::X, Pauli::X, Pauli::Y, Pauli::X}); - QubitPauliString qp_map2({Pauli::X, Pauli::Y, Pauli::X, Pauli::X}); - QubitPauliString qp_map3({Pauli::X, Pauli::Y, Pauli::Y, Pauli::Y}); - QubitPauliString qp_map4({Pauli::Y, Pauli::X, Pauli::X, Pauli::X}); - QubitPauliString qp_map5({Pauli::Y, Pauli::X, Pauli::Y, Pauli::Y}); - QubitPauliString qp_map6({Pauli::Y, Pauli::Y, Pauli::X, Pauli::Y}); - QubitPauliString qp_map7({Pauli::Y, Pauli::Y, Pauli::Y, Pauli::X}); - QubitPauliString qp_map8({Pauli::I, Pauli::I, Pauli::I, Pauli::I}); - std::list pts{qp_map0, qp_map1, qp_map2, qp_map3, qp_map4, - qp_map5, qp_map6, qp_map7, qp_map8}; + SpPauliString qp_map0({Pauli::X, Pauli::X, Pauli::X, Pauli::Y}); + SpPauliString qp_map1({Pauli::X, Pauli::X, Pauli::Y, Pauli::X}); + SpPauliString qp_map2({Pauli::X, Pauli::Y, Pauli::X, Pauli::X}); + SpPauliString qp_map3({Pauli::X, Pauli::Y, Pauli::Y, Pauli::Y}); + SpPauliString qp_map4({Pauli::Y, Pauli::X, Pauli::X, Pauli::X}); + SpPauliString qp_map5({Pauli::Y, Pauli::X, Pauli::Y, Pauli::Y}); + SpPauliString qp_map6({Pauli::Y, Pauli::Y, Pauli::X, Pauli::Y}); + SpPauliString qp_map7({Pauli::Y, Pauli::Y, Pauli::Y, Pauli::X}); + SpPauliString qp_map8({Pauli::I, Pauli::I, Pauli::I, Pauli::I}); + std::list pts{qp_map0, qp_map1, qp_map2, qp_map3, qp_map4, + qp_map5, qp_map6, qp_map7, qp_map8}; WHEN("Commuting sets") { MeasurementSetup measurements = measurement_reduction(pts, PauliPartitionStrat::CommutingSets); diff --git a/tket/test/src/test_MeasurementSetup.cpp b/tket/test/src/test_MeasurementSetup.cpp index 4312bddaee..6bfc5785ed 100644 --- a/tket/test/src/test_MeasurementSetup.cpp +++ b/tket/test/src/test_MeasurementSetup.cpp @@ -33,10 +33,10 @@ SCENARIO("verify_measurement_setup") { ms.add_measurement_circuit(mc); Qubit q0(q_default_reg(), 0); Qubit q1(q_default_reg(), 1); - QubitPauliString ii; - QubitPauliString zi({{q0, Pauli::Z}}); - QubitPauliString iz({{q1, Pauli::Z}}); - QubitPauliString zz({{q0, Pauli::Z}, {q1, Pauli::Z}}); + SpPauliString ii; + SpPauliString zi({{q0, Pauli::Z}}); + SpPauliString iz({{q1, Pauli::Z}}); + SpPauliString zz({{q0, Pauli::Z}, {q1, Pauli::Z}}); ms.add_result_for_term(ii, {0, {}, false}); ms.add_result_for_term(zi, {0, {0}, false}); ms.add_result_for_term(iz, {0, {1}, false}); @@ -56,7 +56,7 @@ SCENARIO("verify_measurement_setup") { ms.add_measurement_circuit(mc1); Qubit q0(q_default_reg(), 0); Qubit q1(q_default_reg(), 1); - QubitPauliString zi({{q0, Pauli::Z}}); + SpPauliString zi({{q0, Pauli::Z}}); ms.add_result_for_term(zi, {0, {0}, false}); ms.add_result_for_term(zi, {1, {0}, false}); REQUIRE(ms.verify()); @@ -68,7 +68,7 @@ SCENARIO("verify_measurement_setup") { mc.add_measure(0, 0); ms.add_measurement_circuit(mc); Qubit q0(q_default_reg(), 0); - QubitPauliString z({{q0, Pauli::Z}}); + SpPauliString z({{q0, Pauli::Z}}); ms.add_result_for_term(z, {0, {0}, true}); REQUIRE(ms.verify()); } @@ -83,17 +83,17 @@ SCENARIO("verify_measurement_setup") { Qubit q0(q_default_reg(), 0); Qubit q1(q_default_reg(), 1); WHEN("Wrong parity") { - QubitPauliString zi({{q0, Pauli::Z}}); + SpPauliString zi({{q0, Pauli::Z}}); ms.add_result_for_term(zi, {0, {0}, false}); REQUIRE_FALSE(ms.verify()); } WHEN("Wrong string") { - QubitPauliString ix({{q1, Pauli::X}}); + SpPauliString ix({{q1, Pauli::X}}); ms.add_result_for_term(ix, {0, {1}, false}); REQUIRE_FALSE(ms.verify()); } WHEN("Wrong bit set") { - QubitPauliString iy({{q1, Pauli::Y}}); + SpPauliString iy({{q1, Pauli::Y}}); ms.add_result_for_term(iy, {0, {0, 1}, false}); REQUIRE_FALSE(ms.verify()); } @@ -149,18 +149,18 @@ SCENARIO("verify_measurement_setup") { Qubit q2(q_default_reg(), 2); Qubit q3(q_default_reg(), 3); - QubitPauliTensor x0(q0, Pauli::X); - QubitPauliTensor y0(q0, Pauli::Y); - QubitPauliTensor z0(q0, Pauli::Z); - QubitPauliTensor x1(q1, Pauli::X); - QubitPauliTensor y1(q1, Pauli::Y); - QubitPauliTensor z1(q1, Pauli::Z); - QubitPauliTensor x2(q2, Pauli::X); - QubitPauliTensor y2(q2, Pauli::Y); - QubitPauliTensor z2(q2, Pauli::Z); - QubitPauliTensor x3(q3, Pauli::X); - QubitPauliTensor y3(q3, Pauli::Y); - QubitPauliTensor z3(q3, Pauli::Z); + SpPauliString x0(q0, Pauli::X); + SpPauliString y0(q0, Pauli::Y); + SpPauliString z0(q0, Pauli::Z); + SpPauliString x1(q1, Pauli::X); + SpPauliString y1(q1, Pauli::Y); + SpPauliString z1(q1, Pauli::Z); + SpPauliString x2(q2, Pauli::X); + SpPauliString y2(q2, Pauli::Y); + SpPauliString z2(q2, Pauli::Z); + SpPauliString x3(q3, Pauli::X); + SpPauliString y3(q3, Pauli::Y); + SpPauliString z3(q3, Pauli::Z); ms.add_result_for_term(z0, {1, {0}, false}); ms.add_result_for_term(z0 * z1, {1, {0, 1}, false}); @@ -289,24 +289,24 @@ SCENARIO("verify_measurement_setup") { Qubit q4(q_default_reg(), 4); Qubit q5(q_default_reg(), 5); - QubitPauliTensor x0(q0, Pauli::X); - QubitPauliTensor y0(q0, Pauli::Y); - QubitPauliTensor z0(q0, Pauli::Z); - QubitPauliTensor x1(q1, Pauli::X); - QubitPauliTensor y1(q1, Pauli::Y); - QubitPauliTensor z1(q1, Pauli::Z); - QubitPauliTensor x2(q2, Pauli::X); - QubitPauliTensor y2(q2, Pauli::Y); - QubitPauliTensor z2(q2, Pauli::Z); - QubitPauliTensor x3(q3, Pauli::X); - QubitPauliTensor y3(q3, Pauli::Y); - QubitPauliTensor z3(q3, Pauli::Z); - QubitPauliTensor x4(q4, Pauli::X); - QubitPauliTensor y4(q4, Pauli::Y); - QubitPauliTensor z4(q4, Pauli::Z); - QubitPauliTensor x5(q5, Pauli::X); - QubitPauliTensor y5(q5, Pauli::Y); - QubitPauliTensor z5(q5, Pauli::Z); + SpPauliString x0(q0, Pauli::X); + SpPauliString y0(q0, Pauli::Y); + SpPauliString z0(q0, Pauli::Z); + SpPauliString x1(q1, Pauli::X); + SpPauliString y1(q1, Pauli::Y); + SpPauliString z1(q1, Pauli::Z); + SpPauliString x2(q2, Pauli::X); + SpPauliString y2(q2, Pauli::Y); + SpPauliString z2(q2, Pauli::Z); + SpPauliString x3(q3, Pauli::X); + SpPauliString y3(q3, Pauli::Y); + SpPauliString z3(q3, Pauli::Z); + SpPauliString x4(q4, Pauli::X); + SpPauliString y4(q4, Pauli::Y); + SpPauliString z4(q4, Pauli::Z); + SpPauliString x5(q5, Pauli::X); + SpPauliString y5(q5, Pauli::Y); + SpPauliString z5(q5, Pauli::Z); ms.add_result_for_term(y0 * z1 * y2 * z3, {0, {0, 1, 3}, false}); ms.add_result_for_term( diff --git a/tket/test/src/test_Partition.cpp b/tket/test/src/test_Partition.cpp index 62894ff408..c33e7c5a93 100644 --- a/tket/test/src/test_Partition.cpp +++ b/tket/test/src/test_Partition.cpp @@ -36,8 +36,8 @@ SCENARIO("Small sets of Gadgets are partitioned correctly") { GIVEN("No gadgets") { for (auto colouring_method : colouring_methods) { for (PauliPartitionStrat strat : strats) { - std::list tensors; - std::list> void_terms = + std::list tensors; + std::list> void_terms = term_sequence(tensors, strat, colouring_method); REQUIRE(void_terms.empty()); } @@ -49,13 +49,13 @@ SCENARIO("Small sets of Gadgets are partitioned correctly") { Qubit q0(0); Qubit q1(1); Qubit q2(2); - QubitPauliString qp_map0({{q0, Pauli::I}, {q1, Pauli::X}, {q2, Pauli::Y}}); - QubitPauliString qp_map1({{q0, Pauli::Z}, {q1, Pauli::Z}, {q2, Pauli::Y}}); - std::list tensors{qp_map0, qp_map1}; + SpPauliString qp_map0({{q0, Pauli::I}, {q1, Pauli::X}, {q2, Pauli::Y}}); + SpPauliString qp_map1({{q0, Pauli::Z}, {q1, Pauli::Z}, {q2, Pauli::Y}}); + std::list tensors{qp_map0, qp_map1}; for (auto colouring_method : colouring_methods) { for (PauliPartitionStrat strat : strats) { - std::list> terms = + std::list> terms = term_sequence(tensors, strat, colouring_method); REQUIRE(terms.size() == 2); @@ -67,20 +67,20 @@ SCENARIO("Small sets of Gadgets are partitioned correctly") { } } GIVEN("Three partitions of four gadgets") { - QubitPauliString qp_map0(Qubit(0), Pauli::I); - QubitPauliString qp_map1(Qubit(0), Pauli::X); - QubitPauliString qp_map2(Qubit(0), Pauli::Y); - QubitPauliString qp_map3(Qubit(0), Pauli::Z); - std::list tensors{qp_map0, qp_map1, qp_map2, qp_map3}; + SpPauliString qp_map0(Qubit(0), Pauli::I); + SpPauliString qp_map1(Qubit(0), Pauli::X); + SpPauliString qp_map2(Qubit(0), Pauli::Y); + SpPauliString qp_map3(Qubit(0), Pauli::Z); + std::list tensors{qp_map0, qp_map1, qp_map2, qp_map3}; for (auto colouring_method : colouring_methods) { for (PauliPartitionStrat strat : strats) { - std::list> terms = + std::list> terms = term_sequence(tensors, strat, colouring_method); REQUIRE(terms.size() == 3); unsigned total_terms = 0; - for (const std::list& g_map : terms) { + for (const std::list& g_map : terms) { REQUIRE((g_map.size() == 1 || g_map.size() == 2)); total_terms += g_map.size(); } diff --git a/tket/test/src/test_PauliGraph.cpp b/tket/test/src/test_PauliGraph.cpp index 01fbd61f2e..c8d4758dc4 100644 --- a/tket/test/src/test_PauliGraph.cpp +++ b/tket/test/src/test_PauliGraph.cpp @@ -207,7 +207,7 @@ SCENARIO("Correct creation of PauliGraphs") { circ.add_op(OpType::ZZPhase, 0.2, {0, 1}); circ.add_op(OpType::Vdg, {0}); circ.add_op(OpType::H, {1}); - PauliExpBox peb({Pauli::Y, Pauli::X}, 0.333); + PauliExpBox peb({{Pauli::Y, Pauli::X}, 0.333}); circ.add_box(peb, {0, 1}); PauliGraph pg = circuit_to_pauli_graph(circ); REQUIRE(pg.n_vertices() == 1); @@ -292,7 +292,7 @@ static void add_ops_to_prepend_2(Circuit& circ) { SCENARIO("Test mutual diagonalisation of fully commuting sets") { GIVEN("A 2 qb Identity gadget") { Circuit circ(2); - PauliExpBox peb({Pauli::I, Pauli::I}, 0.333); + PauliExpBox peb({{Pauli::I, Pauli::I}, 0.333}); circ.add_box(peb, {0, 1}); const auto& prepend = CircuitsForTesting::get().prepend_2qb_circuit; Circuit test1 = prepend >> circ; @@ -308,7 +308,7 @@ SCENARIO("Test mutual diagonalisation of fully commuting sets") { GIVEN("2 qb 1 Pauli gadget circuit") { const auto& prepend = CircuitsForTesting::get().prepend_2qb_circuit; Circuit circ(2); - PauliExpBox peb({Pauli::Z, Pauli::X}, 0.333); + PauliExpBox peb({{Pauli::Z, Pauli::X}, 0.333}); circ.add_box(peb, {0, 1}); Circuit test1 = prepend >> circ; @@ -321,9 +321,9 @@ SCENARIO("Test mutual diagonalisation of fully commuting sets") { GIVEN("2 qb 2 Pauli gadget circuit") { const auto& prepend = CircuitsForTesting::get().prepend_2qb_circuit; Circuit circ(2); - PauliExpBox peb({Pauli::Z, Pauli::X}, 0.333); + PauliExpBox peb({{Pauli::Z, Pauli::X}, 0.333}); circ.add_box(peb, {0, 1}); - PauliExpBox peb2({Pauli::Y, Pauli::Y}, 0.174); + PauliExpBox peb2({{Pauli::Y, Pauli::Y}, 0.174}); circ.add_box(peb2, {0, 1}); Circuit test1 = prepend >> circ; @@ -345,11 +345,11 @@ SCENARIO("Test mutual diagonalisation of fully commuting sets") { std::map symbol_map = { {a, 0.3112}, {b, 1.178}, {c, -0.911}}; - PauliExpBox peb({Pauli::Z, Pauli::Z}, ea); + PauliExpBox peb({{Pauli::Z, Pauli::Z}, ea}); circ.add_box(peb, {0, 1}); - PauliExpBox peb2({Pauli::X, Pauli::X}, eb); + PauliExpBox peb2({{Pauli::X, Pauli::X}, eb}); circ.add_box(peb2, {0, 1}); - PauliExpBox peb3({Pauli::Y, Pauli::Y}, ec); + PauliExpBox peb3({{Pauli::Y, Pauli::Y}, ec}); circ.add_box(peb3, {0, 1}); Circuit test1 = prepend >> circ; test1.symbol_substitution(symbol_map); @@ -373,11 +373,11 @@ SCENARIO("Test mutual diagonalisation of fully commuting sets") { std::map symbol_map = { {a, 0.3112}, {b, 1.178}, {c, -0.911}}; - PauliExpBox peb({Pauli::Z, Pauli::Z}, ea); + PauliExpBox peb({{Pauli::Z, Pauli::Z}, ea}); circ.add_box(peb, {0, 1}); - PauliExpBox peb2({Pauli::I, Pauli::X}, eb); + PauliExpBox peb2({{Pauli::I, Pauli::X}, eb}); circ.add_box(peb2, {0, 1}); - PauliExpBox peb3({Pauli::Y, Pauli::I}, ec); + PauliExpBox peb3({{Pauli::Y, Pauli::I}, ec}); circ.add_box(peb3, {0, 1}); Circuit test1 = prepend >> circ; REQUIRE(test1.is_symbolic()); @@ -396,9 +396,9 @@ SCENARIO("Test mutual diagonalisation of fully commuting sets") { add_ops_to_prepend_1(prepend); Circuit circ(3); - PauliExpBox peb({Pauli::Z, Pauli::X, Pauli::Z}, 0.333); + PauliExpBox peb({{Pauli::Z, Pauli::X, Pauli::Z}, 0.333}); circ.add_box(peb, {0, 1, 2}); - PauliExpBox peb2({Pauli::Y, Pauli::X, Pauli::X}, 0.174); + PauliExpBox peb2({{Pauli::Y, Pauli::X, Pauli::X}, 0.174}); circ.add_box(peb2, {0, 1, 2}); Circuit test1 = prepend >> circ; PauliGraph pg = circuit_to_pauli_graph(circ); @@ -412,11 +412,11 @@ SCENARIO("Test mutual diagonalisation of fully commuting sets") { add_ops_to_prepend_1(prepend); Circuit circ(4); - PauliExpBox peb({Pauli::Z, Pauli::Z, Pauli::Z, Pauli::Z}, 0.333); + PauliExpBox peb({{Pauli::Z, Pauli::Z, Pauli::Z, Pauli::Z}, 0.333}); circ.add_box(peb, {0, 1, 2, 3}); - PauliExpBox peb2({Pauli::X, Pauli::Z, Pauli::X, Pauli::I}, 0.233); + PauliExpBox peb2({{Pauli::X, Pauli::Z, Pauli::X, Pauli::I}, 0.233}); circ.add_box(peb2, {0, 1, 2, 3}); - PauliExpBox peb3({Pauli::X, Pauli::X, Pauli::X, Pauli::X}, 0.174); + PauliExpBox peb3({{Pauli::X, Pauli::X, Pauli::X, Pauli::X}, 0.174}); circ.add_box(peb3, {0, 1, 2, 3}); Circuit test1 = prepend >> circ; @@ -444,17 +444,17 @@ SCENARIO("Test mutual diagonalisation of fully commuting sets") { CircuitsForTesting::add_initial_prepend_ops(circ); add_ops_to_prepend_1(circ); - PauliExpBox peb({Pauli::Z, Pauli::Y, Pauli::X}, 0.333); + PauliExpBox peb({{Pauli::Z, Pauli::Y, Pauli::X}, 0.333}); circ.add_box(peb, {0, 1, 2}); - PauliExpBox peb2({Pauli::Y, Pauli::Z, Pauli::X}, 0.174); + PauliExpBox peb2({{Pauli::Y, Pauli::Z, Pauli::X}, 0.174}); circ.add_box(peb2, {0, 1, 2}); - PauliExpBox peb3({Pauli::Y, Pauli::Z, Pauli::I}, 0.567); + PauliExpBox peb3({{Pauli::Y, Pauli::Z, Pauli::I}, 0.567}); circ.add_box(peb3, {0, 1, 2}); - PauliExpBox peb4({Pauli::Z, Pauli::Y, Pauli::I}, 1.849); + PauliExpBox peb4({{Pauli::Z, Pauli::Y, Pauli::I}, 1.849}); circ.add_box(peb4, {0, 1, 2}); - PauliExpBox peb5({Pauli::X, Pauli::X, Pauli::X}, 1.67); + PauliExpBox peb5({{Pauli::X, Pauli::X, Pauli::X}, 1.67}); circ.add_box(peb5, {0, 1, 2}); - PauliExpBox peb6({Pauli::X, Pauli::X, Pauli::I}, 0.83); + PauliExpBox peb6({{Pauli::X, Pauli::X, Pauli::I}, 0.83}); circ.add_box(peb6, {0, 1, 2}); Circuit test1 = circ; @@ -573,21 +573,21 @@ SCENARIO("Test mutual diagonalisation of fully commuting sets") { {a, 0.3112}, {b, 1.178}, {c, -0.911}, {d, 0.7122}, {e, 1.102}, {f, 0.151}, {g, 1.223}, {h, 1.666}}; - PauliExpBox peb0({Pauli::X, Pauli::X, Pauli::X, Pauli::Y}, ea); + PauliExpBox peb0({{Pauli::X, Pauli::X, Pauli::X, Pauli::Y}, ea}); circ.add_box(peb0, {0, 1, 2, 3}); - PauliExpBox peb1({Pauli::X, Pauli::X, Pauli::Y, Pauli::X}, eb); + PauliExpBox peb1({{Pauli::X, Pauli::X, Pauli::Y, Pauli::X}, eb}); circ.add_box(peb1, {0, 1, 2, 3}); - PauliExpBox peb2({Pauli::X, Pauli::Y, Pauli::X, Pauli::X}, ec); + PauliExpBox peb2({{Pauli::X, Pauli::Y, Pauli::X, Pauli::X}, ec}); circ.add_box(peb2, {0, 1, 2, 3}); - PauliExpBox peb3({Pauli::X, Pauli::Y, Pauli::Y, Pauli::Y}, ed); + PauliExpBox peb3({{Pauli::X, Pauli::Y, Pauli::Y, Pauli::Y}, ed}); circ.add_box(peb3, {0, 1, 2, 3}); - PauliExpBox peb4({Pauli::Y, Pauli::X, Pauli::X, Pauli::X}, ee); + PauliExpBox peb4({{Pauli::Y, Pauli::X, Pauli::X, Pauli::X}, ee}); circ.add_box(peb4, {0, 1, 2, 3}); - PauliExpBox peb5({Pauli::Y, Pauli::X, Pauli::Y, Pauli::Y}, ef); + PauliExpBox peb5({{Pauli::Y, Pauli::X, Pauli::Y, Pauli::Y}, ef}); circ.add_box(peb5, {0, 1, 2, 3}); - PauliExpBox peb6({Pauli::Y, Pauli::Y, Pauli::X, Pauli::Y}, eg); + PauliExpBox peb6({{Pauli::Y, Pauli::Y, Pauli::X, Pauli::Y}, eg}); circ.add_box(peb6, {0, 1, 2, 3}); - PauliExpBox peb7({Pauli::Y, Pauli::Y, Pauli::Y, Pauli::X}, eh); + PauliExpBox peb7({{Pauli::Y, Pauli::Y, Pauli::Y, Pauli::X}, eh}); circ.add_box(peb7, {0, 1, 2, 3}); Circuit test1 = prepend >> circ; @@ -619,13 +619,13 @@ SCENARIO("Test mutual diagonalisation of fully commuting sets") { std::map symbol_map = { {a, 0.3112}, {b, 1.178}, {c, -0.911}, {d, 0.7122}}; - PauliExpBox peb0({Pauli::Y, Pauli::Z, Pauli::X, Pauli::I}, ea); + PauliExpBox peb0({{Pauli::Y, Pauli::Z, Pauli::X, Pauli::I}, ea}); circ.add_box(peb0, {0, 1, 2, 3}); - PauliExpBox peb1({Pauli::X, Pauli::Z, Pauli::Y, Pauli::I}, eb); + PauliExpBox peb1({{Pauli::X, Pauli::Z, Pauli::Y, Pauli::I}, eb}); circ.add_box(peb1, {0, 1, 2, 3}); - PauliExpBox peb2({Pauli::I, Pauli::Y, Pauli::Z, Pauli::X}, ec); + PauliExpBox peb2({{Pauli::I, Pauli::Y, Pauli::Z, Pauli::X}, ec}); circ.add_box(peb2, {0, 1, 2, 3}); - PauliExpBox peb3({Pauli::I, Pauli::X, Pauli::Y, Pauli::Z}, ed); + PauliExpBox peb3({{Pauli::I, Pauli::X, Pauli::Y, Pauli::Z}, ed}); circ.add_box(peb3, {0, 1, 2, 3}); Circuit test1 = prepend >> circ; @@ -667,19 +667,19 @@ SCENARIO("Test mutual diagonalisation of fully commuting sets") { {a, 0.3112}, {b, 1.178}, {c, -0.911}, {d, 0.7122}, {e, 1.102}, {f, 0.151}, {g, 1.223}}; - PauliExpBox peb0({Pauli::I, Pauli::X, Pauli::Z, Pauli::I, Pauli::Z}, ea); + PauliExpBox peb0({{Pauli::I, Pauli::X, Pauli::Z, Pauli::I, Pauli::Z}, ea}); circ.add_box(peb0, {0, 1, 2, 3, 4}); - PauliExpBox peb1({Pauli::I, Pauli::Y, Pauli::I, Pauli::Z, Pauli::Y}, eb); + PauliExpBox peb1({{Pauli::I, Pauli::Y, Pauli::I, Pauli::Z, Pauli::Y}, eb}); circ.add_box(peb1, {0, 1, 2, 3, 4}); - PauliExpBox peb2({Pauli::X, Pauli::X, Pauli::I, Pauli::Y, Pauli::I}, ec); + PauliExpBox peb2({{Pauli::X, Pauli::X, Pauli::I, Pauli::Y, Pauli::I}, ec}); circ.add_box(peb2, {0, 1, 2, 3, 4}); - PauliExpBox peb3({Pauli::Y, Pauli::Y, Pauli::X, Pauli::I, Pauli::I}, ed); + PauliExpBox peb3({{Pauli::Y, Pauli::Y, Pauli::X, Pauli::I, Pauli::I}, ed}); circ.add_box(peb3, {0, 1, 2, 3, 4}); - PauliExpBox peb4({Pauli::Z, Pauli::I, Pauli::Y, Pauli::X, Pauli::X}, ee); + PauliExpBox peb4({{Pauli::Z, Pauli::I, Pauli::Y, Pauli::X, Pauli::X}, ee}); circ.add_box(peb4, {0, 1, 2, 3, 4}); - PauliExpBox peb5({Pauli::Z, Pauli::X, Pauli::I, Pauli::Z, Pauli::Z}, ef); + PauliExpBox peb5({{Pauli::Z, Pauli::X, Pauli::I, Pauli::Z, Pauli::Z}, ef}); circ.add_box(peb5, {0, 1, 2, 3, 4}); - PauliExpBox peb6({Pauli::Z, Pauli::Y, Pauli::Z, Pauli::I, Pauli::Y}, eg); + PauliExpBox peb6({{Pauli::Z, Pauli::Y, Pauli::Z, Pauli::I, Pauli::Y}, eg}); circ.add_box(peb6, {0, 1, 2, 3, 4}); Circuit test1 = prepend >> circ; @@ -730,90 +730,68 @@ SCENARIO("Test mutual diagonalisation of fully commuting sets") { SCENARIO("Conjugating Cliffords through Pauli tensors") { GIVEN("A 3qb XYZ pauli tensor") { - QubitPauliTensor qpt({Pauli::X, Pauli::Y, Pauli::Z}); + SpPauliStabiliser qpt(DensePauliMap{Pauli::X, Pauli::Y, Pauli::Z}); WHEN("Commuting a Hadamard through qb0") { Qubit qb0(0); conjugate_PauliTensor(qpt, OpType::H, qb0); - auto it = qpt.string.map.find(qb0); - REQUIRE(it != qpt.string.map.end()); THEN("X becomes Z") { - REQUIRE(it->second == Pauli::Z); - REQUIRE(std::abs(qpt.coeff - 1.) < EPS); + REQUIRE(qpt.get(qb0) == Pauli::Z); + REQUIRE(qpt.is_real_negative() == false); } } WHEN("Commuting a X through qb0") { Qubit qb0(0); conjugate_PauliTensor(qpt, OpType::X, qb0); - auto it = qpt.string.map.find(qb0); - REQUIRE(it != qpt.string.map.end()); THEN("X remains X") { - REQUIRE(it->second == Pauli::X); - REQUIRE(std::abs(qpt.coeff - 1.) < EPS); + REQUIRE(qpt.get(qb0) == Pauli::X); + REQUIRE(qpt.is_real_negative() == false); } } WHEN("Commuting a X through qb1") { Qubit qb1(1); conjugate_PauliTensor(qpt, OpType::X, qb1); - auto it = qpt.string.map.find(qb1); - REQUIRE(it != qpt.string.map.end()); THEN("Y becomes -Y") { - REQUIRE(it->second == Pauli::Y); - REQUIRE(std::abs(qpt.coeff + 1.) < EPS); + REQUIRE(qpt.get(qb1) == Pauli::Y); + REQUIRE(qpt.is_real_negative() == true); } } WHEN("Commuting a CX through qb0-qb1") { Qubit qb0(0), qb1(1); conjugate_PauliTensor(qpt, OpType::CX, qb0, qb1); - auto it0 = qpt.string.map.find(qb0); - auto it1 = qpt.string.map.find(qb1); - REQUIRE(it0 != qpt.string.map.end()); - REQUIRE(it1 != qpt.string.map.end()); THEN("XY becomes YZ") { - REQUIRE(it0->second == Pauli::Y); - REQUIRE(it1->second == Pauli::Z); - REQUIRE(std::abs(qpt.coeff - 1.) < EPS); + REQUIRE(qpt.get(qb0) == Pauli::Y); + REQUIRE(qpt.get(qb1) == Pauli::Z); + REQUIRE(qpt.is_real_negative() == false); } } WHEN("Commuting an XXPhase3 through qb0-qb1-qb2") { Qubit qb0(0), qb1(1), qb2(2); conjugate_PauliTensor(qpt, OpType::XXPhase3, qb0, qb1, qb2); - auto it0 = qpt.string.map.find(qb0); - auto it1 = qpt.string.map.find(qb1); - auto it2 = qpt.string.map.find(qb2); - REQUIRE(it0 != qpt.string.map.end()); - REQUIRE(it1 != qpt.string.map.end()); - REQUIRE(it2 != qpt.string.map.end()); THEN("XYZ becomes -XZY") { - REQUIRE(it0->second == Pauli::X); - REQUIRE(it1->second == Pauli::Z); - REQUIRE(it2->second == Pauli::Y); - REQUIRE(std::abs(qpt.coeff + 1.) < EPS); + REQUIRE(qpt.get(qb0) == Pauli::X); + REQUIRE(qpt.get(qb1) == Pauli::Z); + REQUIRE(qpt.get(qb2) == Pauli::Y); + REQUIRE(qpt.is_real_negative() == true); } } } GIVEN("A 3qb XXX pauli tensor") { - QubitPauliTensor qpt({Pauli::X, Pauli::X, Pauli::X}); + SpPauliStabiliser qpt(DensePauliMap{Pauli::X, Pauli::X, Pauli::X}); WHEN("Commuting an XXPhase3 through qb0-qb1-qb2") { Qubit qb0(0), qb1(1), qb2(2); - auto it0 = qpt.string.map.find(qb0); - auto it1 = qpt.string.map.find(qb1); - auto it2 = qpt.string.map.find(qb2); - REQUIRE(it0 != qpt.string.map.end()); - REQUIRE(it1 != qpt.string.map.end()); - REQUIRE(it2 != qpt.string.map.end()); THEN("XXX remains XXX") { - REQUIRE(it0->second == Pauli::X); - REQUIRE(it1->second == Pauli::X); - REQUIRE(it2->second == Pauli::X); - REQUIRE(std::abs(qpt.coeff - 1.) < EPS); + REQUIRE(qpt.get(qb0) == Pauli::X); + REQUIRE(qpt.get(qb1) == Pauli::X); + REQUIRE(qpt.get(qb2) == Pauli::X); + REQUIRE(qpt.is_real_negative() == false); } } } } SCENARIO("Test greedy diagonalisation explicitly") { - auto is_diagonal = [](const QubitPauliTensor& qpt) { - for (auto [qb, p] : qpt.string.map) { + auto is_diagonal = [](const SpSymPauliTensor& qpt) { + for (auto [qb, p] : qpt.string) { if (p != Pauli::I && p != Pauli::Z) { return false; } @@ -821,45 +799,38 @@ SCENARIO("Test greedy diagonalisation explicitly") { return true; }; - auto apply_strategy = - [](std::list>& gadgets, - std::set& qubits, Circuit& cliff_circ, - const CXConfigType config) { - while (!qubits.empty()) { - Conjugations conjugations; - greedy_diagonalise(gadgets, qubits, conjugations, cliff_circ, config); - for (auto& [g, expr] : gadgets) { - apply_conjugations(g, conjugations); - } - check_easy_diagonalise(gadgets, qubits, cliff_circ); - } - }; + auto apply_strategy = [](std::list& gadgets, + std::set& qubits, Circuit& cliff_circ, + const CXConfigType config) { + while (!qubits.empty()) { + Conjugations conjugations; + greedy_diagonalise(gadgets, qubits, conjugations, cliff_circ, config); + for (auto& g : gadgets) { + apply_conjugations(g, conjugations); + } + check_easy_diagonalise(gadgets, qubits, cliff_circ); + } + }; GIVEN("A large-ish set of PauliTensor") { unsigned n_qbs = 6; std::set qbs; for (unsigned i = 0; i < n_qbs; ++i) qbs.insert(Qubit(i)); - std::list> gadgets; + std::list gadgets; Conjugations conjugations; Circuit cliff_circ(n_qbs); // commuting set - std::vector tensors; - tensors.push_back(QubitPauliTensor( - {Pauli::Z, Pauli::Z, Pauli::Z, Pauli::X, Pauli::X, Pauli::X})); - tensors.push_back(QubitPauliTensor( - {Pauli::Z, Pauli::X, Pauli::Y, Pauli::Z, Pauli::Z, Pauli::X})); - tensors.push_back(QubitPauliTensor( - {Pauli::Z, Pauli::Y, Pauli::X, Pauli::Z, Pauli::Z, Pauli::X})); - tensors.push_back(QubitPauliTensor( - {Pauli::Z, Pauli::Y, Pauli::X, Pauli::Y, Pauli::Y, Pauli::X})); - tensors.push_back(QubitPauliTensor( - {Pauli::X, Pauli::Z, Pauli::Z, Pauli::Y, Pauli::Y, Pauli::Y})); - std::vector exprs{1.13, 0.226, 0.013, 0.952, 1.88}; - - for (unsigned i = 0; i < 5; ++i) { - gadgets.push_back({tensors[i], exprs[i]}); - } + gadgets.push_back(SpSymPauliTensor( + {Pauli::Z, Pauli::Z, Pauli::Z, Pauli::X, Pauli::X, Pauli::X}, 1.13)); + gadgets.push_back(SpSymPauliTensor( + {Pauli::Z, Pauli::X, Pauli::Y, Pauli::Z, Pauli::Z, Pauli::X}, 0.226)); + gadgets.push_back(SpSymPauliTensor( + {Pauli::Z, Pauli::Y, Pauli::X, Pauli::Z, Pauli::Z, Pauli::X}, 0.013)); + gadgets.push_back(SpSymPauliTensor( + {Pauli::Z, Pauli::Y, Pauli::X, Pauli::Y, Pauli::Y, Pauli::X}, 0.952)); + gadgets.push_back(SpSymPauliTensor( + {Pauli::X, Pauli::Z, Pauli::Z, Pauli::Y, Pauli::Y, Pauli::Y}, 1.88)); WHEN("a single run with Snake configuration") { CXConfigType cx_config = CXConfigType::Snake; @@ -874,7 +845,7 @@ SCENARIO("Test greedy diagonalisation explicitly") { apply_strategy(gadgets, qbs, cliff_circ, cx_config); THEN("gadgets are diagonal") { for (const auto& g : gadgets) { - REQUIRE(is_diagonal(g.first)); + REQUIRE(is_diagonal(g)); } } } @@ -891,7 +862,7 @@ SCENARIO("Test greedy diagonalisation explicitly") { apply_strategy(gadgets, qbs, cliff_circ, cx_config); THEN("gadgets are diagonal") { for (const auto& g : gadgets) { - REQUIRE(is_diagonal(g.first)); + REQUIRE(is_diagonal(g)); } } } @@ -908,7 +879,7 @@ SCENARIO("Test greedy diagonalisation explicitly") { apply_strategy(gadgets, qbs, cliff_circ, cx_config); THEN("gadgets are diagonal") { for (const auto& g : gadgets) { - REQUIRE(is_diagonal(g.first)); + REQUIRE(is_diagonal(g)); } } } @@ -925,7 +896,7 @@ SCENARIO("Test greedy diagonalisation explicitly") { apply_strategy(gadgets, qbs, cliff_circ, cx_config); THEN("gadgets are diagonal") { for (const auto& g : gadgets) { - REQUIRE(is_diagonal(g.first)); + REQUIRE(is_diagonal(g)); } } } @@ -941,23 +912,21 @@ SCENARIO("Diagonalise a pair of gadgets") { Circuit circ(n_qbs); // commuting set - std::vector tensors; - tensors.push_back(QubitPauliTensor( - {Pauli::Z, Pauli::Z, Pauli::X, Pauli::I, Pauli::I, Pauli::X})); - tensors.push_back(QubitPauliTensor( - {Pauli::Z, Pauli::Z, Pauli::X, Pauli::Z, Pauli::Z, Pauli::I})); - std::vector exprs{1.13, 0.226}; + std::vector gadgets; + gadgets.push_back(SpSymPauliTensor( + {Pauli::Z, Pauli::Z, Pauli::X, Pauli::I, Pauli::I, Pauli::X}, 1.13)); + gadgets.push_back(SpSymPauliTensor( + {Pauli::Z, Pauli::Z, Pauli::X, Pauli::Z, Pauli::Z, Pauli::I}, 0.226)); Circuit correct; for (unsigned i = 0; i < 2; ++i) { - append_single_pauli_gadget(correct, tensors[i], exprs[i]); + append_single_pauli_gadget(correct, gadgets.at(i)); } auto u_correct = tket_sim::get_unitary(correct); GIVEN("Snake configuration") { CXConfigType config = CXConfigType::Snake; - append_pauli_gadget_pair( - circ, tensors[0], exprs[0], tensors[1], exprs[1], config); + append_pauli_gadget_pair(circ, gadgets.at(0), gadgets.at(1), config); THEN("Unitary is correct") { auto u_res = tket_sim::get_unitary(circ); REQUIRE((u_correct - u_res).cwiseAbs().sum() < ERR_EPS); @@ -965,8 +934,7 @@ SCENARIO("Diagonalise a pair of gadgets") { } GIVEN("Star configuration") { CXConfigType config = CXConfigType::Star; - append_pauli_gadget_pair( - circ, tensors[0], exprs[0], tensors[1], exprs[1], config); + append_pauli_gadget_pair(circ, gadgets.at(0), gadgets.at(1), config); THEN("Unitary is correct") { auto u_res = tket_sim::get_unitary(circ); REQUIRE((u_correct - u_res).cwiseAbs().sum() < ERR_EPS); @@ -974,8 +942,7 @@ SCENARIO("Diagonalise a pair of gadgets") { } GIVEN("Tree configuration") { CXConfigType config = CXConfigType::Tree; - append_pauli_gadget_pair( - circ, tensors[0], exprs[0], tensors[1], exprs[1], config); + append_pauli_gadget_pair(circ, gadgets.at(0), gadgets.at(1), config); THEN("Unitary is correct") { auto u_res = tket_sim::get_unitary(circ); REQUIRE((u_correct - u_res).cwiseAbs().sum() < ERR_EPS); @@ -983,8 +950,7 @@ SCENARIO("Diagonalise a pair of gadgets") { } GIVEN("MultiQGate configuration") { CXConfigType config = CXConfigType::MultiQGate; - append_pauli_gadget_pair( - circ, tensors[0], exprs[0], tensors[1], exprs[1], config); + append_pauli_gadget_pair(circ, gadgets.at(0), gadgets.at(1), config); circ.decompose_boxes_recursively(); THEN("XXPhase3 were used") { REQUIRE(circ.count_gates(OpType::XXPhase3) == 2); diff --git a/tket/test/src/test_PauliString.cpp b/tket/test/src/test_PauliString.cpp deleted file mode 100644 index fca7fdbe63..0000000000 --- a/tket/test/src/test_PauliString.cpp +++ /dev/null @@ -1,277 +0,0 @@ -// Copyright 2019-2023 Cambridge Quantum Computing -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include -#include - -#include "CircuitsForTesting.hpp" -#include "testutil.hpp" -#include "tket/Converters/PauliGadget.hpp" -#include "tket/PauliGraph/ConjugatePauliFunctions.hpp" -#include "tket/Utils/PauliStrings.hpp" - -namespace tket { -namespace test_PauliString { - -SCENARIO("Testing equality of QubitPauliTensor") { - Qubit q0 = Qubit("q", 0); - Qubit q1 = Qubit("q", 1); - Qubit q2 = Qubit("r", 0); - Qubit q3 = Qubit("s"); - Qubit q4 = Qubit("t", 0, 1); - Qubit q5 = Qubit("t", 0, 0); - GIVEN("Two exactly identical Pauli strings") { - QubitPauliMap map = { - {q0, Pauli::I}, {q1, Pauli::X}, {q2, Pauli::Y}, {q3, Pauli::Z}}; - QubitPauliTensor a(map, i_); - QubitPauliTensor b(map, i_); - REQUIRE(a == b); - THEN("We add some extra Is on each one") { - a.string.map.insert({q4, Pauli::I}); - b.string.map.insert({q5, Pauli::I}); - REQUIRE(a == b); - } - } - GIVEN("Two Pauli strings with different Paulis but same coefficient") { - QubitPauliTensor a(q0, Pauli::X); - QubitPauliTensor b(q0, Pauli::Y); - REQUIRE(!(a == b)); - } - GIVEN("Two Pauli strings with disjoint Paulis but same coefficient") { - QubitPauliTensor a(q0, Pauli::X); - QubitPauliTensor b(q1, Pauli::X); - REQUIRE(!(a == b)); - } - GIVEN("Two Pauli strings with same Paulis but different coefficient") { - QubitPauliTensor a(q0, Pauli::X, 1.); - QubitPauliTensor b(q0, Pauli::X, i_); - REQUIRE(!(a == b)); - } - GIVEN("Two completely different Pauli strings") { - QubitPauliString tensor_a( - {{q0, Pauli::I}, {q1, Pauli::X}, {q2, Pauli::Y}, {q3, Pauli::Z}}); - QubitPauliString tensor_b( - {{q0, Pauli::X}, {q1, Pauli::I}, {q2, Pauli::Z}, {q4, Pauli::Y}}); - QubitPauliTensor a(tensor_a, 1.); - QubitPauliTensor b(tensor_b, i_); - REQUIRE(!(a == b)); - } -} - -SCENARIO("Testing multiplication of QubitPauliTensor") { - Qubit q0 = Qubit("q", 0); - Qubit q1 = Qubit("q", 1); - Qubit q2 = Qubit("r", 0); - Qubit q3 = Qubit("s"); - Qubit q4 = Qubit("t", 0, 1); - GIVEN("Two Pauli strings with disjoint non-trivial components") { - QubitPauliTensor a(q0, Pauli::X, 2.); - QubitPauliTensor b(q1, Pauli::Y, i_); - QubitPauliTensor c({{q0, Pauli::X}, {q1, Pauli::Y}}, 2. * i_); - REQUIRE((a * b) == c); - } - GIVEN("Multiplying by a trivial Pauli string") { - QubitPauliTensor a(q0, Pauli::X, 2.); - QubitPauliTensor b(q0, Pauli::X, 3. * i_); - REQUIRE((a * QubitPauliTensor(1.5 * i_)) == b); - } - GIVEN("Two exactly identical Pauli strings") { - QubitPauliMap map = { - {q0, Pauli::I}, {q1, Pauli::X}, {q2, Pauli::Y}, {q3, Pauli::Z}}; - QubitPauliTensor a(map, i_); - QubitPauliTensor b(-1.); - REQUIRE((a * a) == b); - } - GIVEN("Each individual Pauli combination") { - QubitPauliTensor I(q0, Pauli::I); - QubitPauliTensor X(q0, Pauli::X); - QubitPauliTensor Y(q0, Pauli::Y); - QubitPauliTensor Z(q0, Pauli::Z); - QubitPauliTensor i(i_); - QubitPauliTensor mi(-i_); - REQUIRE((I * I) == I); - REQUIRE((I * X) == X); - REQUIRE((I * Y) == Y); - REQUIRE((I * Z) == Z); - REQUIRE((X * I) == X); - REQUIRE((X * X) == I); - REQUIRE((X * Y) == (i * Z)); - REQUIRE((X * Z) == (mi * Y)); - REQUIRE((Y * I) == Y); - REQUIRE((Y * X) == (mi * Z)); - REQUIRE((Y * Y) == I); - REQUIRE((Y * Z) == (i * X)); - REQUIRE((Z * I) == Z); - REQUIRE((Z * X) == (i * Y)); - REQUIRE((Z * Y) == (mi * X)); - REQUIRE((Z * Z) == I); - } - GIVEN("2*IXYZ(I) * -1.5i*XIZ(I)Y") { - QubitPauliString tensor_a( - {{q0, Pauli::I}, {q1, Pauli::X}, {q2, Pauli::Y}, {q3, Pauli::Z}}); - QubitPauliString tensor_b( - {{q0, Pauli::X}, {q1, Pauli::I}, {q2, Pauli::Z}, {q4, Pauli::Y}}); - QubitPauliTensor a(tensor_a, 2.); - QubitPauliTensor b(tensor_b, -1.5 * i_); - QubitPauliString tensor_c( - {{q0, Pauli::X}, - {q1, Pauli::X}, - {q2, Pauli::X}, - {q3, Pauli::Z}, - {q4, Pauli::Y}}); - QubitPauliTensor c(tensor_c, 3.); - REQUIRE((a * b) == c); - } -} - -SCENARIO("Test basic conjugations") { - const Qubit q0 = Qubit("q", 0); - const Qubit q1 = Qubit("q", 1); - Circuit circ; - circ.add_qubit(q0); - circ.add_qubit(q1); - // add some arbitrary rotations to get away from |00> state - const auto& prepend = CircuitsForTesting::get().prepend_2qb_circuit; - const double angle = 0.845; - // generate all different 2-qb pauli strings - std::vector qps_vec; - for (const auto& map_entry : QubitPauliTensor::get_mult_matrix()) { - const std::pair& paulis = map_entry.first; - QubitPauliMap map = {{q0, paulis.first}, {q1, paulis.second}}; - QubitPauliTensor qps(map); - qps_vec.push_back(qps); - } - - const auto perform_test = [&q0, &qps_vec, angle, &prepend]( - OpType op_type, OpType op_type_dag, - OpType tensor_op_type, bool reverse = false) { - for (QubitPauliTensor qps : qps_vec) { - Circuit test(2); - test.add_op(op_type, {0}); - append_single_pauli_gadget(test, qps, angle); - test.add_op(op_type_dag, {0}); - test = prepend >> test; - conjugate_PauliTensor(qps, tensor_op_type, q0, reverse); - Circuit test1 = prepend; - append_single_pauli_gadget(test1, qps, angle); - REQUIRE(test_statevector_comparison(test, test1)); - } - }; - - WHEN("Test Hs") { perform_test(OpType::H, OpType::H, OpType::H); } - WHEN("Test Ss") { - perform_test(OpType::S, OpType::Sdg, OpType::S); - perform_test(OpType::Sdg, OpType::S, OpType::S, true); - } - WHEN("Test Vs") { - perform_test(OpType::V, OpType::Vdg, OpType::V); - perform_test(OpType::Vdg, OpType::V, OpType::V, true); - } - WHEN("Test Xs") { perform_test(OpType::X, OpType::X, OpType::X); } - WHEN("Test Zs") { perform_test(OpType::Z, OpType::Z, OpType::Z); } - WHEN("Test CXs") { - for (QubitPauliTensor qps : qps_vec) { - Circuit test(2); - test.add_op(OpType::CX, {0, 1}); - append_single_pauli_gadget(test, qps, angle); - test.add_op(OpType::CX, {0, 1}); - test = prepend >> test; - conjugate_PauliTensor(qps, OpType::CX, q0, q1); - Circuit test2 = prepend; - append_single_pauli_gadget(test2, qps, angle); - REQUIRE(test_statevector_comparison(test, test2)); - } - } -} - -SCENARIO("Test hashing") { - GIVEN("Trivial strings") { - QubitPauliString qps1; - QubitPauliString qps2; - REQUIRE(hash_value(qps1) == hash_value(qps2)); - WHEN("Add I Pauli") { - qps1.map[Qubit(0)] = Pauli::I; - REQUIRE(hash_value(qps1) == hash_value(qps2)); - } - } - GIVEN("Nontrivial strings") { - QubitPauliMap qpm1{ - {Qubit(0), Pauli::Z}, - {Qubit(1), Pauli::Y}, - {Qubit(2), Pauli::X}, - {Qubit(3), Pauli::I}}; - QubitPauliString qps1(qpm1); - QubitPauliString qps2(qpm1); - qps1.map[Qubit(4)] = Pauli::X; - qps2.map[Qubit(4)] = Pauli::X; - qps2.map[Qubit(5)] = Pauli::I; - REQUIRE(hash_value(qps1) == hash_value(qps2)); - } - GIVEN("Trivial tensor") { - QubitPauliTensor qpt1; - QubitPauliTensor qpt2; - REQUIRE(hash_value(qpt1) == hash_value(qpt2)); - WHEN("Add I Pauli") { - qpt1.string.map[Qubit(0)] = Pauli::I; - REQUIRE(hash_value(qpt1) == hash_value(qpt2)); - } - } - GIVEN("Nontrivial tensors") { - QubitPauliMap qpm1{ - {Qubit(0), Pauli::Z}, - {Qubit(1), Pauli::Y}, - {Qubit(2), Pauli::X}, - {Qubit(3), Pauli::I}}; - QubitPauliString qps1(qpm1); - QubitPauliString qps2(qpm1); - qps1.map[Qubit(4)] = Pauli::X; - qps2.map[Qubit(4)] = Pauli::X; - qps2.map[Qubit(5)] = Pauli::I; - QubitPauliTensor qpt1(qps1, .5 * i_); - QubitPauliTensor qpt2(qps2, .5 * i_); - qpt2.string.map[Qubit(6)] = Pauli::I; - REQUIRE(hash_value(qpt1) == hash_value(qpt2)); - } -} - -SCENARIO("Test matrix product utilities") { - GIVEN("Simple operator and its +1 eigenvector") { - const QubitPauliString op({{Qubit(0), Pauli::X}, {Qubit(1), Pauli::Y}}); - Eigen::VectorXcd state(4); - - state << Complex(0.5, 0), Complex(0, 0.5), Complex(0.5, 0), Complex(0, 0.5); - WHEN("Operator acts on statevector") { - Eigen::VectorXcd dotproduct = op.dot_state(state); - THEN("The eigenvector is unchanged") { - REQUIRE(dotproduct.isApprox(state)); - } - } - - WHEN("Expectation value is calculated w.r.t. +1 eigenvector") { - Complex eigenval = op.state_expectation(state); - THEN("The eigenvalue is correct") { REQUIRE(eigenval == Complex(1, 0)); } - } - } - GIVEN("A qubit list with repeats") { - const QubitPauliString op({{Qubit(0), Pauli::X}, {Qubit(1), Pauli::Y}}); - REQUIRE_THROWS(op.to_sparse_matrix({Qubit(0), Qubit(0), Qubit(1)})); - } - GIVEN("A qubit list that doesn't contain all qubits in the string") { - const QubitPauliString op({{Qubit(0), Pauli::X}, {Qubit(1), Pauli::Y}}); - REQUIRE_THROWS(op.to_sparse_matrix({Qubit(0), Qubit(2)})); - } -} - -} // namespace test_PauliString -} // namespace tket diff --git a/tket/test/src/test_PauliTensor.cpp b/tket/test/src/test_PauliTensor.cpp new file mode 100644 index 0000000000..3a90882922 --- /dev/null +++ b/tket/test/src/test_PauliTensor.cpp @@ -0,0 +1,623 @@ +// Copyright 2019-2023 Cambridge Quantum Computing +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "tket/Utils/PauliTensor.hpp" + +namespace tket { +namespace test_PauliTensor { + +SCENARIO("Testing equality of sparse PauliTensor variants") { + Qubit q0 = Qubit("q", 0); + Qubit q1 = Qubit("q", 1); + Qubit q2 = Qubit("r", 0); + Qubit q3 = Qubit("s"); + Qubit q4 = Qubit("t", 0, 1); + Qubit q5 = Qubit("t", 0, 0); + GIVEN("Two exactly identical Pauli strings") { + QubitPauliMap map = { + {q0, Pauli::I}, {q1, Pauli::X}, {q2, Pauli::Y}, {q3, Pauli::Z}}; + SpCxPauliTensor a(map, i_); + SpCxPauliTensor b(map, i_); + REQUIRE(a == b); + THEN("We add some extra Is on each one") { + a.set(q4, Pauli::I); + b.set(q5, Pauli::I); + REQUIRE(a == b); + } + } + GIVEN("Two Pauli strings with different Paulis but same coefficient") { + SpPauliString a({{q0, Pauli::X}}); + SpPauliString b({{q0, Pauli::Y}}); + REQUIRE(a != b); + REQUIRE(a < b); + } + GIVEN("Two Pauli strings with disjoint Paulis but same coefficient") { + SpPauliString a(q0, Pauli::X); + SpPauliString b(q1, Pauli::X); + REQUIRE(a != b); + REQUIRE(b < a); + } + GIVEN("Two Pauli strings with same Paulis but different coefficient") { + SpCxPauliTensor a(q0, Pauli::X, 1.); + SpCxPauliTensor b(q0, Pauli::X, i_); + REQUIRE(a != b); + REQUIRE(b < a); + } + GIVEN("Two completely different Pauli strings") { + QubitPauliMap qpm_a( + {{q0, Pauli::I}, {q1, Pauli::X}, {q2, Pauli::Y}, {q3, Pauli::Z}}); + QubitPauliMap qpm_b( + {{q0, Pauli::X}, {q1, Pauli::I}, {q2, Pauli::Z}, {q4, Pauli::Y}}); + SpCxPauliTensor a(qpm_a, 1.); + SpCxPauliTensor b(qpm_b, i_); + REQUIRE(a != b); + REQUIRE(a < b); + } +} + +SCENARIO("Testing equality of dense PauliTensor variants") { + GIVEN("Two exactly identical Pauli strings") { + DensePauliMap map = {Pauli::I, Pauli::X, Pauli::Y, Pauli::Z}; + CxPauliTensor a(map, i_); + CxPauliTensor b(map, i_); + REQUIRE(a == b); + THEN("We add some extra Is on each one") { + a.set(4, Pauli::I); + b.set(5, Pauli::I); + REQUIRE(a == b); + } + } + GIVEN("Two Pauli strings with different Paulis but same coefficient") { + PauliString a({Pauli::X}); + PauliString b({Pauli::Y}); + REQUIRE(a != b); + REQUIRE(a < b); + } + GIVEN("Two Pauli strings with disjoint Paulis but same coefficient") { + PauliString a({Pauli::X}); + PauliString b({Pauli::I, Pauli::X}); + REQUIRE(a != b); + REQUIRE(b < a); + } + GIVEN("Two Pauli strings with same Paulis but different coefficient") { + CxPauliTensor a({Pauli::X}, 1.); + CxPauliTensor b({Pauli::X}, i_); + REQUIRE(a != b); + REQUIRE(b < a); + } + GIVEN("Two completely different Pauli strings") { + DensePauliMap qpm_a({Pauli::I, Pauli::X, Pauli::Y, Pauli::Z}); + DensePauliMap qpm_b({Pauli::X, Pauli::I, Pauli::Z, Pauli::Y}); + CxPauliTensor a(qpm_a, 1.); + CxPauliTensor b(qpm_b, i_); + REQUIRE(a != b); + REQUIRE(a < b); + } +} + +SCENARIO("Testing casting between different PauliTensor variants") { + GIVEN("Casting between different coefficient types") { + SpPauliString a1{}; + SpPauliStabiliser b1{}; + SpPauliStabiliser bi({}, 1); + SpPauliStabiliser bm1({}, 2); + SpPauliStabiliser bmi({}, 3); + SpCxPauliTensor c1{}; + SpCxPauliTensor ci({}, i_); + SpCxPauliTensor cm1({}, -1.); + SpCxPauliTensor cmi({}, -i_); + SpCxPauliTensor cval({}, 0.48 - 2.3 * i_); + SpSymPauliTensor d1{}; + SpSymPauliTensor di({}, Expr(SymEngine::I)); + SpSymPauliTensor dm1({}, -1.); + SpSymPauliTensor dmi({}, -Expr(SymEngine::I)); + SpSymPauliTensor dval({}, 0.48 - 2.3 * i_); + SpSymPauliTensor dsym({}, Expr("a")); + + CHECK((SpPauliString)a1 == a1); + CHECK((SpPauliString)b1 == a1); + CHECK((SpPauliString)c1 == a1); + CHECK((SpPauliString)d1 == a1); + CHECK((SpPauliStabiliser)a1 == b1); + CHECK((SpPauliStabiliser)b1 == b1); + CHECK((SpPauliStabiliser)c1 == b1); + CHECK((SpPauliStabiliser)d1 == b1); + CHECK((SpCxPauliTensor)a1 == c1); + CHECK((SpCxPauliTensor)b1 == c1); + CHECK((SpCxPauliTensor)c1 == c1); + CHECK((SpCxPauliTensor)d1 == c1); + CHECK((SpSymPauliTensor)a1 == d1); + CHECK((SpSymPauliTensor)b1 == d1); + CHECK((SpSymPauliTensor)c1 == d1); + CHECK((SpSymPauliTensor)d1 == d1); + + CHECK((SpPauliString)bi == a1); + CHECK((SpPauliString)ci == a1); + CHECK((SpPauliString)di == a1); + CHECK((SpPauliStabiliser)bi == bi); + CHECK((SpPauliStabiliser)ci == bi); + CHECK((SpPauliStabiliser)di == bi); + CHECK((SpCxPauliTensor)bi == ci); + CHECK((SpCxPauliTensor)ci == ci); + CHECK((SpCxPauliTensor)di == ci); + CHECK((SpSymPauliTensor)bi == di); + CHECK((SpSymPauliTensor)ci == di); + CHECK((SpSymPauliTensor)di == di); + + CHECK((SpPauliString)bm1 == a1); + CHECK((SpPauliString)cm1 == a1); + CHECK((SpPauliString)dm1 == a1); + CHECK((SpPauliStabiliser)bm1 == bm1); + CHECK((SpPauliStabiliser)cm1 == bm1); + CHECK((SpPauliStabiliser)dm1 == bm1); + CHECK((SpCxPauliTensor)bm1 == cm1); + CHECK((SpCxPauliTensor)cm1 == cm1); + CHECK((SpCxPauliTensor)dm1 == cm1); + CHECK((SpSymPauliTensor)bm1 == dm1); + CHECK((SpSymPauliTensor)cm1 == dm1); + CHECK((SpSymPauliTensor)dm1 == dm1); + + CHECK((SpPauliString)bmi == a1); + CHECK((SpPauliString)cmi == a1); + CHECK((SpPauliString)dmi == a1); + CHECK((SpPauliStabiliser)bmi == bmi); + CHECK((SpPauliStabiliser)cmi == bmi); + CHECK((SpPauliStabiliser)dmi == bmi); + CHECK((SpCxPauliTensor)bmi == cmi); + CHECK((SpCxPauliTensor)cmi == cmi); + CHECK((SpCxPauliTensor)dmi == cmi); + CHECK((SpSymPauliTensor)bmi == dmi); + CHECK((SpSymPauliTensor)cmi == dmi); + CHECK((SpSymPauliTensor)dmi == dmi); + + CHECK((SpPauliString)cval == a1); + CHECK((SpPauliString)dval == a1); + REQUIRE_THROWS((SpPauliStabiliser)cval); + REQUIRE_THROWS((SpPauliStabiliser)dval); + CHECK((SpCxPauliTensor)cval == cval); + CHECK((SpCxPauliTensor)dval == cval); + CHECK((SpSymPauliTensor)cval == dval); + CHECK((SpSymPauliTensor)dval == dval); + + CHECK((SpPauliString)dsym == a1); + REQUIRE_THROWS((SpPauliStabiliser)dsym); + REQUIRE_THROWS((SpCxPauliTensor)dsym); + CHECK((SpSymPauliTensor)dsym == dsym); + } + GIVEN("Casting between different Pauli containers") { + PauliString ps({Pauli::I, Pauli::X, Pauli::Y}); + SpPauliString sps({Qubit(1), Qubit(2)}, {Pauli::X, Pauli::Y}); + SpPauliString non_default(Qubit("a", 0), Pauli::Z); + + CHECK((SpPauliString)ps == sps); + CHECK((SpPauliString)sps == sps); + CHECK((PauliString)ps == ps); + CHECK((PauliString)sps == ps); + CHECK((SpPauliString)non_default == non_default); + REQUIRE_THROWS((PauliString)non_default); + } + GIVEN("Casting coefficient, keeping dense container") { + DensePauliMap dpm{Pauli::X, Pauli::I, Pauli::Z}; + CHECK((PauliStabiliser)PauliString(dpm) == PauliStabiliser(dpm)); + CHECK( + (SymPauliTensor)CxPauliTensor(dpm, 0.87 + 1.2 * i_) == + SymPauliTensor(dpm, Expr(0.87 + 1.2 * i_))); + } +} + +SCENARIO("Qubit partitions") { + GIVEN("Sparse PauliTensors") { + std::vector qs{Qubit(0), Qubit("a", 0), Qubit("a", 1), + Qubit("b", {0, 0}), Qubit("c", 4), Qubit("p", 12), + Qubit("anc", 0), Qubit(2)}; + std::list qsl{qs.begin(), qs.end()}; + SpPauliString xxyyzzii( + qsl, {Pauli::X, Pauli::X, Pauli::Y, Pauli::Y, Pauli::Z, Pauli::Z, + Pauli::I, Pauli::I}); + SpPauliString ixyxyziz( + qsl, {Pauli::I, Pauli::X, Pauli::Y, Pauli::X, Pauli::Y, Pauli::Z, + Pauli::I, Pauli::Z}); + xxyyzzii.compress(); + + // Common qubits should ignore Pauli::I matches + CHECK( + xxyyzzii.common_qubits(ixyxyziz) == + std::set{qs[1], qs[2], qs[5]}); + CHECK( + xxyyzzii.conflicting_qubits(ixyxyziz) == std::set{qs[3], qs[4]}); + CHECK(xxyyzzii.own_qubits(ixyxyziz) == std::set{qs[0]}); + CHECK(ixyxyziz.own_qubits(xxyyzzii) == std::set{qs[7]}); + } + GIVEN("Dense PauliTensors") { + PauliString xxyyzzii( + {Pauli::X, Pauli::X, Pauli::Y, Pauli::Y, Pauli::Z, Pauli::Z, Pauli::I}); + PauliString ixyxyziz( + {Pauli::I, Pauli::X, Pauli::Y, Pauli::X, Pauli::Y, Pauli::Z, Pauli::I, + Pauli::Z}); + + // Common indices should ignore Pauli::I matches + CHECK(xxyyzzii.common_indices(ixyxyziz) == std::set{1, 2, 5}); + CHECK(xxyyzzii.conflicting_indices(ixyxyziz) == std::set{3, 4}); + CHECK(xxyyzzii.own_indices(ixyxyziz) == std::set{0}); + CHECK(ixyxyziz.own_indices(xxyyzzii) == std::set{7}); + } +} + +SCENARIO("String formatting of PauliTensor") { + CHECK(SpPauliString().to_str() == "()"); + CHECK(SpPauliStabiliser({}, 0).to_str() == "()"); + CHECK(SpPauliStabiliser({}, 1).to_str() == "i*()"); + CHECK(SpPauliStabiliser({}, 2).to_str() == "-()"); + CHECK(SpPauliStabiliser({}, 3).to_str() == "-i*()"); + CHECK(SpCxPauliTensor({}, 1.).to_str() == "()"); + CHECK(SpCxPauliTensor({}, -1.).to_str() == "-()"); + CHECK(SpCxPauliTensor({}, 4.2 + 0.87 * i_).to_str() == "(4.2,0.87)*()"); + CHECK(SpSymPauliTensor({}, 1.).to_str() == "()"); + CHECK(SpSymPauliTensor({}, -1.).to_str() == "-()"); + CHECK( + SpSymPauliTensor({}, 4.2 + 0.87 * Expr(SymEngine::I)).to_str() == + "(4.2 + 0.87*I)*()"); + CHECK(SpSymPauliTensor({}, Expr("2*a")).to_str() == "(2*a)*()"); + + CHECK( + SpPauliString({{Qubit("a", 2), Pauli::X}, + {Qubit("a", 0), Pauli::Z}, + {Qubit("b", 0), Pauli::I}, + {Qubit("b", 1), Pauli::Y}}) + .to_str() == "(Za[0], Xa[2], Ib[0], Yb[1])"); + CHECK( + PauliString({Pauli::I, Pauli::Z, Pauli::X, Pauli::Y, Pauli::I}) + .to_str() == "IZXYI"); + CHECK(PauliStabiliser({Pauli::X, Pauli::Y}, 2).to_str() == "-XY"); + CHECK( + CxPauliTensor({Pauli::Z, Pauli::Z, Pauli::I}, 3.1 - 0.1 * i_).to_str() == + "(3.1,-0.1)*ZZI"); + CHECK( + SymPauliTensor(DensePauliMap(5, Pauli::Y), Expr("k")).to_str() == + "(k)*YYYYY"); +} + +SCENARIO("Testing multiplication of sparse PauliTensor") { + Qubit q0 = Qubit("q", 0); + Qubit q1 = Qubit("q", 1); + Qubit q2 = Qubit("r", 0); + Qubit q3 = Qubit("s"); + Qubit q4 = Qubit("t", 0, 1); + GIVEN("Two Pauli strings with disjoint non-trivial components") { + SpPauliString a(q0, Pauli::X); + SpPauliString b(q1, Pauli::Y); + SpPauliString c({{q0, Pauli::X}, {q1, Pauli::Y}}); + REQUIRE((a * b) == c); + } + GIVEN("Multiplying by a trivial Pauli string") { + SpCxPauliTensor a(q0, Pauli::X, 2.); + SpCxPauliTensor b(q0, Pauli::X, 3. * i_); + REQUIRE((a * SpCxPauliTensor({}, 1.5 * i_)) == b); + } + GIVEN("Two exactly identical Pauli strings") { + QubitPauliMap map = { + {q0, Pauli::I}, {q1, Pauli::X}, {q2, Pauli::Y}, {q3, Pauli::Z}}; + SpPauliStabiliser a(map, 3); + SpPauliStabiliser b({}, 2); + REQUIRE((a * a).get(q0) == Pauli::I); + REQUIRE((a * a) == b); + } + GIVEN("Each individual Pauli combination") { + SpPauliStabiliser I(q0, Pauli::I); + SpPauliStabiliser X(q0, Pauli::X); + SpPauliStabiliser Y(q0, Pauli::Y); + SpPauliStabiliser Z(q0, Pauli::Z); + SpPauliStabiliser i({}, 1); + SpPauliStabiliser mi({}, 3); + REQUIRE((I * I) == I); + REQUIRE((I * X) == X); + REQUIRE((I * Y) == Y); + REQUIRE((I * Z) == Z); + REQUIRE((X * I) == X); + REQUIRE((X * X) == I); + REQUIRE((X * Y) == (i * Z)); + REQUIRE((X * Z) == (mi * Y)); + REQUIRE((Y * I) == Y); + REQUIRE((Y * X) == (mi * Z)); + REQUIRE((Y * Y) == I); + REQUIRE((Y * Z) == (i * X)); + REQUIRE((Z * I) == Z); + REQUIRE((Z * X) == (i * Y)); + REQUIRE((Z * Y) == (mi * X)); + REQUIRE((Z * Z) == I); + } + GIVEN("2*IXYZ(I) * -1.5i*XIZ(I)Y") { + QubitPauliMap tensor_a( + {{q0, Pauli::I}, {q1, Pauli::X}, {q2, Pauli::Y}, {q3, Pauli::Z}}); + QubitPauliMap tensor_b( + {{q0, Pauli::X}, {q1, Pauli::I}, {q2, Pauli::Z}, {q4, Pauli::Y}}); + SpCxPauliTensor a(tensor_a, 2.); + SpCxPauliTensor b(tensor_b, -1.5 * i_); + QubitPauliMap tensor_c( + {{q0, Pauli::X}, + {q1, Pauli::X}, + {q2, Pauli::X}, + {q3, Pauli::Z}, + {q4, Pauli::Y}}); + SpCxPauliTensor c(tensor_c, 3.); + REQUIRE((a * b) == c); + } +} + +SCENARIO("Testing multiplication of dense PauliTensor") { + GIVEN("Two Pauli strings with disjoint non-trivial components") { + PauliString a({Pauli::X}); + PauliString b({Pauli::I, Pauli::Y}); + PauliString c({Pauli::X, Pauli::Y}); + REQUIRE((a * b) == c); + } + GIVEN("Multiplying by a trivial Pauli string") { + CxPauliTensor a({Pauli::X}, 2.); + CxPauliTensor b({Pauli::X}, 3. * i_); + REQUIRE((a * CxPauliTensor({}, 1.5 * i_)) == b); + } + GIVEN("Two exactly identical Pauli strings") { + DensePauliMap map = {Pauli::I, Pauli::X, Pauli::Y, Pauli::Z}; + PauliStabiliser a(map, 3); + PauliStabiliser b({}, 2); + REQUIRE((a * a) == b); + } + GIVEN("Each individual Pauli combination") { + PauliStabiliser I({Pauli::I}); + PauliStabiliser X({Pauli::X}); + PauliStabiliser Y({Pauli::Y}); + PauliStabiliser Z({Pauli::Z}); + PauliStabiliser i({}, 1); + PauliStabiliser mi({}, 3); + REQUIRE((I * I) == I); + REQUIRE((I * X) == X); + REQUIRE((I * Y) == Y); + REQUIRE((I * Z) == Z); + REQUIRE((X * I) == X); + REQUIRE((X * X) == I); + REQUIRE((X * Y) == (i * Z)); + REQUIRE((X * Z) == (mi * Y)); + REQUIRE((Y * I) == Y); + REQUIRE((Y * X) == (mi * Z)); + REQUIRE((Y * Y) == I); + REQUIRE((Y * Z) == (i * X)); + REQUIRE((Z * I) == Z); + REQUIRE((Z * X) == (i * Y)); + REQUIRE((Z * Y) == (mi * X)); + REQUIRE((Z * Z) == I); + } + GIVEN("2*IXYZ(I) * -1.5i*XIZ(I)Y") { + DensePauliMap tensor_a({Pauli::I, Pauli::X, Pauli::Y, Pauli::Z}); + DensePauliMap tensor_b({Pauli::X, Pauli::I, Pauli::Z, Pauli::I, Pauli::Y}); + CxPauliTensor a(tensor_a, 2.); + CxPauliTensor b(tensor_b, -1.5 * i_); + DensePauliMap tensor_c({Pauli::X, Pauli::X, Pauli::X, Pauli::Z, Pauli::Y}); + CxPauliTensor c(tensor_c, 3.); + REQUIRE((a * b) == c); + } +} + +SCENARIO("Test hashing for sparse PauliTensor") { + GIVEN("Trivial strings") { + SpPauliString qps1; + SpPauliString qps2; + REQUIRE(qps1.hash_value() == qps2.hash_value()); + WHEN("Add I Pauli") { + qps1.set(Qubit(0), Pauli::I); + REQUIRE(qps1.hash_value() == qps2.hash_value()); + } + } + GIVEN("Nontrivial strings") { + QubitPauliMap qpm{ + {Qubit(0), Pauli::Z}, + {Qubit(1), Pauli::Y}, + {Qubit(2), Pauli::X}, + {Qubit(3), Pauli::I}}; + SpPauliString qps1(qpm); + SpPauliString qps2(qpm); + qps1.set(Qubit(4), Pauli::X); + qps2.set(Qubit(4), Pauli::X); + qps2.set(Qubit(5), Pauli::I); + REQUIRE(qps1.hash_value() == qps2.hash_value()); + } + GIVEN("Trivial tensor") { + SpCxPauliTensor qpt1; + SpCxPauliTensor qpt2; + REQUIRE(qpt1.hash_value() == qpt2.hash_value()); + WHEN("Add I Pauli") { + qpt1.set(Qubit(0), Pauli::I); + REQUIRE(qpt1.hash_value() == qpt2.hash_value()); + } + } + GIVEN("Nontrivial tensors") { + QubitPauliMap qpm{ + {Qubit(0), Pauli::Z}, + {Qubit(1), Pauli::Y}, + {Qubit(2), Pauli::X}, + {Qubit(3), Pauli::I}}; + SpSymPauliTensor qpt1(qpm, .5 * i_); + SpSymPauliTensor qpt2(qpm, .5 * i_); + qpt1.set(Qubit(4), Pauli::X); + qpt2.set(Qubit(4), Pauli::X); + qpt2.set(Qubit(5), Pauli::I); + qpt2.set(Qubit(6), Pauli::I); + REQUIRE(qpt1.hash_value() == qpt2.hash_value()); + } +} + +SCENARIO("Test hashing for dense PauliTensor") { + GIVEN("Trivial strings") { + PauliString qps1; + PauliString qps2; + REQUIRE(qps1.hash_value() == qps2.hash_value()); + WHEN("Add I Pauli") { + qps1.set(0, Pauli::I); + REQUIRE(qps1.hash_value() == qps2.hash_value()); + } + } + GIVEN("Nontrivial strings") { + DensePauliMap qpm{Pauli::Z, Pauli::Y, Pauli::X, Pauli::I}; + PauliString qps1(qpm); + PauliString qps2(qpm); + qps1.set(4, Pauli::X); + qps2.set(4, Pauli::X); + qps2.set(5, Pauli::I); + REQUIRE(qps1.hash_value() == qps2.hash_value()); + } + GIVEN("Stabilisers") { + DensePauliMap pm{Pauli::Z, Pauli::Y, Pauli::X, Pauli::I}; + PauliStabiliser ps1(pm, 2); + PauliStabiliser ps2(pm, 6); + REQUIRE(ps1.hash_value() == ps2.hash_value()); + } + GIVEN("Trivial tensor") { + CxPauliTensor qpt1; + CxPauliTensor qpt2; + REQUIRE(qpt1.hash_value() == qpt2.hash_value()); + WHEN("Add I Pauli") { + qpt1.set(0, Pauli::I); + REQUIRE(qpt1.hash_value() == qpt2.hash_value()); + } + } + GIVEN("Nontrivial tensors") { + DensePauliMap qpm{Pauli::Z, Pauli::Y, Pauli::X, Pauli::I}; + SymPauliTensor qpt1(qpm, .5 * i_); + SymPauliTensor qpt2(qpm, .5 * i_); + qpt1.set(4, Pauli::X); + qpt2.set(4, Pauli::X); + qpt2.set(5, Pauli::I); + qpt2.set(6, Pauli::I); + REQUIRE(qpt1.hash_value() == qpt2.hash_value()); + } +} + +SCENARIO("json serialisation of PauliTensor") { + PauliString xyz({Pauli::X, Pauli::Y, Pauli::Z}); + nlohmann::json j = xyz; + CHECK(j.get() == xyz); + SpPauliString za(Qubit("a", 0), Pauli::Z); + j = za; + CHECK(j.get() == za); + PauliStabiliser zz({Pauli::Z, Pauli::Z}, 3); + j = zz; + CHECK(j.get() == zz); + SpPauliStabiliser ziz({Pauli::Z, Pauli::I, Pauli::Z}, 2); + j = ziz; + CHECK(j.get() == ziz); + CxPauliTensor yiy({Pauli::Y, Pauli::I, Pauli::Y}, 0.2 * i_); + j = yiy; + CHECK(j.get() == yiy); + SpCxPauliTensor xb(Qubit("b", {1, 0}), Pauli::X, -2.3); + j = xb; + CHECK(j.get() == xb); + SymPauliTensor izyx({Pauli::I, Pauli::Z, Pauli::Y, Pauli::X}, Expr("g")); + j = izyx; + CHECK(j.get() == izyx); + SpSymPauliTensor xaxb( + {Qubit("a", 0), Qubit("b")}, {Pauli::X, Pauli::X}, -1.98); + j = xaxb; + CHECK(j.get() == xaxb); +} + +SCENARIO("Test matrix evaluation") { + GIVEN("Default ordering") { + SpPauliString ixs({Qubit(0), Qubit(1)}, {Pauli::I, Pauli::X}); + PauliString ixd({Pauli::I, Pauli::X}); + CmplxSpMat ix(4, 4); + ix.insert(0, 1) = 1.; + ix.insert(1, 0) = 1.; + ix.insert(2, 3) = 1.; + ix.insert(3, 2) = 1.; + // Eigen sparse matrices don't have an equality check, isApprox is the + // nearest + CHECK(ixs.to_sparse_matrix().isApprox(ix)); + CHECK(ixd.to_sparse_matrix().isApprox(ix)); + SpPauliString ixq({{Qubit("b", 0), Pauli::X}, {Qubit("a", 1), Pauli::I}}); + CHECK(ixq.to_sparse_matrix().isApprox(ix)); + } + GIVEN("Padding to n qubits") { + SpPauliString ixs(DensePauliMap{Pauli::I, Pauli::X}); + PauliString ixd({Pauli::I, Pauli::X}); + CmplxSpMat ixi(8, 8); + ixi.insert(0, 2) = 1.; + ixi.insert(1, 3) = 1.; + ixi.insert(2, 0) = 1.; + ixi.insert(3, 1) = 1.; + ixi.insert(4, 6) = 1.; + ixi.insert(5, 7) = 1.; + ixi.insert(6, 4) = 1.; + ixi.insert(7, 5) = 1.; + CHECK(ixs.to_sparse_matrix(3).isApprox(ixi)); + CHECK(ixd.to_sparse_matrix(3).isApprox(ixi)); + } + GIVEN("Custom qubit ordering") { + SpPauliString ixs(DensePauliMap{Pauli::I, Pauli::X}); + PauliString ixd({Pauli::I, Pauli::X}); + CmplxSpMat xi(4, 4); + xi.insert(0, 2) = 1.; + xi.insert(1, 3) = 1.; + xi.insert(2, 0) = 1.; + xi.insert(3, 1) = 1.; + CmplxSpMat ixi(8, 8); + ixi.insert(0, 2) = 1.; + ixi.insert(1, 3) = 1.; + ixi.insert(2, 0) = 1.; + ixi.insert(3, 1) = 1.; + ixi.insert(4, 6) = 1.; + ixi.insert(5, 7) = 1.; + ixi.insert(6, 4) = 1.; + ixi.insert(7, 5) = 1.; + CHECK(ixs.to_sparse_matrix({Qubit(1), Qubit(0)}).isApprox(xi)); + CHECK(ixs.to_sparse_matrix({Qubit(2), Qubit(1), Qubit(0)}).isApprox(ixi)); + CHECK(ixd.to_sparse_matrix({Qubit(1), Qubit(0)}).isApprox(xi)); + CHECK(ixd.to_sparse_matrix({Qubit(2), Qubit(1), Qubit(0)}).isApprox(ixi)); + } + GIVEN("Different strings") { + PauliString xyzd({Pauli::X, Pauli::Y, Pauli::Z}); + CmplxSpMat xyz(8, 8); + xyz.insert(0, 6) = -i_; + xyz.insert(1, 7) = i_; + xyz.insert(2, 4) = i_; + xyz.insert(3, 5) = -i_; + xyz.insert(4, 2) = -i_; + xyz.insert(5, 3) = i_; + xyz.insert(6, 0) = i_; + xyz.insert(7, 1) = -i_; + CHECK(xyzd.to_sparse_matrix().isApprox(xyz)); + } + GIVEN("Different coefficients") { + CmplxSpMat ix(4, 4); + ix.insert(0, 1) = 1.; + ix.insert(1, 0) = 1.; + ix.insert(2, 3) = 1.; + ix.insert(3, 2) = 1.; + DensePauliMap ixd{Pauli::I, Pauli::X}; + CHECK(PauliString(ixd).to_sparse_matrix().isApprox(ix)); + CHECK(PauliStabiliser(ixd, 0).to_sparse_matrix().isApprox(ix)); + CHECK(PauliStabiliser(ixd, 1).to_sparse_matrix().isApprox(i_ * ix)); + CHECK(PauliStabiliser(ixd, 2).to_sparse_matrix().isApprox(-ix)); + CHECK(PauliStabiliser(ixd, 3).to_sparse_matrix().isApprox(-i_ * ix)); + CHECK(CxPauliTensor(ixd, 4.2 + 0.1 * i_) + .to_sparse_matrix() + .isApprox(Complex(4.2 + 0.1 * i_) * ix)); + CHECK(SymPauliTensor(ixd, 4.2 + 0.1 * i_) + .to_sparse_matrix() + .isApprox(Complex(4.2 + 0.1 * i_) * ix)); + } +} + +} // namespace test_PauliTensor +} // namespace tket diff --git a/tket/test/src/test_Predicates.cpp b/tket/test/src/test_Predicates.cpp index b98756abae..6117591837 100644 --- a/tket/test/src/test_Predicates.cpp +++ b/tket/test/src/test_Predicates.cpp @@ -270,7 +270,7 @@ SCENARIO("Test CliffordCircuitPredicate") { CircBox cbox(circ); Circuit circ1(8); circ1.add_box(cbox, {0, 1, 2, 3, 4, 5, 6, 7}); - PauliExpBox pebox({Pauli::Y, Pauli::Z}, 0.5); + PauliExpBox pebox(SymPauliTensor({Pauli::Y, Pauli::Z}, 0.5)); circ1.add_box(pebox, {0, 1}); Circuit setup(2); Sym a = SymTable::fresh_symbol("a"); diff --git a/tket/test/src/test_UnitaryTableau.cpp b/tket/test/src/test_UnitaryTableau.cpp index 1279980d60..68cd333c23 100644 --- a/tket/test/src/test_UnitaryTableau.cpp +++ b/tket/test/src/test_UnitaryTableau.cpp @@ -115,7 +115,7 @@ SCENARIO("Correct updates of SymplecticTableau") { SymplecticTableau tab2 = get_initial_stab_destab_tab(); tab0.apply_S(0); tab1.apply_pauli_gadget( - PauliStabiliser({Pauli::Z, Pauli::I, Pauli::I}, true), 1); + PauliStabiliser({Pauli::Z, Pauli::I, Pauli::I}, 0), 1); tab2.apply_gate(OpType::S, {0}); std::stringstream tabstr; tabstr << tab0; @@ -140,7 +140,7 @@ SCENARIO("Correct updates of SymplecticTableau") { SymplecticTableau tab2 = get_initial_stab_destab_tab(); tab0.apply_V(0); tab1.apply_pauli_gadget( - PauliStabiliser({Pauli::X, Pauli::I, Pauli::I}, true), 1); + PauliStabiliser({Pauli::X, Pauli::I, Pauli::I}, 0), 1); tab2.apply_gate(OpType::V, {0}); std::stringstream tabstr; tabstr << tab0; @@ -165,17 +165,17 @@ SCENARIO("Correct updates of SymplecticTableau") { SymplecticTableau tab3 = get_initial_stab_destab_tab(); tab0.apply_CX(0, 1); tab1.apply_pauli_gadget( - PauliStabiliser({Pauli::Z, Pauli::I, Pauli::I}, true), 1); + PauliStabiliser({Pauli::Z, Pauli::I, Pauli::I}, 0), 1); tab1.apply_pauli_gadget( - PauliStabiliser({Pauli::I, Pauli::X, Pauli::I}, true), 1); + PauliStabiliser({Pauli::I, Pauli::X, Pauli::I}, 0), 1); tab1.apply_pauli_gadget( - PauliStabiliser({Pauli::Z, Pauli::X, Pauli::I}, false), 1); + PauliStabiliser({Pauli::Z, Pauli::X, Pauli::I}, 2), 1); tab2.apply_pauli_gadget( - PauliStabiliser({Pauli::Z, Pauli::I, Pauli::I}, true), 3); + PauliStabiliser({Pauli::Z, Pauli::I, Pauli::I}, 0), 3); tab2.apply_pauli_gadget( - PauliStabiliser({Pauli::I, Pauli::X, Pauli::I}, true), 3); + PauliStabiliser({Pauli::I, Pauli::X, Pauli::I}, 0), 3); tab2.apply_pauli_gadget( - PauliStabiliser({Pauli::Z, Pauli::X, Pauli::I}, false), 3); + PauliStabiliser({Pauli::Z, Pauli::X, Pauli::I}, 2), 3); tab3.apply_gate(OpType::CX, {0, 1}); std::stringstream tabstr; tabstr << tab0; @@ -196,12 +196,12 @@ SCENARIO("Correct updates of SymplecticTableau") { SCENARIO("Correct creation of UnitaryTableau") { GIVEN("An identity circuit") { UnitaryTableau tab(3); - REQUIRE(tab.get_zrow(Qubit(0)) == QubitPauliTensor(Qubit(0), Pauli::Z, 1.)); - REQUIRE(tab.get_zrow(Qubit(1)) == QubitPauliTensor(Qubit(1), Pauli::Z, 1.)); - REQUIRE(tab.get_zrow(Qubit(2)) == QubitPauliTensor(Qubit(2), Pauli::Z, 1.)); - REQUIRE(tab.get_xrow(Qubit(0)) == QubitPauliTensor(Qubit(0), Pauli::X, 1.)); - REQUIRE(tab.get_xrow(Qubit(1)) == QubitPauliTensor(Qubit(1), Pauli::X, 1.)); - REQUIRE(tab.get_xrow(Qubit(2)) == QubitPauliTensor(Qubit(2), Pauli::X, 1.)); + REQUIRE(tab.get_zrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::Z)); + REQUIRE(tab.get_zrow(Qubit(1)) == SpPauliStabiliser(Qubit(1), Pauli::Z)); + REQUIRE(tab.get_zrow(Qubit(2)) == SpPauliStabiliser(Qubit(2), Pauli::Z)); + REQUIRE(tab.get_xrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::X)); + REQUIRE(tab.get_xrow(Qubit(1)) == SpPauliStabiliser(Qubit(1), Pauli::X)); + REQUIRE(tab.get_xrow(Qubit(2)) == SpPauliStabiliser(Qubit(2), Pauli::X)); } GIVEN("A single S gate") { UnitaryTableau tab0(3); @@ -214,11 +214,11 @@ SCENARIO("Correct creation of UnitaryTableau") { tab1.apply_S_at_end(Qubit(0)); tab2.apply_gate_at_front(OpType::S, {Qubit(0)}); tab3.apply_gate_at_end(OpType::S, {Qubit(0)}); - tab4.apply_pauli_at_front(QubitPauliTensor(Qubit(0), Pauli::Z), 1); - tab5.apply_pauli_at_end(QubitPauliTensor(Qubit(0), Pauli::Z), 1); + tab4.apply_pauli_at_front(SpPauliStabiliser(Qubit(0), Pauli::Z), 1); + tab5.apply_pauli_at_end(SpPauliStabiliser(Qubit(0), Pauli::Z), 1); // Phases should match those in the tests for SymplecticTableau - CHECK(tab0.get_zrow(Qubit(0)) == QubitPauliTensor(Qubit(0), Pauli::Z, 1.)); - CHECK(tab0.get_xrow(Qubit(0)) == QubitPauliTensor(Qubit(0), Pauli::Y, 1.)); + CHECK(tab0.get_zrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::Z)); + CHECK(tab0.get_xrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::Y)); CHECK(tab0 == tab1); CHECK(tab0 == tab2); CHECK(tab0 == tab3); @@ -236,10 +236,10 @@ SCENARIO("Correct creation of UnitaryTableau") { tab1.apply_V_at_end(Qubit(0)); tab2.apply_gate_at_front(OpType::V, {Qubit(0)}); tab3.apply_gate_at_end(OpType::V, {Qubit(0)}); - tab4.apply_pauli_at_front(QubitPauliTensor(Qubit(0), Pauli::X), 1); - tab5.apply_pauli_at_end(QubitPauliTensor(Qubit(0), Pauli::X), 1); - CHECK(tab0.get_zrow(Qubit(0)) == QubitPauliTensor(Qubit(0), Pauli::Y, -1.)); - CHECK(tab0.get_xrow(Qubit(0)) == QubitPauliTensor(Qubit(0), Pauli::X, 1.)); + tab4.apply_pauli_at_front(SpPauliStabiliser(Qubit(0), Pauli::X), 1); + tab5.apply_pauli_at_end(SpPauliStabiliser(Qubit(0), Pauli::X), 1); + CHECK(tab0.get_zrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::Y, 2)); + CHECK(tab0.get_xrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::X, 0)); CHECK(tab0 == tab1); CHECK(tab0 == tab2); CHECK(tab0 == tab3); @@ -259,8 +259,8 @@ SCENARIO("Correct creation of UnitaryTableau") { tab3.apply_gate_at_end(OpType::Vdg, {Qubit(0)}); tab3.apply_gate_at_end(OpType::Sdg, {Qubit(0)}); tab3.apply_gate_at_end(OpType::Vdg, {Qubit(0)}); - CHECK(tab0.get_zrow(Qubit(0)) == QubitPauliTensor(Qubit(0), Pauli::X, 1.)); - CHECK(tab0.get_xrow(Qubit(0)) == QubitPauliTensor(Qubit(0), Pauli::Z, 1.)); + CHECK(tab0.get_zrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::X)); + CHECK(tab0.get_xrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::Z)); CHECK(tab0 == tab1); CHECK(tab0 == tab2); CHECK(tab0 == tab3); @@ -272,22 +272,22 @@ SCENARIO("Correct creation of UnitaryTableau") { UnitaryTableau tab3(3); tab0.apply_CX_at_front(Qubit(0), Qubit(1)); tab1.apply_CX_at_end(Qubit(0), Qubit(1)); - tab2.apply_pauli_at_front(QubitPauliTensor(Qubit(0), Pauli::Z), 1); - tab2.apply_pauli_at_front(QubitPauliTensor(Qubit(1), Pauli::X), 1); + tab2.apply_pauli_at_front(SpPauliStabiliser(Qubit(0), Pauli::Z), 1); + tab2.apply_pauli_at_front(SpPauliStabiliser(Qubit(1), Pauli::X), 1); tab2.apply_pauli_at_front( - QubitPauliTensor(std::list{Pauli::Z, Pauli::X}), 3); - tab3.apply_pauli_at_end(QubitPauliTensor(Qubit(0), Pauli::Z), 3); - tab3.apply_pauli_at_end(QubitPauliTensor(Qubit(1), Pauli::X), 3); + SpPauliStabiliser(DensePauliMap{Pauli::Z, Pauli::X}), 3); + tab3.apply_pauli_at_end(SpPauliStabiliser(Qubit(0), Pauli::Z), 3); + tab3.apply_pauli_at_end(SpPauliStabiliser(Qubit(1), Pauli::X), 3); tab3.apply_pauli_at_end( - QubitPauliTensor(std::list{Pauli::Z, Pauli::X}), 1); - CHECK(tab0.get_zrow(Qubit(0)) == QubitPauliTensor(Qubit(0), Pauli::Z, 1.)); - CHECK(tab0.get_xrow(Qubit(1)) == QubitPauliTensor(Qubit(1), Pauli::X, 1.)); + SpPauliStabiliser(DensePauliMap{Pauli::Z, Pauli::X}), 1); + CHECK(tab0.get_zrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::Z)); + CHECK(tab0.get_xrow(Qubit(1)) == SpPauliStabiliser(Qubit(1), Pauli::X)); CHECK( tab0.get_zrow(Qubit(1)) == - QubitPauliTensor(std::list({Pauli::Z, Pauli::Z}))); + SpPauliStabiliser(DensePauliMap{Pauli::Z, Pauli::Z})); CHECK( tab0.get_xrow(Qubit(0)) == - QubitPauliTensor(std::list({Pauli::X, Pauli::X}))); + SpPauliStabiliser(DensePauliMap{Pauli::X, Pauli::X})); CHECK(tab0 == tab1); CHECK(tab0 == tab2); CHECK(tab0 == tab3); @@ -301,8 +301,7 @@ SCENARIO("Correct creation of UnitaryTableau") { GIVEN("A PI/2 rotation") { Circuit circ = get_test_circ(); UnitaryTableau tab = circuit_to_unitary_tableau(circ); - QubitPauliTensor pauli = - QubitPauliTensor(std::list{Pauli::X, Pauli::Y, Pauli::Z}); + SpPauliStabiliser pauli(DensePauliMap{Pauli::X, Pauli::Y, Pauli::Z}); tab.apply_pauli_at_end(pauli, 3); add_ops_list_two_to_circuit(circ, OpType::Sdg); @@ -311,8 +310,7 @@ SCENARIO("Correct creation of UnitaryTableau") { } GIVEN("A PI/2 rotation at front") { UnitaryTableau tab = get_tableau_with_gates_applied_at_front(); - QubitPauliTensor pauli = - QubitPauliTensor(std::list({Pauli::X, Pauli::Y, Pauli::Z})); + SpPauliStabiliser pauli(DensePauliMap{Pauli::X, Pauli::Y, Pauli::Z}); tab.apply_pauli_at_front(pauli, 1); Circuit circ(3); @@ -366,12 +364,12 @@ SCENARIO("Synthesis of circuits from UnitaryTableau") { SCENARIO("Correct creation of UnitaryRevTableau") { GIVEN("An identity circuit") { UnitaryRevTableau tab(3); - REQUIRE(tab.get_zrow(Qubit(0)) == QubitPauliTensor(Qubit(0), Pauli::Z, 1.)); - REQUIRE(tab.get_zrow(Qubit(1)) == QubitPauliTensor(Qubit(1), Pauli::Z, 1.)); - REQUIRE(tab.get_zrow(Qubit(2)) == QubitPauliTensor(Qubit(2), Pauli::Z, 1.)); - REQUIRE(tab.get_xrow(Qubit(0)) == QubitPauliTensor(Qubit(0), Pauli::X, 1.)); - REQUIRE(tab.get_xrow(Qubit(1)) == QubitPauliTensor(Qubit(1), Pauli::X, 1.)); - REQUIRE(tab.get_xrow(Qubit(2)) == QubitPauliTensor(Qubit(2), Pauli::X, 1.)); + REQUIRE(tab.get_zrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::Z)); + REQUIRE(tab.get_zrow(Qubit(1)) == SpPauliStabiliser(Qubit(1), Pauli::Z)); + REQUIRE(tab.get_zrow(Qubit(2)) == SpPauliStabiliser(Qubit(2), Pauli::Z)); + REQUIRE(tab.get_xrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::X)); + REQUIRE(tab.get_xrow(Qubit(1)) == SpPauliStabiliser(Qubit(1), Pauli::X)); + REQUIRE(tab.get_xrow(Qubit(2)) == SpPauliStabiliser(Qubit(2), Pauli::X)); } GIVEN("A single S gate") { UnitaryRevTableau tab0(3); @@ -384,14 +382,14 @@ SCENARIO("Correct creation of UnitaryRevTableau") { tab1.apply_S_at_front(Qubit(0)); tab2.apply_gate_at_end(OpType::S, {Qubit(0)}); tab3.apply_gate_at_front(OpType::S, {Qubit(0)}); - tab4.apply_pauli_at_end(QubitPauliTensor(Qubit(0), Pauli::Z), 1); - tab5.apply_pauli_at_front(QubitPauliTensor(Qubit(0), Pauli::Z), 1); + tab4.apply_pauli_at_end(SpPauliStabiliser(Qubit(0), Pauli::Z), 1); + tab5.apply_pauli_at_front(SpPauliStabiliser(Qubit(0), Pauli::Z), 1); // Reading the stabilizers in the reverse direction changes how we apply the // Pauli reorder rules to determine the correct phase: // U Q e^{-i P pi/4} = U e^{-i P pi/4}. // (iPQ) iZX = -Y - CHECK(tab0.get_zrow(Qubit(0)) == QubitPauliTensor(Qubit(0), Pauli::Z, 1.)); - CHECK(tab0.get_xrow(Qubit(0)) == QubitPauliTensor(Qubit(0), Pauli::Y, -1.)); + CHECK(tab0.get_zrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::Z, 0)); + CHECK(tab0.get_xrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::Y, 2)); CHECK(tab0 == tab1); CHECK(tab0 == tab2); CHECK(tab0 == tab3); @@ -409,11 +407,11 @@ SCENARIO("Correct creation of UnitaryRevTableau") { tab1.apply_V_at_front(Qubit(0)); tab2.apply_gate_at_end(OpType::V, {Qubit(0)}); tab3.apply_gate_at_front(OpType::V, {Qubit(0)}); - tab4.apply_pauli_at_end(QubitPauliTensor(Qubit(0), Pauli::X), 1); - tab5.apply_pauli_at_front(QubitPauliTensor(Qubit(0), Pauli::X), 1); + tab4.apply_pauli_at_end(SpPauliStabiliser(Qubit(0), Pauli::X), 1); + tab5.apply_pauli_at_front(SpPauliStabiliser(Qubit(0), Pauli::X), 1); // iXZ = +Y - CHECK(tab0.get_zrow(Qubit(0)) == QubitPauliTensor(Qubit(0), Pauli::Y, 1.)); - CHECK(tab0.get_xrow(Qubit(0)) == QubitPauliTensor(Qubit(0), Pauli::X, 1.)); + CHECK(tab0.get_zrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::Y)); + CHECK(tab0.get_xrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::X)); CHECK(tab0 == tab1); CHECK(tab0 == tab2); CHECK(tab0 == tab3); @@ -425,10 +423,8 @@ SCENARIO("Correct creation of UnitaryRevTableau") { UnitaryRevTableau tab1(3); tab0.apply_gate_at_end(OpType::H, {Qubit(0)}); tab1.apply_gate_at_front(OpType::H, {Qubit(0)}); - REQUIRE( - tab0.get_zrow(Qubit(0)) == QubitPauliTensor(Qubit(0), Pauli::X, 1.)); - REQUIRE( - tab0.get_xrow(Qubit(0)) == QubitPauliTensor(Qubit(0), Pauli::Z, 1.)); + REQUIRE(tab0.get_zrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::X)); + REQUIRE(tab0.get_xrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::Z)); REQUIRE(tab0 == tab1); } GIVEN("A single CX gate") { @@ -436,16 +432,14 @@ SCENARIO("Correct creation of UnitaryRevTableau") { UnitaryRevTableau tab1(3); tab0.apply_CX_at_end(Qubit(0), Qubit(1)); tab1.apply_CX_at_front(Qubit(0), Qubit(1)); - REQUIRE( - tab0.get_zrow(Qubit(0)) == QubitPauliTensor(Qubit(0), Pauli::Z, 1.)); - REQUIRE( - tab0.get_xrow(Qubit(1)) == QubitPauliTensor(Qubit(1), Pauli::X, 1.)); + REQUIRE(tab0.get_zrow(Qubit(0)) == SpPauliStabiliser(Qubit(0), Pauli::Z)); + REQUIRE(tab0.get_xrow(Qubit(1)) == SpPauliStabiliser(Qubit(1), Pauli::X)); REQUIRE( tab0.get_zrow(Qubit(1)) == - QubitPauliTensor(std::list({Pauli::Z, Pauli::Z}))); + SpPauliStabiliser(DensePauliMap{Pauli::Z, Pauli::Z})); REQUIRE( tab0.get_xrow(Qubit(0)) == - QubitPauliTensor(std::list({Pauli::X, Pauli::X}))); + SpPauliStabiliser(DensePauliMap{Pauli::X, Pauli::X})); REQUIRE(tab0 == tab1); std::stringstream tabstr; tabstr << tab0; @@ -468,8 +462,7 @@ SCENARIO("Correct creation of UnitaryRevTableau") { GIVEN("A PI/2 rotation") { Circuit circ = get_test_circ(); UnitaryRevTableau tab = circuit_to_unitary_rev_tableau(circ); - QubitPauliTensor pauli = - QubitPauliTensor(std::list{Pauli::X, Pauli::Y, Pauli::Z}); + SpPauliStabiliser pauli(DensePauliMap{Pauli::X, Pauli::Y, Pauli::Z}); tab.apply_pauli_at_end(pauli, 3); add_ops_list_two_to_circuit(circ, OpType::Sdg); @@ -478,8 +471,7 @@ SCENARIO("Correct creation of UnitaryRevTableau") { } GIVEN("A PI/2 rotation at front") { UnitaryRevTableau tab = get_rev_tableau_with_gates_applied_at_front(); - QubitPauliTensor pauli = - QubitPauliTensor(std::list({Pauli::X, Pauli::Y, Pauli::Z})); + SpPauliStabiliser pauli(DensePauliMap{Pauli::X, Pauli::Y, Pauli::Z}); tab.apply_pauli_at_front(pauli, 1); Circuit circ(3); @@ -595,19 +587,19 @@ SCENARIO("Unitary inversions") { SCENARIO("Compare SymplecticTableau and UnitaryTableau") { GIVEN("The same sequence of gates, compare string representations") { - SymplecticTableau stab(PauliStabiliserList{ - {{Pauli::X, Pauli::I, Pauli::I}, true}, - {{Pauli::I, Pauli::X, Pauli::I}, true}, - {{Pauli::I, Pauli::I, Pauli::X}, true}, - {{Pauli::Z, Pauli::I, Pauli::I}, true}, - {{Pauli::I, Pauli::Z, Pauli::I}, true}, - {{Pauli::I, Pauli::I, Pauli::Z}, true}, + SymplecticTableau stab(PauliStabiliserVec{ + {DensePauliMap{Pauli::X, Pauli::I, Pauli::I}}, + {DensePauliMap{Pauli::I, Pauli::X, Pauli::I}}, + {DensePauliMap{Pauli::I, Pauli::I, Pauli::X}}, + {DensePauliMap{Pauli::Z, Pauli::I, Pauli::I}}, + {DensePauliMap{Pauli::I, Pauli::Z, Pauli::I}}, + {DensePauliMap{Pauli::I, Pauli::I, Pauli::Z}}, }); // Paulis cancel with subsequent gadget stab.apply_gate(OpType::X, uvec{0}); stab.apply_gate(OpType::Y, uvec{1}); stab.apply_gate(OpType::Z, uvec{2}); - stab.apply_pauli_gadget({{Pauli::X, Pauli::Y, Pauli::Z}, true}, 2); + stab.apply_pauli_gadget({DensePauliMap{Pauli::X, Pauli::Y, Pauli::Z}}, 2); // CY and CZ combine to Sdg(0), CX(0, 1) stab.apply_gate(OpType::CY, uvec{0, 1}); stab.apply_gate(OpType::CZ, uvec{0, 1}); @@ -631,8 +623,7 @@ SCENARIO("Compare SymplecticTableau and UnitaryTableau") { utab.apply_gate_at_front(OpType::SWAP, {Qubit(1), Qubit(2)}); utab.apply_gate_at_front(OpType::CZ, {Qubit(0), Qubit(1)}); utab.apply_gate_at_front(OpType::CY, {Qubit(0), Qubit(1)}); - utab.apply_pauli_at_front( - QubitPauliTensor{{Pauli::X, Pauli::Y, Pauli::Z}}, 2); + utab.apply_pauli_at_front({DensePauliMap{Pauli::X, Pauli::Y, Pauli::Z}}, 2); utab.apply_gate_at_front(OpType::X, {Qubit(0)}); utab.apply_gate_at_front(OpType::Y, {Qubit(1)}); utab.apply_gate_at_front(OpType::Z, {Qubit(2)}); diff --git a/tket/test/src/test_json.cpp b/tket/test/src/test_json.cpp index 0345e83ad1..13849fcca9 100644 --- a/tket/test/src/test_json.cpp +++ b/tket/test/src/test_json.cpp @@ -322,7 +322,7 @@ SCENARIO("Test Circuit serialization") { GIVEN("PauliExpBoxes") { Circuit c(4, 2, "paulibox"); PauliExpBox pbox( - {Pauli::X, Pauli::Y, Pauli::I, Pauli::Z}, -0.72521, + {{Pauli::X, Pauli::Y, Pauli::I, Pauli::Z}, -0.72521}, CXConfigType::MultiQGate); c.add_box(pbox, {0, 1, 2, 3}); nlohmann::json j_pbox = c; @@ -340,8 +340,8 @@ SCENARIO("Test Circuit serialization") { GIVEN("PauliExpPairBoxes") { Circuit c(4, 2, "paulipairbox"); PauliExpPairBox pbox( - {Pauli::X, Pauli::Y, Pauli::I, Pauli::Z}, -0.72521, - {Pauli::X, Pauli::I, Pauli::I, Pauli::X}, -0.32421, + {{Pauli::X, Pauli::Y, Pauli::I, Pauli::Z}, -0.72521}, + {{Pauli::X, Pauli::I, Pauli::I, Pauli::X}, -0.32421}, CXConfigType::MultiQGate); c.add_box(pbox, {0, 1, 2, 3}); nlohmann::json j_pbox = c; @@ -1147,12 +1147,12 @@ SCENARIO("Test compiler pass combinator serializations") { } } -SCENARIO("Test QubitPauliString serialization") { - QubitPauliString qps( +SCENARIO("Test PauliTensor serialization") { + SpPauliString qps( {{Qubit(2), Pauli::X}, {Qubit(7), Pauli::Y}, {Qubit(0), Pauli::I}}); nlohmann::json j_qps = qps; - QubitPauliString new_qps = j_qps.get(); + SpPauliString new_qps = j_qps.get(); REQUIRE(qps == new_qps); } @@ -1194,12 +1194,12 @@ SCENARIO("Test MeasurementSetup serializations") { ms.add_measurement_circuit(mc2); Qubit q0(q_default_reg(), 0); Qubit q1(q_default_reg(), 1); - QubitPauliString ii; - QubitPauliString zi({{q0, Pauli::Z}}); - QubitPauliString iz({{q1, Pauli::Z}}); - QubitPauliString zz({{q0, Pauli::Z}, {q1, Pauli::Z}}); - QubitPauliString xx({{q0, Pauli::X}, {q1, Pauli::X}}); - QubitPauliString yy({{q0, Pauli::Y}, {q1, Pauli::Y}}); + QubitPauliMap ii; + QubitPauliMap zi({{q0, Pauli::Z}}); + QubitPauliMap iz({{q1, Pauli::Z}}); + QubitPauliMap zz({{q0, Pauli::Z}, {q1, Pauli::Z}}); + QubitPauliMap xx({{q0, Pauli::X}, {q1, Pauli::X}}); + QubitPauliMap yy({{q0, Pauli::Y}, {q1, Pauli::Y}}); ms.add_result_for_term(ii, {0, {}, false}); ms.add_result_for_term(zi, {0, {0}, false}); ms.add_result_for_term(iz, {0, {1}, false});