From 5e21d78a1c5254be8275b5c8519d1162d4271c5a Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Thu, 22 Feb 2024 11:00:58 +0000 Subject: [PATCH] [feature] Methods for adding a `CircBox` registerwise (#1260) --- pytket/CMakeLists.txt | 1 + pytket/binders/circuit/Circuit/add_op.cpp | 157 ++++++++++++++++++- pytket/binders/circuit/Circuit/main.cpp | 50 +----- pytket/binders/include/circuit_registers.hpp | 59 +++++++ pytket/conanfile.py | 2 +- pytket/docs/changelog.rst | 11 ++ pytket/pytket/_tket/circuit.pyi | 30 +++- pytket/tests/circuit_test.py | 67 ++++++++ tket/conanfile.py | 2 +- tket/include/tket/Circuit/Boxes.hpp | 2 +- tket/src/Circuit/Boxes.cpp | 1 - tket/src/Circuit/macro_manipulation.cpp | 1 + tket/test/src/Circuit/test_Boxes.cpp | 29 ++-- tket/test/src/Circuit/test_Circ.cpp | 14 +- 14 files changed, 352 insertions(+), 74 deletions(-) create mode 100644 pytket/binders/include/circuit_registers.hpp diff --git a/pytket/CMakeLists.txt b/pytket/CMakeLists.txt index 5c2e5360a1..455fc2d297 100644 --- a/pytket/CMakeLists.txt +++ b/pytket/CMakeLists.txt @@ -102,6 +102,7 @@ set(HEADER_FILES binders/include/add_gate.hpp binders/include/binder_json.hpp binders/include/binder_utils.hpp + binders/include/circuit_registers.hpp binders/include/deleted_hash.hpp binders/include/py_operators.hpp binders/include/typecast.hpp diff --git a/pytket/binders/circuit/Circuit/add_op.cpp b/pytket/binders/circuit/Circuit/add_op.cpp index d66c30b607..acb10b5538 100644 --- a/pytket/binders/circuit/Circuit/add_op.cpp +++ b/pytket/binders/circuit/Circuit/add_op.cpp @@ -20,6 +20,7 @@ #include "UnitRegister.hpp" #include "add_gate.hpp" +#include "circuit_registers.hpp" #include "tket/Circuit/Boxes.hpp" #include "tket/Circuit/Circuit.hpp" #include "tket/Circuit/ClassicalExpBox.hpp" @@ -41,6 +42,8 @@ namespace tket { typedef py::tket_custom::SequenceVec py_unit_vector_t; typedef py::tket_custom::SequenceVec py_bit_vector_t; typedef py::tket_custom::SequenceVec py_qubit_vector_t; +typedef py::tket_custom::SequenceVec py_qreg_vector_t; +typedef py::tket_custom::SequenceVec py_creg_vector_t; const bit_vector_t no_bits; @@ -219,9 +222,13 @@ void init_circuit_add_op(py::class_> &c) { return add_box_method( circ, std::make_shared(box), args, kwargs); }, - "Append a :py:class:`CircBox` to the circuit.\n\n:param " - "circbox: The box to append\n:param args: Indices of the " - "qubits/bits to append the box to" + "Append a :py:class:`CircBox` to the circuit." + "\n\nThe qubits and bits of the :py:class:`CircBox` are wired into " + "the circuit in lexicographic order. Bits follow qubits in the order " + "of arguments." + "\n\n:param circbox: The box to append" + "\n:param args: Indices of the (default-register) qubits/bits to " + "append the box to" "\n:return: the new :py:class:`Circuit`", py::arg("circbox"), py::arg("args")) .def( @@ -517,11 +524,149 @@ void init_circuit_add_op(py::class_> &c) { return add_box_method( circ, std::make_shared(box), args, kwargs); }, - "Append a :py:class:`CircBox` to the circuit.\n\n:param " - "circbox: The box to append\n:param args: The qubits/bits " - "to append the box to" + "Append a :py:class:`CircBox` to the circuit." + "\n\nThe qubits and bits of the :py:class:`CircBox` are wired into " + "the circuit in lexicographic order. Bits follow qubits in the order " + "of arguments." + "\n\n:param circbox: The box to append" + "\n:param args: The qubits/bits to append the box to" "\n:return: the new :py:class:`Circuit`", py::arg("circbox"), py::arg("args")) + .def( + "add_circbox_regwise", + [](Circuit *circ, const CircBox &box, const py_qreg_vector_t &qregs, + const py_creg_vector_t &cregs, const py::kwargs &kwargs) { + std::vector args; + + for (const QubitRegister &qreg : qregs) { + const std::string &name = qreg.name(); + for (std::size_t i = 0; i < qreg.size(); i++) { + args.push_back(Qubit(name, i)); + } + } + for (const BitRegister &creg : cregs) { + const std::string &name = creg.name(); + for (std::size_t i = 0; i < creg.size(); i++) { + args.push_back(Bit(name, i)); + } + } + return add_box_method( + circ, std::make_shared(box), args, kwargs); + }, + "Append a :py:class:`CircBox` to the circuit, wiring whole registers " + "together." + "\n\n:param circbox: The box to append" + "\n:param qregs: Sequence of :py:class:`QubitRegister` from the " + "outer :py:class:`Circuit`, the order corresponding to the " + "lexicographic order of corresponding registers in the " + ":py:class:`CircBox`" + "\n:param cregs: Sequence of :py:class:`BitRegister` from the " + "outer :py:class:`Circuit`, the order corresponding to the " + "lexicographic order of corresponding registers in the " + ":py:class:`CircBox`" + "\n:return: the new :py:class:`Circuit`", + py::arg("circbox"), py::arg("qregs"), py::arg("cregs")) + .def( + "add_circbox_with_regmap", + [](Circuit *circ, const CircBox &box, + const std::map &qregmap, + const std::map &cregmap, + const py::kwargs &kwargs) { + // Get registers of circuit: + std::vector circ_qregs = + get_unit_registers(*circ); + std::vector circ_cregs = + get_unit_registers(*circ); + + // Map name -> size for circuit registers: + std::map circ_qreg_sizes; + for (const QubitRegister &qreg : circ_qregs) { + circ_qreg_sizes[qreg.name()] = qreg.size(); + } + std::map circ_creg_sizes; + for (const BitRegister &creg : circ_cregs) { + circ_creg_sizes[creg.name()] = creg.size(); + } + + // Get box circuit: + std::shared_ptr box_circ = box.to_circuit(); + + // Get units of box (in lexicographic order): + const qubit_vector_t box_qubits = box_circ->all_qubits(); + const bit_vector_t box_bits = box_circ->all_bits(); + + // Get registers of box: + std::vector box_qregs = + get_unit_registers(*box_circ); + std::vector box_cregs = + get_unit_registers(*box_circ); + + // Map name -> size for box registers + std::map box_qreg_sizes; + for (const QubitRegister &qreg : box_qregs) { + box_qreg_sizes[qreg.name()] = qreg.size(); + } + std::map box_creg_sizes; + for (const BitRegister &creg : box_cregs) { + box_creg_sizes[creg.name()] = creg.size(); + } + + // Check that all units in the box are in a register: + for (const Qubit &qb : box_qubits) { + if (box_qreg_sizes.find(qb.reg_name()) == box_qreg_sizes.end()) { + throw CircuitInvalidity("Box contains non-register qubits."); + } + } + for (const Bit &cb : box_bits) { + if (box_creg_sizes.find(cb.reg_name()) == box_creg_sizes.end()) { + throw CircuitInvalidity("Box contains non-register bits."); + } + } + + // Check that the sizes of the registers match up: + for (const auto &pair : box_qreg_sizes) { + if (circ_qreg_sizes.at(qregmap.at(pair.first)) != pair.second) { + throw CircuitInvalidity("Size mismatch in qubit registers"); + } + } + for (const auto &pair : box_creg_sizes) { + if (circ_creg_sizes.at(cregmap.at(pair.first)) != pair.second) { + throw CircuitInvalidity("Size mismatch in bit registers"); + } + } + + // Populate the arguments (qubits then bits). Note that they are in + // lexicographic order; when the box is inserted into the circuit + // (in Circuit::substitute_box_vertex()) the units are also + // connected in lexicographic order. + std::vector args; + for (const Qubit &qb : box_qubits) { + args.push_back(Qubit(qregmap.at(qb.reg_name()), qb.index()[0])); + } + for (const Bit &cb : box_bits) { + args.push_back(Bit(cregmap.at(cb.reg_name()), cb.index()[0])); + } + + // Add the box: + return add_box_method( + circ, std::make_shared(box), args, kwargs); + }, + "Append a :py:class:`CircBox` to the circuit, wiring whole registers " + "together." + "\n\nThis method expects two maps (one for qubit registers and one " + "for bit registers), which must have keys corresponding to all " + "register names in the box. The box may not contain any qubits or " + "bits that do not belong to a register, i.e. all must be single-" + "indexed contiguously from zero." + "\n\n:param circbox: The box to append" + "\n:param qregmap: Map specifying which qubit register in the " + ":py:class:`CircBox` (the map's keys) matches which register in the " + "outer circuit (the map's values)" + "\n:param cregmap: Map specifying which bit register in the " + ":py:class:`CircBox` (the map's keys) matches which register in the " + "outer circuit (the map's values)" + "\n:return: the new :py:class:`Circuit`", + py::arg("circbox"), py::arg("qregmap"), py::arg("cregmap")) .def( "add_unitary1qbox", [](Circuit *circ, const Unitary1qBox &box, const Qubit &q0, diff --git a/pytket/binders/circuit/Circuit/main.cpp b/pytket/binders/circuit/Circuit/main.cpp index a0b64b8170..69f5eae586 100644 --- a/pytket/binders/circuit/Circuit/main.cpp +++ b/pytket/binders/circuit/Circuit/main.cpp @@ -24,6 +24,7 @@ #include "UnitRegister.hpp" #include "binder_json.hpp" #include "boost/graph/iteration_macros.hpp" +#include "circuit_registers.hpp" #include "deleted_hash.hpp" #include "py_operators.hpp" #include "tket/Circuit/Boxes.hpp" @@ -58,43 +59,6 @@ UnitID to_cpp_unitid(const PyUnitID &py_unitid) { return get(py_unitid); } -template -std::vector get_unit_registers(Circuit &circ) { - static_assert( - std::is_same::value || - std::is_same::value, - "T must be either QubitRegister or BitRegister"); - using T2 = typename std::conditional< - std::is_same::value, Qubit, Bit>::type; - std::vector unitids; - if constexpr (std::is_same::value) { - unitids = circ.all_qubits(); - } else if constexpr (std::is_same::value) { - unitids = circ.all_bits(); - } - // map from register name to unsigned indices - std::map> unit_map; - std::vector regs; - for (const T2 &unitid : unitids) { - // UnitRegisters only describe registers with 1-d indices - if (unitid.reg_dim() > 1) continue; - auto it = unit_map.find(unitid.reg_name()); - if (it == unit_map.end()) { - unit_map.insert({unitid.reg_name(), {unitid.index()[0]}}); - } else { - it->second.insert(unitid.index()[0]); - } - } - regs.reserve(unit_map.size()); - for (auto const &it : unit_map) { - // only return registers that are indexed consecutively from zero - if (*it.second.rbegin() == it.second.size() - 1) { - regs.emplace_back(it.first, it.second.size()); - } - } - return regs; -} - void init_circuit_add_op(py::class_> &c); void init_circuit_add_classical_op( py::class_> &c); @@ -291,10 +255,8 @@ void def_circuit(py::class_> &pyCircuit) { .def_property_readonly( "c_registers", &get_unit_registers, "Get all classical registers.\n\n" - "This property is only valid if the bits in the circuit are " - "organized into registers (i.e. all bit indices are single numbers " - "and all sets of bits with the same string identifier consist of " - "bits indexed consecutively from zero).\n\n" + "The list only includes registers that are singly-indexed " + "contiguously from zero.\n\n" ":return: List of :py:class:`BitRegister`") .def( "get_q_register", @@ -313,10 +275,8 @@ void def_circuit(py::class_> &pyCircuit) { .def_property_readonly( "q_registers", &get_unit_registers, "Get all quantum registers.\n\n" - "This property is only valid if the qubits in the circuit are " - "organized into registers (i.e. all qubit indices are single numbers " - "and all sets of qubits with the same string identifier consist of " - "qubits indexed consecutively from zero).\n\n" + "The list only includes registers that are singly-indexed " + "contiguously from zero.\n\n" ":return: List of :py:class:`QubitRegister`") .def( "add_qubit", &Circuit::add_qubit, diff --git a/pytket/binders/include/circuit_registers.hpp b/pytket/binders/include/circuit_registers.hpp new file mode 100644 index 0000000000..20cf111cc4 --- /dev/null +++ b/pytket/binders/include/circuit_registers.hpp @@ -0,0 +1,59 @@ +// Copyright 2019-2024 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 "UnitRegister.hpp" +#include "tket/Circuit/Circuit.hpp" + +namespace tket { + +template +std::vector get_unit_registers(Circuit &circ) { + static_assert( + std::is_same::value || + std::is_same::value, + "T must be either QubitRegister or BitRegister"); + using T2 = typename std::conditional< + std::is_same::value, Qubit, Bit>::type; + std::vector unitids; + if constexpr (std::is_same::value) { + unitids = circ.all_qubits(); + } else if constexpr (std::is_same::value) { + unitids = circ.all_bits(); + } + // map from register name to unsigned indices + std::map> unit_map; + std::vector regs; + for (const T2 &unitid : unitids) { + // UnitRegisters only describe registers with 1-d indices + if (unitid.reg_dim() > 1) continue; + auto it = unit_map.find(unitid.reg_name()); + if (it == unit_map.end()) { + unit_map.insert({unitid.reg_name(), {unitid.index()[0]}}); + } else { + it->second.insert(unitid.index()[0]); + } + } + regs.reserve(unit_map.size()); + for (auto const &it : unit_map) { + // only return registers that are indexed consecutively from zero + if (*it.second.rbegin() == it.second.size() - 1) { + regs.emplace_back(it.first, it.second.size()); + } + } + return regs; +} + +} // namespace tket diff --git a/pytket/conanfile.py b/pytket/conanfile.py index c630d27eee..fd7e6eb361 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.91@tket/stable") + self.requires("tket/1.2.92@tket/stable") self.requires("tklog/0.3.3@tket/stable") self.requires("tkrng/0.3.3@tket/stable") self.requires("tkassert/0.3.4@tket/stable") diff --git a/pytket/docs/changelog.rst b/pytket/docs/changelog.rst index 1b13d9c9cc..284c99e97f 100644 --- a/pytket/docs/changelog.rst +++ b/pytket/docs/changelog.rst @@ -1,6 +1,17 @@ Changelog ========= +Unreleased +---------- + +Features: + +* Allow ``CircBox`` containing non-default registers. +* Add new methods ``Circuit.add_circbox_regwise()`` and + ``Circuit.add_circbox_with_regmap()`` for adding a ``CircBox`` to a circuit + providing either an ordered sequence of registers or a mapping of registers + from the box to the containing circuit. + 1.25.0 (February 2024) ---------------------- diff --git a/pytket/pytket/_tket/circuit.pyi b/pytket/pytket/_tket/circuit.pyi index a98b62d6bc..3e24e7af48 100644 --- a/pytket/pytket/_tket/circuit.pyi +++ b/pytket/pytket/_tket/circuit.pyi @@ -1309,8 +1309,10 @@ class Circuit: """ Append a :py:class:`CircBox` to the circuit. + The qubits and bits of the :py:class:`CircBox` are wired into the circuit in lexicographic order. Bits follow qubits in the order of arguments. + :param circbox: The box to append - :param args: Indices of the qubits/bits to append the box to + :param args: Indices of the (default-register) qubits/bits to append the box to :return: the new :py:class:`Circuit` """ @typing.overload @@ -1318,10 +1320,32 @@ class Circuit: """ Append a :py:class:`CircBox` to the circuit. + The qubits and bits of the :py:class:`CircBox` are wired into the circuit in lexicographic order. Bits follow qubits in the order of arguments. + :param circbox: The box to append :param args: The qubits/bits to append the box to :return: the new :py:class:`Circuit` """ + def add_circbox_regwise(self, circbox: CircBox, qregs: typing.Sequence[pytket._tket.unit_id.QubitRegister], cregs: typing.Sequence[pytket._tket.unit_id.BitRegister], **kwargs: Any) -> Circuit: + """ + Append a :py:class:`CircBox` to the circuit, wiring whole registers together. + + :param circbox: The box to append + :param qregs: Sequence of :py:class:`QubitRegister` from the outer :py:class:`Circuit`, the order corresponding to the lexicographic order of corresponding registers in the :py:class:`CircBox` + :param cregs: Sequence of :py:class:`BitRegister` from the outer :py:class:`Circuit`, the order corresponding to the lexicographic order of corresponding registers in the :py:class:`CircBox` + :return: the new :py:class:`Circuit` + """ + def add_circbox_with_regmap(self, circbox: CircBox, qregmap: dict[str, str], cregmap: dict[str, str], **kwargs: Any) -> Circuit: + """ + Append a :py:class:`CircBox` to the circuit, wiring whole registers together. + + This method expects two maps (one for qubit registers and one for bit registers), which must have keys corresponding to all register names in the box. The box may not contain any qubits or bits that do not belong to a register, i.e. all must be single-indexed contiguously from zero. + + :param circbox: The box to append + :param qregmap: Map specifying which qubit register in the :py:class:`CircBox` (the map's keys) matches which register in the outer circuit (the map's values) + :param cregmap: Map specifying which bit register in the :py:class:`CircBox` (the map's keys) matches which register in the outer circuit (the map's values) + :return: the new :py:class:`Circuit` + """ @typing.overload def add_circuit(self, circuit: Circuit, qubits: typing.Sequence[pytket._tket.unit_id.Qubit], bits: typing.Sequence[pytket._tket.unit_id.Bit] = []) -> Circuit: """ @@ -2353,7 +2377,7 @@ class Circuit: """ Get all classical registers. - This property is only valid if the bits in the circuit are organized into registers (i.e. all bit indices are single numbers and all sets of bits with the same string identifier consist of bits indexed consecutively from zero). + The list only includes registers that are singly-indexed contiguously from zero. :return: List of :py:class:`BitRegister` """ @@ -2408,7 +2432,7 @@ class Circuit: """ Get all quantum registers. - This property is only valid if the qubits in the circuit are organized into registers (i.e. all qubit indices are single numbers and all sets of qubits with the same string identifier consist of qubits indexed consecutively from zero). + The list only includes registers that are singly-indexed contiguously from zero. :return: List of :py:class:`QubitRegister` """ diff --git a/pytket/tests/circuit_test.py b/pytket/tests/circuit_test.py index 4617c7748a..0999dfafe6 100644 --- a/pytket/tests/circuit_test.py +++ b/pytket/tests/circuit_test.py @@ -1372,6 +1372,73 @@ def test_resources() -> None: assert two_qubit_gate_depth.get_max() == 13 +def test_add_circbox_with_registers() -> None: + c0 = Circuit() + areg = c0.add_q_register("a", 2) + breg = c0.add_q_register("b", 3) + c0.CZ(areg[0], areg[1]) + c0.CZ(areg[1], breg[0]) + c0.CCX(breg[0], breg[1], breg[2]) + cbox = CircBox(c0) + c = Circuit() + xreg = c.add_q_register("x", 3) + yreg = c.add_q_register("y", 2) + zreg = c.add_q_register("z", 3) + wreg = c.add_q_register("w", 2) + for qb in c.qubits: + c.H(qb) + c1 = c.copy() + c1.add_circbox_regwise(cbox, [wreg, zreg], []) + assert c1.n_gates == 11 + DecomposeBoxes().apply(c1) + assert c1.n_gates == 13 + c2 = c.copy() + c2.add_circbox_with_regmap(cbox, {"a": "w", "b": "z"}, {}) + DecomposeBoxes().apply(c2) + assert c1 == c2 + + +def test_add_circbox_with_mixed_registers() -> None: + c0 = Circuit() + c0_qreg1 = c0.add_q_register("q1", 2) + c0_qreg2 = c0.add_q_register("q2", 3) + c0_creg1 = c0.add_c_register("c1", 4) + c0_creg2 = c0.add_c_register("c2", 5) + cbox = CircBox(c0) + c = Circuit() + c_qreg1 = c.add_q_register("q1", 2) + c_qreg2 = c.add_q_register("q2", 3) + c_creg1 = c.add_c_register("c1", 4) + c_creg2 = c.add_c_register("c2", 5) + + c.add_circbox_with_regmap( + cbox, qregmap={"q1": "q1", "q2": "q2"}, cregmap={"c1": "c1", "c2": "c2"} + ) + + # Incomplete map: + with pytest.raises(IndexError): + c.add_circbox_with_regmap( + cbox, qregmap={"q1": "q1", "q2": "q2"}, cregmap={"c1": "c1"} + ) + + # Mismatched register sizes: + with pytest.raises(RuntimeError): + c.add_circbox_with_regmap( + cbox, qregmap={"q1": "q2", "q2": "q1"}, cregmap={"c1": "c2", "c2": "c1"} + ) + + # Non-register qubit in box: + c0.add_qubit(Qubit("q3", 1)) + cbox = CircBox(c0) + c.add_qubit(Qubit("q3", 0)) + with pytest.raises(RuntimeError): + c.add_circbox_with_regmap( + cbox, + qregmap={"q1": "q1", "q2": "q2", "q3": "q3"}, + cregmap={"c1": "c1", "c2": "c2"}, + ) + + if __name__ == "__main__": test_circuit_gen() test_symbolic_ops() diff --git a/tket/conanfile.py b/tket/conanfile.py index 7bc0d4f61d..5ba413c175 100644 --- a/tket/conanfile.py +++ b/tket/conanfile.py @@ -23,7 +23,7 @@ class TketConan(ConanFile): name = "tket" - version = "1.2.91" + version = "1.2.92" package_type = "library" license = "Apache 2" homepage = "https://github.com/CQCL/tket" diff --git a/tket/include/tket/Circuit/Boxes.hpp b/tket/include/tket/Circuit/Boxes.hpp index 0e9a45e95f..b73ed436e4 100644 --- a/tket/include/tket/Circuit/Boxes.hpp +++ b/tket/include/tket/Circuit/Boxes.hpp @@ -134,7 +134,7 @@ Op_ptr set_box_id(BoxT &b, boost::uuids::uuid newid) { class CircBox : public Box { public: /** - * Construct from a given circuit. The circuit must be simple. + * Construct from a given circuit. */ explicit CircBox(const Circuit &circ); diff --git a/tket/src/Circuit/Boxes.cpp b/tket/src/Circuit/Boxes.cpp index 960f016c9c..d521b8308b 100644 --- a/tket/src/Circuit/Boxes.cpp +++ b/tket/src/Circuit/Boxes.cpp @@ -69,7 +69,6 @@ Op_ptr Box::deserialize(const nlohmann::json &j) { } CircBox::CircBox(const Circuit &circ) : Box(OpType::CircBox) { - if (!circ.is_simple()) throw SimpleOnly(); signature_ = op_signature_t(circ.n_qubits(), EdgeType::Quantum); op_signature_t bits(circ.n_bits(), EdgeType::Classical); signature_.insert(signature_.end(), bits.begin(), bits.end()); diff --git a/tket/src/Circuit/macro_manipulation.cpp b/tket/src/Circuit/macro_manipulation.cpp index 77927c75e0..56360f4445 100644 --- a/tket/src/Circuit/macro_manipulation.cpp +++ b/tket/src/Circuit/macro_manipulation.cpp @@ -699,6 +699,7 @@ bool Circuit::substitute_box_vertex( if (op->get_type() == OpType::ClassicalExpBox) return false; const Box& b = static_cast(*op); Circuit replacement = *b.to_circuit(); + replacement.flatten_registers(); if (conditional) { substitute_conditional( replacement, vert, vertex_deletion, OpGroupTransfer::Merge); diff --git a/tket/test/src/Circuit/test_Boxes.cpp b/tket/test/src/Circuit/test_Boxes.cpp index 90a5c155de..621d62c14a 100644 --- a/tket/test/src/Circuit/test_Boxes.cpp +++ b/tket/test/src/Circuit/test_Boxes.cpp @@ -41,21 +41,6 @@ using Catch::Matchers::StartsWith; namespace tket { namespace test_Boxes { -SCENARIO("CircBox requires simple circuits", "[boxes]") { - Circuit circ(2); - circ.add_op(OpType::Y, {0}); - circ.add_op(OpType::CX, {0, 1}); - REQUIRE(circ.is_simple()); - Qubit qb0(0); - Qubit qb1(1); - Qubit a0("a", 0); - Qubit a1("a", 1); - unit_map_t qubit_map = {{qb0, a0}, {qb1, a1}}; - circ.rename_units(qubit_map); - REQUIRE(!circ.is_simple()); - REQUIRE_THROWS_AS(CircBox(circ), SimpleOnly); -} - SCENARIO("CircBox in-place symbol substitution") { Sym asym = SymEngine::symbol("a"); Expr alpha(asym); @@ -135,6 +120,20 @@ SCENARIO("Using Boxes", "[boxes]") { Eigen::MatrixXcd uc0a = tket_sim::get_unitary(c0a); REQUIRE((uc0 - uc0a).cwiseAbs().sum() < ERR_EPS); } + GIVEN("CircBox with non-default units") { + Circuit c0; + c0.add_q_register("a", 2); + c0.add_q_register("b", 1); + c0.add_op( + OpType::CCX, {Qubit("a", 1), Qubit("a", 0), Qubit("b", 0)}); + CircBox cbox(c0); + Circuit c(3); + c.add_box(cbox, {0, 2, 1}); + REQUIRE(!test_unitary_comparison(c0, c)); + Circuit c1(3); + c1.add_box(cbox, {0, 1, 2}); + REQUIRE(test_unitary_comparison(c0, c1)); + } GIVEN("Unitary1qBox manipulation") { // random 1qb gate Circuit setup(1); diff --git a/tket/test/src/Circuit/test_Circ.cpp b/tket/test/src/Circuit/test_Circ.cpp index 1532f4d758..c6278be44c 100644 --- a/tket/test/src/Circuit/test_Circ.cpp +++ b/tket/test/src/Circuit/test_Circ.cpp @@ -869,7 +869,6 @@ SCENARIO( REQUIRE(test2.get_slices().size() == depth_before); test2.assert_valid(); } - // test2.print_graph(); WHEN("The reverse substitution is performed") { Edge f1 = test.get_nth_in_edge(h1, 0); Edge f2 = test.get_nth_in_edge(h2, 0); @@ -880,6 +879,19 @@ SCENARIO( REQUIRE(test.get_slices().size() == depth_before); test.assert_valid(); } + WHEN("The circuit to insert is not simple") { + unit_map_t qmap; + Edge e1 = test2.get_nth_in_edge(x1, 0); + Edge e2 = test2.get_nth_in_edge(x2, 0); + Edge e3 = test2.get_nth_out_edge(x1, 0); + Edge e4 = test2.get_nth_out_edge(x2, 0); + Subcircuit sub = {{e1, e2}, {e3, e4}, {x1, x2}}; + test2.substitute(test, sub); + qmap.insert({Qubit(0), Qubit("a", 1)}); + qmap.insert({Qubit(1), Qubit("b", 0)}); + test.rename_units(qmap); + REQUIRE_THROWS_AS(test2.substitute(test, sub), SimpleOnly); + } } GIVEN("Circuits with Classical effects") { Circuit circ(2, 1);