diff --git a/pytket/binders/circuit/Circuit/add_op.cpp b/pytket/binders/circuit/Circuit/add_op.cpp index a7a38daffd..087c84afde 100644 --- a/pytket/binders/circuit/Circuit/add_op.cpp +++ b/pytket/binders/circuit/Circuit/add_op.cpp @@ -25,12 +25,14 @@ #include "tket/Circuit/ClassicalExpBox.hpp" #include "tket/Circuit/ConjugationBox.hpp" #include "tket/Circuit/DiagonalBox.hpp" +#include "tket/Circuit/DummyBox.hpp" #include "tket/Circuit/Multiplexor.hpp" #include "tket/Circuit/PauliExpBoxes.hpp" #include "tket/Circuit/StatePreparation.hpp" #include "tket/Circuit/ToffoliBox.hpp" #include "tket/Converters/PhasePoly.hpp" #include "tket/Gate/OpPtrFunctions.hpp" +#include "tket/Utils/UnitID.hpp" #include "typecast.hpp" namespace py = pybind11; @@ -332,6 +334,30 @@ void init_circuit_add_op(py::class_> &c) { "qubits: Indices of the qubits to append the box to" "\n:return: the new :py:class:`Circuit`", py::arg("toffolibox"), py::arg("qubits")) + .def( + "add_dummybox", + [](Circuit *circ, const DummyBox &box, + const py::tket_custom::SequenceVec &qubits, + const py::tket_custom::SequenceVec &bits, + const py::kwargs &kwargs) { + std::vector args; + for (unsigned i : qubits) { + args.push_back(Qubit(i)); + } + for (unsigned i : bits) { + args.push_back(Bit(i)); + } + return add_box_method( + circ, std::make_shared(box), args, kwargs); + }, + "Append a :py:class:`DummyBox` to the circuit." + "\n\n:param dummybox: The box to append" + "\n:param qubits: Indices (in the default register) of the qubits to " + "append the box to" + "\n:param bits: Indices of the bits (in the default register) to " + "append the box to" + "\n:return: the new :py:class:`Circuit`", + py::arg("dummybox"), py::arg("qubits"), py::arg("bits")) .def( "add_qcontrolbox", [](Circuit *circ, const QControlBox &box, @@ -603,6 +629,22 @@ void init_circuit_add_op(py::class_> &c) { "qubits: Indices of the qubits to append the box to" "\n:return: the new :py:class:`Circuit`", py::arg("toffolibox"), py::arg("qubits")) + .def( + "add_dummybox", + [](Circuit *circ, const DummyBox &box, + const py_qubit_vector_t &qubits, const py_bit_vector_t &bits, + const py::kwargs &kwargs) { + std::vector args = {qubits.begin(), qubits.end()}; + args.insert(args.end(), bits.begin(), bits.end()); + return add_box_method( + circ, std::make_shared(box), args, kwargs); + }, + "Append a :py:class:`DummyBox` to the circuit." + "\n\n:param dummybox: The box to append" + "\n:param qubits: Qubits to append the box to" + "\n:param bits: Bits to append the box to" + "\n:return: the new :py:class:`Circuit`", + py::arg("dummybox"), py::arg("qubits"), py::arg("bits")) .def( "add_qcontrolbox", [](Circuit *circ, const QControlBox &box, diff --git a/pytket/binders/circuit/Circuit/main.cpp b/pytket/binders/circuit/Circuit/main.cpp index fe56309db0..44ad29ea8c 100644 --- a/pytket/binders/circuit/Circuit/main.cpp +++ b/pytket/binders/circuit/Circuit/main.cpp @@ -29,6 +29,7 @@ #include "tket/Circuit/Boxes.hpp" #include "tket/Circuit/Circuit.hpp" #include "tket/Circuit/Command.hpp" +#include "tket/Circuit/DummyBox.hpp" #include "tket/Circuit/PauliExpBoxes.hpp" #include "tket/Circuit/Simulation/CircuitSimulator.hpp" #include "tket/Circuit/ToffoliBox.hpp" @@ -763,6 +764,17 @@ void def_circuit(py::class_> &pyCircuit) { ":param opgroup: the name of the operations group to replace\n" ":return: whether any replacements were made", py::arg("box"), py::arg("opgroup")) + .def( + "substitute_named", + [](Circuit &circ, const DummyBox &box, const std::string &opgroup) { + return circ.substitute_named(box, opgroup); + }, + "Substitute all ops with the given name for the given box." + "The replacement boxes retain the same name.\n\n" + ":param box: the replacement DummyBox\n" + ":param opgroup: the name of the operations group to replace\n" + ":return: whether any replacements were made", + py::arg("box"), py::arg("opgroup")) .def( "substitute_named", [](Circuit &circ, const QControlBox &box, @@ -854,6 +866,71 @@ void def_circuit(py::class_> &pyCircuit) { "\n\n:param optype: operation type" "\n\n:return: list of :py:class:`Command`", py::arg("optype")) + .def( + "get_resources", &Circuit::get_resources, + "Calculate the overall resources of the circuit." + "\n\nThis takes account of the data stored in each " + "py:class:`DummyBox` within the circuit, as well as other gates, " + "to compute upper and lower bounds." + "\n\n:return: bounds on resources of the circuit" + "\n\n" + ">>> resource_data0 = ResourceData(\n" + "... op_type_count={\n" + "... OpType.T: ResourceBounds(1, 2),\n" + "... OpType.H: ResourceBounds(0, 1),\n" + "... OpType.CX: ResourceBounds(1, 2),\n" + "... OpType.CZ: ResourceBounds(3, 3),\n" + "... },\n" + "... gate_depth=ResourceBounds(5, 8),\n" + "... op_type_depth={\n" + "... OpType.T: ResourceBounds(0, 10),\n" + "... OpType.H: ResourceBounds(0, 10),\n" + "... OpType.CX: ResourceBounds(1, 2),\n" + "... OpType.CZ: ResourceBounds(3, 3),\n" + "... },\n" + "... two_qubit_gate_depth=ResourceBounds(4, 5),\n" + "... )\n" + ">>> dbox0 = DummyBox(n_qubits=2, n_bits=0, " + "resource_data=resource_data0)\n" + ">>> resource_data1 = ResourceData(\n" + "... op_type_count={\n" + "... OpType.T: ResourceBounds(2, 2),\n" + "... OpType.H: ResourceBounds(1, 1),\n" + "... OpType.CX: ResourceBounds(2, 3),\n" + "... OpType.CZ: ResourceBounds(3, 5),\n" + "... },\n" + "... gate_depth=ResourceBounds(5, 10),\n" + "... op_type_depth={\n" + "... OpType.T: ResourceBounds(1, 2),\n" + "... OpType.H: ResourceBounds(2, 4),\n" + "... OpType.CX: ResourceBounds(1, 1),\n" + "... OpType.CZ: ResourceBounds(3, 4),\n" + "... },\n" + "... two_qubit_gate_depth=ResourceBounds(3, 5),\n" + "... )\n" + ">>> dbox1 = DummyBox(n_qubits=3, n_bits=0, " + "resource_data=resource_data1)\n" + ">>> c = (\n" + "... Circuit(3)\n" + "... .H(0)\n" + "... .CX(1, 2)\n" + "... .CX(0, 1)\n" + "... .T(2)\n" + "... .H(1)\n" + "... .add_dummybox(dbox0, [0, 1], [])\n" + "... .CZ(1, 2)\n" + "... .add_dummybox(dbox1, [0, 1, 2], [])\n" + "... .H(2)\n" + "... )\n" + ">>> resource_data = c.get_resources()\n" + ">>> print(resource_data)\n" + "ResourceData(op_type_count={OpType.T: ResourceBounds(4, 5), " + "OpType.H: ResourceBounds(4, 5), OpType.CX: ResourceBounds(5, 7), " + "OpType.CZ: ResourceBounds(7, 9), }, gate_depth=ResourceBounds(15, " + "23), op_type_depth={OpType.T: ResourceBounds(2, 12), OpType.H: " + "ResourceBounds(5, 17), OpType.CX: ResourceBounds(4, 5), OpType.CZ: " + "ResourceBounds(7, 8), }, two_qubit_gate_depth=ResourceBounds(10, " + "13))") .def_property_readonly( "_dag_data", [](Circuit &circ) { diff --git a/pytket/binders/circuit/boxes.cpp b/pytket/binders/circuit/boxes.cpp index 9a20f64f64..1747ad57d0 100644 --- a/pytket/binders/circuit/boxes.cpp +++ b/pytket/binders/circuit/boxes.cpp @@ -17,16 +17,22 @@ #include #include +#include +#include + #include "binder_json.hpp" #include "binder_utils.hpp" #include "tket/Circuit/Circuit.hpp" #include "tket/Circuit/ConjugationBox.hpp" #include "tket/Circuit/DiagonalBox.hpp" +#include "tket/Circuit/DummyBox.hpp" #include "tket/Circuit/Multiplexor.hpp" #include "tket/Circuit/PauliExpBoxes.hpp" +#include "tket/Circuit/ResourceData.hpp" #include "tket/Circuit/StatePreparation.hpp" #include "tket/Circuit/ToffoliBox.hpp" #include "tket/Converters/PhasePoly.hpp" +#include "tket/OpType/OpType.hpp" #include "tket/Utils/HelperFunctions.hpp" #include "tket/Utils/Json.hpp" #include "typecast.hpp" @@ -453,6 +459,130 @@ void init_boxes(py::module &m) { .def( "get_rotation_axis", &ToffoliBox::get_rotation_axis, ":return: the rotation axis"); + py::class_>( + m, "ResourceBounds", + "Structure holding a minimum and maximum value of some resource, where " + "both values are unsigned integers.") + .def( + py::init([](unsigned min, unsigned max) { + if (min > max) { + throw std::invalid_argument( + "minimum must be less than or equal to maximum"); + } + return ResourceBounds{min, max}; + }), + "Constructs a ResourceBounds object.\n\n" + ":param min: minimum value\n" + ":param max: maximum value\n", + py::arg("min"), py::arg("max")) + .def( + "get_min", + [](const ResourceBounds &resource_bounds) { + return resource_bounds.min; + }, + ":return: the minimum value") + .def( + "get_max", + [](const ResourceBounds &resource_bounds) { + return resource_bounds.max; + }, + ":return: the maximum value"); + py::class_( + m, "ResourceData", + "An object holding resource data for use in a :py:class:`DummyBox`." + "\n\nThe object holds several fields representing minimum and maximum " + "values for certain resources. The absence of an :py:class:`OpType` in " + "one of these fields is interpreted as the absence of gates of that type " + "in the (imagined) circuit." + "\n\nSee :py:meth:`Circuit.get_resources` for how to use this data.") + .def( + py::init([](std::map> op_type_count, + ResourceBounds gate_depth, + std::map> op_type_depth, + ResourceBounds two_qubit_gate_depth) { + return ResourceData{ + op_type_count, gate_depth, op_type_depth, two_qubit_gate_depth}; + }), + "Constructs a ResourceData object.\n\n" + ":param op_type_count: dictionary of counts of selected " + ":py:class:`OpType`\n" + ":param gate_depth: overall gate depth\n" + ":param op_type_depth: dictionary of depths of selected " + ":py:class:`OpType`\n" + ":param two_qubit_gate_depth: overall two-qubit-gate depth", + py::arg("op_type_count"), py::arg("gate_depth"), + py::arg("op_type_depth"), py::arg("two_qubit_gate_depth")) + .def( + "get_op_type_count", + [](const ResourceData &resource_data) { + return resource_data.OpTypeCount; + }, + ":return: bounds on the op type count") + .def( + "get_gate_depth", + [](const ResourceData &resource_data) { + return resource_data.GateDepth; + }, + ":return: bounds on the gate depth") + .def( + "get_op_type_depth", + [](const ResourceData &resource_data) { + return resource_data.OpTypeDepth; + }, + ":return: bounds on the op type depth") + .def( + "get_two_qubit_gate_depth", + [](const ResourceData &resource_data) { + return resource_data.TwoQubitGateDepth; + }, + ":return: bounds on the two-qubit-gate depth") + .def("__repr__", [](const ResourceData &resource_data) { + std::stringstream ss; + ss << "ResourceData("; + ss << "op_type_count={"; + for (const auto &pair : resource_data.OpTypeCount) { + ss << "OpType." << optypeinfo().at(pair.first).name << ": " + << "ResourceBounds(" << pair.second.min << ", " << pair.second.max + << "), "; + } + ss << "}, "; + ss << "gate_depth=ResourceBounds(" << resource_data.GateDepth.min + << ", " << resource_data.GateDepth.max << "), "; + ss << "op_type_depth={"; + for (const auto &pair : resource_data.OpTypeDepth) { + ss << "OpType." << optypeinfo().at(pair.first).name << ": " + << "ResourceBounds(" << pair.second.min << ", " << pair.second.max + << "), "; + } + ss << "}, "; + ss << "two_qubit_gate_depth=ResourceBounds(" + << resource_data.TwoQubitGateDepth.min << ", " + << resource_data.TwoQubitGateDepth.max << ")"; + ss << ")"; + return ss.str(); + }); + py::class_, Op>( + m, "DummyBox", + "A placeholder operation that holds resource data. This box type cannot " + "be decomposed into a circuit. It only serves to record resource data " + "for a region of a circuit: for example, upper and lower bounds on gate " + "counts and depth. A circuit containing such a box cannot be executed.") + .def( + py::init([](unsigned n_qubits, unsigned n_bits, + const ResourceData &resource_data) { + return DummyBox(n_qubits, n_bits, resource_data); + }), + "Construct a new instance from some resource data.", + py::arg("n_qubits"), py::arg("n_bits"), py::arg("resource_data")) + .def( + "get_n_qubits", &DummyBox::get_n_qubits, + ":return: the number of qubits covered by the box") + .def( + "get_n_bits", &DummyBox::get_n_bits, + ":return: the number of bits covered by the box") + .def( + "get_resource_data", &DummyBox::get_resource_data, + ":return: the associated resource data"); py::class_, Op>( m, "QControlBox", "A user-defined controlled operation specified by an " diff --git a/pytket/binders/circuit/main.cpp b/pytket/binders/circuit/main.cpp index d14ec311b1..4730cef3ec 100644 --- a/pytket/binders/circuit/main.cpp +++ b/pytket/binders/circuit/main.cpp @@ -330,6 +330,9 @@ PYBIND11_MODULE(circuit, m) { .value( "ToffoliBox", OpType::ToffoliBox, "A permutation of classical basis states") + .value( + "DummyBox", OpType::DummyBox, + "A placeholder operation that holds resource data") .value( "CustomGate", OpType::CustomGate, ":math:`(\\alpha, \\beta, \\ldots) \\mapsto` A user-defined " diff --git a/pytket/conanfile.py b/pytket/conanfile.py index e75f704d46..756035975c 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.67@tket/stable") + self.requires("tket/1.2.68@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 71cba0ff00..e38e7c3d31 100644 --- a/pytket/docs/changelog.rst +++ b/pytket/docs/changelog.rst @@ -9,6 +9,9 @@ Minor new features: * Add optional parameter to QASM conversion methods to set the maximum allowed width of classical registers (default 32). * New ``OpType.CS`` and ``OpType.CSdg``. +* New classes ``ResourceBounds``, ``ResourceData`` and ``DummyBox``, and method + ``Circuit.get_resources()``, allowing reasoning about resource requirements + on circuit templates. Fixes: diff --git a/pytket/docs/circuit.rst b/pytket/docs/circuit.rst index a56d730742..b8f4c3342c 100644 --- a/pytket/docs/circuit.rst +++ b/pytket/docs/circuit.rst @@ -102,4 +102,13 @@ pytket.circuit :members: .. autoclass:: pytket._tket.circuit.ConjugationBox :special-members: - :members: \ No newline at end of file + :members: +.. autoclass:: pytket._tket.circuit.ResourceBounds + :special-members: + :members: +.. autoclass:: pytket._tket.circuit.ResourceData + :special-members: + :members: +.. autoclass:: pytket._tket.circuit.DummyBox + :special-members: + :members: diff --git a/pytket/pytket/_tket/circuit.pyi b/pytket/pytket/_tket/circuit.pyi index a9c00a6819..76282ef6d5 100644 --- a/pytket/pytket/_tket/circuit.pyi +++ b/pytket/pytket/_tket/circuit.pyi @@ -9,7 +9,7 @@ import pytket.circuit.logic_exp import pytket.wasm.wasm import sympy import typing -__all__ = ['BarrierOp', 'BasisOrder', 'CXConfigType', 'CircBox', 'Circuit', 'ClassicalEvalOp', 'ClassicalExpBox', 'ClassicalOp', 'Command', 'Conditional', 'ConjugationBox', 'CopyBitsOp', 'CustomGate', 'CustomGateDef', 'DiagonalBox', 'EdgeType', 'ExpBox', 'MetaOp', 'MultiBitOp', 'MultiplexedRotationBox', 'MultiplexedTensoredU2Box', 'MultiplexedU2Box', 'MultiplexorBox', 'Op', 'OpType', 'PauliExpBox', 'PauliExpCommutingSetBox', 'PauliExpPairBox', 'PhasePolyBox', 'ProjectorAssertionBox', 'QControlBox', 'RangePredicateOp', 'SetBitsOp', 'StabiliserAssertionBox', 'StatePreparationBox', 'ToffoliBox', 'ToffoliBoxSynthStrat', 'Unitary1qBox', 'Unitary2qBox', 'Unitary3qBox', 'WASMOp', 'fresh_symbol'] +__all__ = ['BarrierOp', 'BasisOrder', 'CXConfigType', 'CircBox', 'Circuit', 'ClassicalEvalOp', 'ClassicalExpBox', 'ClassicalOp', 'Command', 'Conditional', 'ConjugationBox', 'CopyBitsOp', 'CustomGate', 'CustomGateDef', 'DiagonalBox', 'DummyBox', 'EdgeType', 'ExpBox', 'MetaOp', 'MultiBitOp', 'MultiplexedRotationBox', 'MultiplexedTensoredU2Box', 'MultiplexedU2Box', 'MultiplexorBox', 'Op', 'OpType', 'PauliExpBox', 'PauliExpCommutingSetBox', 'PauliExpPairBox', 'PhasePolyBox', 'ProjectorAssertionBox', 'QControlBox', 'RangePredicateOp', 'ResourceBounds', 'ResourceData', 'SetBitsOp', 'StabiliserAssertionBox', 'StatePreparationBox', 'ToffoliBox', 'ToffoliBoxSynthStrat', 'Unitary1qBox', 'Unitary2qBox', 'Unitary3qBox', 'WASMOp', 'fresh_symbol'] class BarrierOp(Op): """ Barrier operations. @@ -1424,6 +1424,26 @@ class Circuit: :return: the new :py:class:`Circuit` """ @typing.overload + def add_dummybox(self, dummybox: DummyBox, qubits: typing.Sequence[int], bits: typing.Sequence[int], **kwargs: Any) -> Circuit: + """ + Append a :py:class:`DummyBox` to the circuit. + + :param dummybox: The box to append + :param qubits: Indices (in the default register) of the qubits to append the box to + :param bits: Indices of the bits (in the default register) to append the box to + :return: the new :py:class:`Circuit` + """ + @typing.overload + def add_dummybox(self, dummybox: DummyBox, qubits: typing.Sequence[pytket._tket.unit_id.Qubit], bits: typing.Sequence[pytket._tket.unit_id.Bit], **kwargs: Any) -> Circuit: + """ + Append a :py:class:`DummyBox` to the circuit. + + :param dummybox: The box to append + :param qubits: Qubits to append the box to + :param bits: Bits to append the box to + :return: the new :py:class:`Circuit` + """ + @typing.overload def add_expbox(self, expbox: ExpBox, qubit_0: int, qubit_1: int, **kwargs: Any) -> Circuit: """ Append an :py:class:`ExpBox` to the circuit. @@ -1977,6 +1997,64 @@ class Circuit: :param name: name for the register :return: the retrieved :py:class:`QubitRegister` """ + def get_resources(self) -> ResourceData: + """ + Calculate the overall resources of the circuit. + + This takes account of the data stored in each py:class:`DummyBox` within the circuit, as well as other gates, to compute upper and lower bounds. + + :return: bounds on resources of the circuit + + >>> resource_data0 = ResourceData( + ... op_type_count={ + ... OpType.T: ResourceBounds(1, 2), + ... OpType.H: ResourceBounds(0, 1), + ... OpType.CX: ResourceBounds(1, 2), + ... OpType.CZ: ResourceBounds(3, 3), + ... }, + ... gate_depth=ResourceBounds(5, 8), + ... op_type_depth={ + ... OpType.T: ResourceBounds(0, 10), + ... OpType.H: ResourceBounds(0, 10), + ... OpType.CX: ResourceBounds(1, 2), + ... OpType.CZ: ResourceBounds(3, 3), + ... }, + ... two_qubit_gate_depth=ResourceBounds(4, 5), + ... ) + >>> dbox0 = DummyBox(n_qubits=2, n_bits=0, resource_data=resource_data0) + >>> resource_data1 = ResourceData( + ... op_type_count={ + ... OpType.T: ResourceBounds(2, 2), + ... OpType.H: ResourceBounds(1, 1), + ... OpType.CX: ResourceBounds(2, 3), + ... OpType.CZ: ResourceBounds(3, 5), + ... }, + ... gate_depth=ResourceBounds(5, 10), + ... op_type_depth={ + ... OpType.T: ResourceBounds(1, 2), + ... OpType.H: ResourceBounds(2, 4), + ... OpType.CX: ResourceBounds(1, 1), + ... OpType.CZ: ResourceBounds(3, 4), + ... }, + ... two_qubit_gate_depth=ResourceBounds(3, 5), + ... ) + >>> dbox1 = DummyBox(n_qubits=3, n_bits=0, resource_data=resource_data1) + >>> c = ( + ... Circuit(3) + ... .H(0) + ... .CX(1, 2) + ... .CX(0, 1) + ... .T(2) + ... .H(1) + ... .add_dummybox(dbox0, , []) + ... .CZ(1, 2) + ... .add_dummybox(dbox1, [0, 1, 2], []) + ... .H(2) + ... ) + >>> resource_data = c.get_resources() + >>> print(resource_data) + ResourceData(op_type_count={OpType.T: ResourceBounds(4, 5), OpType.H: ResourceBounds(4, 5), OpType.CX: ResourceBounds(5, 7), OpType.CZ: ResourceBounds(7, 9), }, gate_depth=ResourceBounds(15, 23), op_type_depth={OpType.T: ResourceBounds(2, 12), OpType.H: ResourceBounds(5, 17), OpType.CX: ResourceBounds(4, 5), OpType.CZ: ResourceBounds(7, 8), }, two_qubit_gate_depth=ResourceBounds(10, 13)) + """ def get_statevector(self) -> NDArray[numpy.complex128]: """ Calculate the unitary matrix of the circuit, using ILO-BE convention, applied to the column vector (1,0,0...), which is thus another column vector. Due to pybind11 and numpy peculiarities, to treat the result as a genuine column vector and perform further matrix multiplication, you need to call .reshape(rows,1) to get a 2D matrix with the correct dimensions. @@ -2175,6 +2253,15 @@ class Circuit: :return: whether any replacements were made """ @typing.overload + def substitute_named(self, box: DummyBox, opgroup: str) -> bool: + """ + Substitute all ops with the given name for the given box.The replacement boxes retain the same name. + + :param box: the replacement DummyBox + :param opgroup: the name of the operations group to replace + :return: whether any replacements were made + """ + @typing.overload def substitute_named(self, box: QControlBox, opgroup: str) -> bool: """ Substitute all ops with the given name for the given box.The replacement boxes retain the same name. @@ -2567,6 +2654,26 @@ class DiagonalBox(Op): """ :return: the upper_triangle flag """ +class DummyBox(Op): + """ + A placeholder operation that holds resource data. This box type cannot be decomposed into a circuit. It only serves to record resource data for a region of a circuit: for example, upper and lower bounds on gate counts and depth. A circuit containing such a box cannot be executed. + """ + def __init__(self, n_qubits: int, n_bits: int, resource_data: ResourceData) -> None: + """ + Construct a new instance from some resource data. + """ + def get_n_bits(self) -> int: + """ + :return: the number of bits covered by the box + """ + def get_n_qubits(self) -> int: + """ + :return: the number of qubits covered by the box + """ + def get_resource_data(self) -> ResourceData: + """ + :return: the associated resource data + """ class EdgeType: """ Type of a wire in a circuit or input to an op @@ -2997,6 +3104,8 @@ class OpType: ToffoliBox : A permutation of classical basis states + DummyBox : A placeholder operation that holds resource data + CustomGate : :math:`(\alpha, \beta, \ldots) \mapsto` A user-defined operation, based on a :py:class:`Circuit` :math:`C` with parameters :math:`\alpha, \beta, \ldots` substituted in place of bound symbolic variables in :math:`C`, as defined by the :py:class:`CustomGateDef`. Conditional : An operation to be applied conditionally on the value of some classical register @@ -3096,6 +3205,7 @@ class OpType: CopyBits: typing.ClassVar[OpType] # value = CustomGate: typing.ClassVar[OpType] # value = DiagonalBox: typing.ClassVar[OpType] # value = + DummyBox: typing.ClassVar[OpType] # value = ECR: typing.ClassVar[OpType] # value = ESWAP: typing.ClassVar[OpType] # value = ExpBox: typing.ClassVar[OpType] # value = @@ -3158,7 +3268,7 @@ class OpType: Z: typing.ClassVar[OpType] # value = ZZMax: typing.ClassVar[OpType] # value = ZZPhase: typing.ClassVar[OpType] # value = - __members__: typing.ClassVar[dict[str, OpType]] # value = {'Phase': , 'Z': , 'X': , 'Y': , 'S': , 'Sdg': , 'T': , 'Tdg': , 'V': , 'Vdg': , 'SX': , 'SXdg': , 'H': , 'Rx': , 'Ry': , 'Rz': , 'U1': , 'U2': , 'U3': , 'TK1': , 'TK2': , 'CX': , 'CY': , 'CZ': , 'CH': , 'CV': , 'CVdg': , 'CSX': , 'CSXdg': , 'CS': , 'CSdg': , 'CRz': , 'CRx': , 'CRy': , 'CU1': , 'CU3': , 'CCX': , 'ECR': , 'SWAP': , 'CSWAP': , 'noop': , 'Barrier': , 'Label': , 'Branch': , 'Goto': , 'Stop': , 'BRIDGE': , 'Measure': , 'Reset': , 'CircBox': , 'PhasePolyBox': , 'Unitary1qBox': , 'Unitary2qBox': , 'Unitary3qBox': , 'ExpBox': , 'PauliExpBox': , 'PauliExpPairBox': , 'PauliExpCommutingSetBox': , 'QControlBox': , 'ToffoliBox': , 'CustomGate': , 'Conditional': , 'ISWAP': , 'PhasedISWAP': , 'XXPhase': , 'YYPhase': , 'ZZPhase': , 'XXPhase3': , 'PhasedX': , 'NPhasedX': , 'CnRy': , 'CnX': , 'CnY': , 'CnZ': , 'ZZMax': , 'ESWAP': , 'FSim': , 'Sycamore': , 'ISWAPMax': , 'ClassicalTransform': , 'WASM': , 'SetBits': , 'CopyBits': , 'RangePredicate': , 'ExplicitPredicate': , 'ExplicitModifier': , 'MultiBit': , 'ClassicalExpBox': , 'MultiplexorBox': , 'MultiplexedRotationBox': , 'MultiplexedU2Box': , 'MultiplexedTensoredU2Box': , 'StatePreparationBox': , 'DiagonalBox': } + __members__: typing.ClassVar[dict[str, OpType]] # value = {'Phase': , 'Z': , 'X': , 'Y': , 'S': , 'Sdg': , 'T': , 'Tdg': , 'V': , 'Vdg': , 'SX': , 'SXdg': , 'H': , 'Rx': , 'Ry': , 'Rz': , 'U1': , 'U2': , 'U3': , 'TK1': , 'TK2': , 'CX': , 'CY': , 'CZ': , 'CH': , 'CV': , 'CVdg': , 'CSX': , 'CSXdg': , 'CS': , 'CSdg': , 'CRz': , 'CRx': , 'CRy': , 'CU1': , 'CU3': , 'CCX': , 'ECR': , 'SWAP': , 'CSWAP': , 'noop': , 'Barrier': , 'Label': , 'Branch': , 'Goto': , 'Stop': , 'BRIDGE': , 'Measure': , 'Reset': , 'CircBox': , 'PhasePolyBox': , 'Unitary1qBox': , 'Unitary2qBox': , 'Unitary3qBox': , 'ExpBox': , 'PauliExpBox': , 'PauliExpPairBox': , 'PauliExpCommutingSetBox': , 'QControlBox': , 'ToffoliBox': , 'DummyBox': , 'CustomGate': , 'Conditional': , 'ISWAP': , 'PhasedISWAP': , 'XXPhase': , 'YYPhase': , 'ZZPhase': , 'XXPhase3': , 'PhasedX': , 'NPhasedX': , 'CnRy': , 'CnX': , 'CnY': , 'CnZ': , 'ZZMax': , 'ESWAP': , 'FSim': , 'Sycamore': , 'ISWAPMax': , 'ClassicalTransform': , 'WASM': , 'SetBits': , 'CopyBits': , 'RangePredicate': , 'ExplicitPredicate': , 'ExplicitModifier': , 'MultiBit': , 'ClassicalExpBox': , 'MultiplexorBox': , 'MultiplexedRotationBox': , 'MultiplexedU2Box': , 'MultiplexedTensoredU2Box': , 'StatePreparationBox': , 'DiagonalBox': } noop: typing.ClassVar[OpType] # value = @staticmethod def from_name(arg0: str) -> OpType: @@ -3401,6 +3511,60 @@ class RangePredicateOp(ClassicalEvalOp): """ Inclusive upper bound. """ +class ResourceBounds: + """ + Structure holding a minimum and maximum value of some resource, where both values are unsigned integers. + """ + def __init__(self, min: int, max: int) -> None: + """ + Constructs a ResourceBounds object. + + :param min: minimum value + :param max: maximum value + """ + def get_max(self) -> int: + """ + :return: the maximum value + """ + def get_min(self) -> int: + """ + :return: the minimum value + """ +class ResourceData: + """ + An object holding resource data for use in a :py:class:`DummyBox`. + + The object holds several fields representing minimum and maximum values for certain resources. The absence of an :py:class:`OpType` in one of these fields is interpreted as the absence of gates of that type in the (imagined) circuit. + + See :py:meth:`Circuit.get_resources` for how to use this data. + """ + def __init__(self, op_type_count: dict[OpType, ResourceBounds], gate_depth: ResourceBounds, op_type_depth: dict[OpType, ResourceBounds], two_qubit_gate_depth: ResourceBounds) -> None: + """ + Constructs a ResourceData object. + + :param op_type_count: dictionary of counts of selected :py:class:`OpType` + :param gate_depth: overall gate depth + :param op_type_depth: dictionary of depths of selected :py:class:`OpType` + :param two_qubit_gate_depth: overall two-qubit-gate depth + """ + def __repr__(self) -> str: + ... + def get_gate_depth(self) -> ResourceBounds: + """ + :return: bounds on the gate depth + """ + def get_op_type_count(self) -> dict[OpType, ResourceBounds]: + """ + :return: bounds on the op type count + """ + def get_op_type_depth(self) -> dict[OpType, ResourceBounds]: + """ + :return: bounds on the op type depth + """ + def get_two_qubit_gate_depth(self) -> ResourceBounds: + """ + :return: bounds on the two-qubit-gate depth + """ class SetBitsOp(ClassicalEvalOp): """ An operation to set the values of Bits to some constants. diff --git a/pytket/tests/circuit_test.py b/pytket/tests/circuit_test.py index 9b0804c78f..243619aece 100644 --- a/pytket/tests/circuit_test.py +++ b/pytket/tests/circuit_test.py @@ -49,6 +49,9 @@ BitRegister, QubitRegister, CXConfigType, + ResourceBounds, + ResourceData, + DummyBox, ) from pytket.circuit.display import get_circuit_renderer, render_circuit_as_html from pytket.circuit.named_types import ( @@ -1257,6 +1260,99 @@ def test_phase_order() -> None: assert c == c1 +def test_dummy_box() -> None: + resource_data = ResourceData( + op_type_count={OpType.T: ResourceBounds(10, 20)}, + gate_depth=ResourceBounds(5, 8), + op_type_depth={OpType.CZ: ResourceBounds(3, 6), OpType.T: ResourceBounds(4, 6)}, + two_qubit_gate_depth=ResourceBounds(4, 5), + ) + dbox = DummyBox(n_qubits=3, n_bits=1, resource_data=resource_data) + c = Circuit(4, 2) + c.add_dummybox(dbox, [0, 2, 3], [1]) + cmds = c.get_commands() + assert len(cmds) == 1 + op = cmds[0].op + assert type(op) is DummyBox + resource_data1 = op.get_resource_data() + op_type_count = resource_data1.get_op_type_count() + assert op_type_count[OpType.T].get_min() == 10 + assert op_type_count[OpType.T].get_max() == 20 + assert json_validate(c) + + +def test_resources() -> None: + resource_data0 = ResourceData( + op_type_count={ + OpType.T: ResourceBounds(1, 2), + OpType.H: ResourceBounds(0, 1), + OpType.CX: ResourceBounds(1, 2), + OpType.CZ: ResourceBounds(3, 3), + }, + gate_depth=ResourceBounds(5, 8), + op_type_depth={ + OpType.T: ResourceBounds(0, 10), + OpType.H: ResourceBounds(0, 10), + OpType.CX: ResourceBounds(1, 2), + OpType.CZ: ResourceBounds(3, 3), + }, + two_qubit_gate_depth=ResourceBounds(4, 5), + ) + dbox0 = DummyBox(n_qubits=2, n_bits=0, resource_data=resource_data0) + resource_data1 = ResourceData( + op_type_count={ + OpType.T: ResourceBounds(2, 2), + OpType.H: ResourceBounds(1, 1), + OpType.CX: ResourceBounds(2, 3), + OpType.CZ: ResourceBounds(3, 5), + }, + gate_depth=ResourceBounds(5, 10), + op_type_depth={ + OpType.T: ResourceBounds(1, 2), + OpType.H: ResourceBounds(2, 4), + OpType.CX: ResourceBounds(1, 1), + OpType.CZ: ResourceBounds(3, 4), + }, + two_qubit_gate_depth=ResourceBounds(3, 5), + ) + dbox1 = DummyBox(n_qubits=3, n_bits=0, resource_data=resource_data1) + c = Circuit(3) + c.H(0) + c.CX(1, 2) + c.CX(0, 1) + c.T(2) + c.H(1) + c.add_dummybox(dbox0, [0, 1], []) + c.CZ(1, 2) + c.add_dummybox(dbox1, [0, 1, 2], []) + c.H(2) + resource_data = c.get_resources() + op_type_count = resource_data.get_op_type_count() + assert op_type_count[OpType.T].get_min() == 4 + assert op_type_count[OpType.T].get_max() == 5 + assert op_type_count[OpType.H].get_min() == 4 + assert op_type_count[OpType.H].get_max() == 5 + assert op_type_count[OpType.CX].get_min() == 5 + assert op_type_count[OpType.CX].get_max() == 7 + assert op_type_count[OpType.CZ].get_min() == 7 + assert op_type_count[OpType.CZ].get_max() == 9 + gate_depth = resource_data.get_gate_depth() + assert gate_depth.get_min() == 15 + assert gate_depth.get_max() == 23 + op_type_depth = resource_data.get_op_type_depth() + assert op_type_depth[OpType.T].get_min() == 2 + assert op_type_depth[OpType.T].get_max() == 12 + assert op_type_depth[OpType.H].get_min() == 5 + assert op_type_depth[OpType.H].get_max() == 17 + assert op_type_depth[OpType.CX].get_min() == 4 + assert op_type_depth[OpType.CX].get_max() == 5 + assert op_type_depth[OpType.CZ].get_min() == 7 + assert op_type_depth[OpType.CZ].get_max() == 8 + two_qubit_gate_depth = resource_data.get_two_qubit_gate_depth() + assert two_qubit_gate_depth.get_min() == 10 + assert two_qubit_gate_depth.get_max() == 13 + + if __name__ == "__main__": test_circuit_gen() test_symbolic_ops() diff --git a/schemas/circuit_v1.json b/schemas/circuit_v1.json index 9844e8f9bb..baca6161eb 100644 --- a/schemas/circuit_v1.json +++ b/schemas/circuit_v1.json @@ -644,6 +644,65 @@ "Matching" ], "definition": "Strategies for synthesising \"ToffoliBoxes\"" + }, + "resource_bound": { + "type": "object", + "properties": { + "max": { + "type": "integer", + "minimum": 0 + }, + "min": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "max", + "min" + ], + "additionalProperties": false + }, + "optype_resource_bound": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/box/properties/resource_bound" + } + ] + }, + "resource_data": { + "type": "object", + "properties": { + "gate_depth": { + "$ref": "#/definitions/box/properties/resource_bound" + }, + "two_qubit_gate_depth": { + "$ref": "#/definitions/box/properties/resource_bound" + }, + "op_type_count": { + "type": "array", + "items": { + "$ref": "#/definitions/box/properties/optype_resource_bound" + } + }, + "op_type_depth": { + "type": "array", + "items": { + "$ref": "#/definitions/box/properties/optype_resource_bound" + } + } + }, + "required": [ + "gate_depth", + "two_qubit_gate_depth", + "op_type_count", + "op_type_depth" + ], + "additionalProperties": false } }, "required": [ @@ -778,6 +837,23 @@ "maxProperties": 5 } }, + { + "if": { + "properties": { + "type": { + "const": "DummyBox" + } + } + }, + "then": { + "required": [ + "n_qubits", + "n_bits", + "resource_data" + ], + "maxProperties": 5 + } + }, { "if": { "properties": { diff --git a/tket/CMakeLists.txt b/tket/CMakeLists.txt index 958ea72490..8b23409ac8 100644 --- a/tket/CMakeLists.txt +++ b/tket/CMakeLists.txt @@ -168,6 +168,8 @@ target_sources(tket src/Circuit/Circuit.cpp src/Circuit/CircuitJson.cpp src/Circuit/CommandJson.cpp + src/Circuit/ResourceData.cpp + src/Circuit/DummyBox.cpp src/Circuit/macro_manipulation.cpp src/Circuit/basic_circ_manip.cpp src/Circuit/latex_drawing.cpp @@ -343,12 +345,14 @@ target_sources(tket include/tket/Circuit/Conditional.hpp include/tket/Circuit/DAGDefs.hpp include/tket/Circuit/DiagonalBox.hpp + include/tket/Circuit/DummyBox.hpp include/tket/Circuit/Multiplexor.hpp include/tket/Circuit/StatePreparation.hpp include/tket/Circuit/ThreeQubitConversion.hpp include/tket/Circuit/ToffoliBox.hpp include/tket/Circuit/PauliExpBoxes.hpp include/tket/Circuit/ConjugationBox.hpp + include/tket/Circuit/ResourceData.hpp include/tket/Circuit/Simulation/CircuitSimulator.hpp include/tket/Circuit/Simulation/PauliExpBoxUnitaryCalculator.hpp include/tket/Architecture/Architecture.hpp diff --git a/tket/conanfile.py b/tket/conanfile.py index 7cf1e667ab..9193b7cacb 100644 --- a/tket/conanfile.py +++ b/tket/conanfile.py @@ -23,7 +23,7 @@ class TketConan(ConanFile): name = "tket" - version = "1.2.67" + version = "1.2.68" package_type = "library" license = "Apache 2" homepage = "https://github.com/CQCL/tket" diff --git a/tket/include/tket/Circuit/Circuit.hpp b/tket/include/tket/Circuit/Circuit.hpp index 7374e1a870..b5eeac9ff4 100644 --- a/tket/include/tket/Circuit/Circuit.hpp +++ b/tket/include/tket/Circuit/Circuit.hpp @@ -46,6 +46,7 @@ #include "Command.hpp" #include "Conditional.hpp" #include "DAGDefs.hpp" +#include "ResourceData.hpp" #include "tket/Gate/OpPtrFunctions.hpp" #include "tket/Utils/Constants.hpp" #include "tket/Utils/GraphHeaders.hpp" @@ -1651,6 +1652,16 @@ class Circuit { std::vector wasmwire; std::size_t _number_of_wasm_wires = 0; + /** + * Calculate the overall resources of the circuit. + * + * This takes account of the data stored in each \ref DummyBox within the + * circuit, as well as other gates, to compute upper and lower bounds. + * + * @return bounds on resources of the circuit + */ + ResourceData get_resources() /*const*/; + private: std::optional name; /** optional string name descriptor for human identification*/ diff --git a/tket/include/tket/Circuit/DummyBox.hpp b/tket/include/tket/Circuit/DummyBox.hpp new file mode 100644 index 0000000000..c43b520a06 --- /dev/null +++ b/tket/include/tket/Circuit/DummyBox.hpp @@ -0,0 +1,91 @@ +// 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 "Boxes.hpp" +#include "ResourceData.hpp" +#include "tket/Utils/Json.hpp" + +namespace tket { + +/** + * Exception indicating that dummy boxes cannot be decomposed. + */ +class DummyBoxNotDecomposable : public std::logic_error { + public: + DummyBoxNotDecomposable() + : std::logic_error("Cannot generate circuit from DummyBox") {} +}; + +/** + * @brief A placeholder operation that holds resource data. + * + * This box type cannot be decomposed into a circuit. It only serves to record + * resource data for a region of a circuit: for example, upper and lower bounds + * on gate counts and depth. A circuit containing such a box cannot be executed. + */ +class DummyBox : public Box { + public: + /** + * @brief Construct a new instance from some resource data. + * + * @param n_qubits number of qubits + * @param n_bits number of bits + * @param resource_data_ resource data + */ + DummyBox( + unsigned n_qubits, unsigned n_bits, const ResourceData &resource_data_); + + /** + * Copy constructor + */ + DummyBox(const DummyBox &other); + + Op_ptr symbol_substitution( + const SymEngine::map_basic_basic &) const override { + return std::make_shared(*this); + } + + SymSet free_symbols() const override { return {}; } + + bool is_equal(const Op &op_other) const override; + + unsigned get_n_qubits() const; + unsigned get_n_bits() const; + ResourceData get_resource_data() const; + + op_signature_t get_signature() const override; + + static Op_ptr from_json(const nlohmann::json &j); + + static nlohmann::json to_json(const Op_ptr &op); + + protected: + /** + * @brief Throw an exception. + * + * This box does not correspond to any actual circuit. + * + * @throws DummyBoxNotDecomposable + */ + void generate_circuit() const override; + + private: + const unsigned n_qubits; + const unsigned n_bits; + const ResourceData resource_data; +}; + +} // namespace tket diff --git a/tket/include/tket/Circuit/ResourceData.hpp b/tket/include/tket/Circuit/ResourceData.hpp new file mode 100644 index 0000000000..8e23dc0d50 --- /dev/null +++ b/tket/include/tket/Circuit/ResourceData.hpp @@ -0,0 +1,60 @@ +// 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 "tket/OpType/OpType.hpp" + +namespace tket { + +template +concept arithmetic = std::integral or std::floating_point; + +template +struct ResourceBounds { + T min; + T max; + bool operator==(const ResourceBounds& other) const { + return min == other.min && max == other.max; + } + ResourceBounds() : min(0), max(0) {} + ResourceBounds(T val) : min(val), max(val) {} + ResourceBounds(T minval, T maxval) : min(minval), max(maxval) {} +}; + +struct ResourceData { + std::map> OpTypeCount; + ResourceBounds GateDepth; + std::map> OpTypeDepth; + ResourceBounds TwoQubitGateDepth; + bool operator==(const ResourceData& other) const; +}; + +template +void to_json(nlohmann::json& j, const ResourceBounds& bounds) { + j["min"] = bounds.min; + j["max"] = bounds.max; +} +template +void from_json(const nlohmann::json& j, ResourceBounds& bounds) { + bounds.min = j.at("min").get(); + bounds.max = j.at("max").get(); +} + +JSON_DECL(ResourceData) + +} // namespace tket diff --git a/tket/include/tket/OpType/OpType.hpp b/tket/include/tket/OpType/OpType.hpp index 833aacd31d..a3ba10d342 100644 --- a/tket/include/tket/OpType/OpType.hpp +++ b/tket/include/tket/OpType/OpType.hpp @@ -719,7 +719,12 @@ enum class OpType { /** * See \ref UnitaryTableauBox */ - UnitaryTableauBox + UnitaryTableauBox, + + /** + * See \ref DummyBox + */ + DummyBox }; JSON_DECL(OpType) diff --git a/tket/include/tket/OpType/OpTypeFunctions.hpp b/tket/include/tket/OpType/OpTypeFunctions.hpp index 1556ba2dbc..6027b5ec72 100644 --- a/tket/include/tket/OpType/OpTypeFunctions.hpp +++ b/tket/include/tket/OpType/OpTypeFunctions.hpp @@ -60,6 +60,12 @@ bool is_initial_q_type(OpType optype); /** Test for output or discard quantum "ops" */ bool is_final_q_type(OpType optype); +/** Test for input "ops" */ +bool is_initial_type(OpType optype); + +/** Test for output "ops" */ +bool is_final_type(OpType optype); + /** Test for input, creation, output or discard "ops" */ bool is_boundary_type(OpType optype); diff --git a/tket/src/Circuit/Circuit.cpp b/tket/src/Circuit/Circuit.cpp index e5e2615746..87cbdcb174 100644 --- a/tket/src/Circuit/Circuit.cpp +++ b/tket/src/Circuit/Circuit.cpp @@ -14,14 +14,22 @@ #include "tket/Circuit/Circuit.hpp" +#include #include #include #include #include #include +#include #include #include +#include "tket/Circuit/DAGDefs.hpp" +#include "tket/Circuit/DummyBox.hpp" +#include "tket/Circuit/ResourceData.hpp" +#include "tket/OpType/OpDesc.hpp" +#include "tket/OpType/OpType.hpp" +#include "tket/OpType/OpTypeFunctions.hpp" #include "tket/Utils/Expression.hpp" #include "tket/Utils/GraphHeaders.hpp" #include "tket/Utils/HelperFunctions.hpp" @@ -36,17 +44,17 @@ namespace tket { // information apart from qubit path and slices can be seen easily. // Very useful for debugging and eyeball comparison of circuits. // out << "\nrankdir=\"LR\"" <--- put this in for Left-Right circuit -void Circuit::to_graphviz(std::ostream &out) const { +void Circuit::to_graphviz(std::ostream& out) const { IndexMap im = index_map(); out << "digraph G {\n"; out << "{ rank = same\n"; - for (const Vertex &v : all_inputs()) { + for (const Vertex& v : all_inputs()) { out << im[v] << " "; } out << "}\n"; out << "{ rank = same\n"; - for (const Vertex &v : all_outputs()) { + for (const Vertex& v : all_outputs()) { out << im[v] << " "; } out << "}\n"; @@ -66,7 +74,7 @@ void Circuit::to_graphviz(std::ostream &out) const { out << "}"; } -void Circuit::to_graphviz_file(const std::string &filename) const { +void Circuit::to_graphviz_file(const std::string& filename) const { std::ofstream dot_file(filename); to_graphviz(dot_file); } @@ -140,9 +148,9 @@ Expr Circuit::get_phase() const { void Circuit::add_phase(Expr a) { phase += a; } -void Circuit::symbol_substitution(const symbol_map_t &symbol_map) { +void Circuit::symbol_substitution(const symbol_map_t& symbol_map) { SymEngine::map_basic_basic sub_map; - for (const std::pair &p : symbol_map) { + for (const std::pair& p : symbol_map) { ExprPtr s = p.first; ExprPtr e = p.second; // This is a workaround for a symengine issue: symengine currently has poor @@ -158,7 +166,7 @@ void Circuit::symbol_substitution(const symbol_map_t &symbol_map) { } void Circuit::symbol_substitution( - const std::map &symbol_map) { + const std::map& symbol_map) { symbol_map_t s_map; for (std::pair p : symbol_map) { s_map[p.first] = Expr(p.second); @@ -192,7 +200,7 @@ bool Circuit::is_symbolic() const { return !free_symbols().empty(); } // check aspects of circuit for equality, and optionally throw exceptions when // not met bool Circuit::circuit_equality( - const Circuit &other, const std::set &except, + const Circuit& other, const std::set& except, bool throw_error) const { bool check = true; check &= check_iterators_equality(*this, other); @@ -265,9 +273,9 @@ bool Circuit::circuit_equality( // depth) // TODO:: rewrite to work with classical boxes bool Circuit::in_causal_order( - const Vertex &target, const Vertex &from, bool forward, - const std::map &v_to_depth, - const std::map &v_to_units, bool strict) const { + const Vertex& target, const Vertex& from, bool forward, + const std::map& v_to_depth, + const std::map& v_to_units, bool strict) const { unsigned target_depth = v_to_depth.at(target); if (!strict && from == target) return true; if (v_to_depth.at(from) >= target_depth) return false; @@ -285,7 +293,7 @@ bool Circuit::in_causal_order( std::set to_search(c); if (forward) { VertexVec succs = get_successors(from); - for (const Vertex &s : succs) { + for (const Vertex& s : succs) { if (v_to_depth.find(s) != v_to_depth.end()) { to_search.insert(s); } @@ -300,14 +308,14 @@ bool Circuit::in_causal_order( to_search.erase(to_search.begin()); if (v_to_depth.at(v) > target_depth) continue; unit_set_t v_units = v_to_units.at(v); - for (const UnitID &u : lookup_units) { + for (const UnitID& u : lookup_units) { if (v_units.find(u) != v_units.end()) { return true; } } if (forward) { VertexVec succs = get_successors(v); - for (const Vertex &s : succs) { + for (const Vertex& s : succs) { if (v_to_depth.find(s) != v_to_depth.end()) { to_search.insert(s); } @@ -320,4 +328,105 @@ bool Circuit::in_causal_order( return false; } +// Update depth fields of `data` for a vertex with data for the vertex's +// predecessors `preds`, which is assumed to be already stored in `datamap`. +static void update_from_predecessors( + ResourceData& data, const VertexVec& preds, + const std::map& datamap) { + std::vector pre_data; + for (const Vertex& pre_v : preds) { + pre_data.push_back(datamap.at(pre_v)); + } + // 1. GateDepth + data.GateDepth.min += std::max_element( + pre_data.begin(), pre_data.end(), + [](const ResourceData& a, const ResourceData& b) { + return a.GateDepth.min < b.GateDepth.min; + }) + ->GateDepth.min; + data.GateDepth.max += std::max_element( + pre_data.begin(), pre_data.end(), + [](const ResourceData& a, const ResourceData& b) { + return a.GateDepth.max < b.GateDepth.max; + }) + ->GateDepth.max; + // 2. OpTypeDepth + std::map min_depths; + std::map max_depths; + for (const ResourceData& pre_v_data : pre_data) { + for (const auto& pair : pre_v_data.OpTypeDepth) { + if (pair.second.min > min_depths[pair.first]) { + min_depths[pair.first] = pair.second.min; + } + if (pair.second.max > max_depths[pair.first]) { + max_depths[pair.first] = pair.second.max; + } + } + } + for (const auto& pair : min_depths) { + data.OpTypeDepth[pair.first].min += min_depths[pair.first]; + } + for (const auto& pair : max_depths) { + data.OpTypeDepth[pair.first].max += max_depths[pair.first]; + } + // 3. TwoQubitGateDepth + data.TwoQubitGateDepth.min += + std::max_element( + pre_data.begin(), pre_data.end(), + [](const ResourceData& a, const ResourceData& b) { + return a.TwoQubitGateDepth.min < b.TwoQubitGateDepth.min; + }) + ->TwoQubitGateDepth.min; + data.TwoQubitGateDepth.max += + std::max_element( + pre_data.begin(), pre_data.end(), + [](const ResourceData& a, const ResourceData& b) { + return a.TwoQubitGateDepth.max < b.TwoQubitGateDepth.max; + }) + ->TwoQubitGateDepth.max; +} + +ResourceData Circuit::get_resources() /*const*/ { + // Traverse the DAG in topological order. At each vertex compute a new + // ResourceData based on the ResourceData already computed for its immediate + // predecessors. Compute final ResourceData based on terminal nodes. + const VertexVec vertices = vertices_in_order(); + std::map datamap; + std::map> op_type_count; + for (const Vertex& v : vertices) { + ResourceData data; + OpType optype = get_OpType_from_Vertex(v); + if (!is_initial_type(optype)) { + if (!is_final_type(optype)) { + if (optype == OpType::DummyBox) { + const DummyBox& dbox = + static_cast(*get_Op_ptr_from_Vertex(v)); + data = dbox.get_resource_data(); + for (const auto& pair : data.OpTypeCount) { + op_type_count[pair.first].min += pair.second.min; + op_type_count[pair.first].max += pair.second.max; + } + } else { + data.GateDepth = {1}; + data.OpTypeDepth[optype] = {1}; + if (OpDesc(optype).is_gate() && + get_Op_ptr_from_Vertex(v)->n_qubits() == 2) { + data.TwoQubitGateDepth = {1}; + } + op_type_count[optype].min += 1; + op_type_count[optype].max += 1; + } + } + // Aggregate with predecessors + update_from_predecessors(data, get_predecessors(v), datamap); + } + datamap[v] = data; + } + // Finally aggregate outputs + ResourceData final_data; + update_from_predecessors(final_data, all_outputs(), datamap); + final_data.OpTypeCount = op_type_count; + return final_data; +} + } // namespace tket diff --git a/tket/src/Circuit/DummyBox.cpp b/tket/src/Circuit/DummyBox.cpp new file mode 100644 index 0000000000..3b9c4f1a3c --- /dev/null +++ b/tket/src/Circuit/DummyBox.cpp @@ -0,0 +1,76 @@ +// 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/Circuit/DummyBox.hpp" + +#include + +#include "Circuit/ResourceData.hpp" +#include "tket/Ops/OpJsonFactory.hpp" + +namespace tket { + +DummyBox::DummyBox( + unsigned n_qubits_, unsigned n_bits_, const ResourceData &resource_data_) + : Box(OpType::DummyBox), + n_qubits(n_qubits_), + n_bits(n_bits_), + resource_data(resource_data_) {} + +DummyBox::DummyBox(const DummyBox &other) + : Box(other), + n_qubits(other.n_qubits), + n_bits(other.n_bits), + resource_data(other.resource_data) {} + +bool DummyBox::is_equal(const Op &op_other) const { + const DummyBox &other = dynamic_cast(op_other); + if (id_ == other.get_id()) return true; + return resource_data == other.resource_data; +} + +unsigned DummyBox::get_n_qubits() const { return n_qubits; } +unsigned DummyBox::get_n_bits() const { return n_bits; } +ResourceData DummyBox::get_resource_data() const { return resource_data; } + +op_signature_t DummyBox::get_signature() const { + op_signature_t sig(n_qubits, EdgeType::Quantum); + op_signature_t bits(n_bits, EdgeType::Classical); + sig.insert(sig.end(), bits.begin(), bits.end()); + return sig; +} + +nlohmann::json DummyBox::to_json(const Op_ptr &op) { + const auto &box = static_cast(*op); + nlohmann::json j = core_box_json(box); + j["n_qubits"] = box.get_n_qubits(); + j["n_bits"] = box.get_n_bits(); + j["resource_data"] = box.get_resource_data(); + return j; +} + +Op_ptr DummyBox::from_json(const nlohmann::json &j) { + DummyBox box = DummyBox( + j.at("n_qubits").get(), j.at("n_bits").get(), + j.at("resource_data").get()); + return set_box_id( + box, + boost::lexical_cast(j.at("id").get())); +} + +REGISTER_OPFACTORY(DummyBox, DummyBox) + +void DummyBox::generate_circuit() const { throw DummyBoxNotDecomposable(); } + +} // namespace tket diff --git a/tket/src/Circuit/ResourceData.cpp b/tket/src/Circuit/ResourceData.cpp new file mode 100644 index 0000000000..f8e577dfa5 --- /dev/null +++ b/tket/src/Circuit/ResourceData.cpp @@ -0,0 +1,44 @@ +// 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/Circuit/ResourceData.hpp" + +#include "tket/Utils/Json.hpp" + +namespace tket { + +bool ResourceData::operator==(const ResourceData& other) const { + return OpTypeCount == other.OpTypeCount && GateDepth == other.GateDepth && + OpTypeDepth == other.OpTypeDepth && + TwoQubitGateDepth == other.TwoQubitGateDepth; +} + +void to_json(nlohmann::json& j, const ResourceData& data) { + j["op_type_count"] = data.OpTypeCount; + j["gate_depth"] = data.GateDepth; + j["op_type_depth"] = data.OpTypeDepth; + j["two_qubit_gate_depth"] = data.TwoQubitGateDepth; +} + +void from_json(const nlohmann::json& j, ResourceData& data) { + data.OpTypeCount = + j.at("op_type_count").get>>(); + data.GateDepth = j.at("gate_depth").get>(); + data.OpTypeDepth = + j.at("op_type_depth").get>>(); + data.TwoQubitGateDepth = + j.at("two_qubit_gate_depth").get>(); +} + +} // namespace tket diff --git a/tket/src/Circuit/setters_and_getters.cpp b/tket/src/Circuit/setters_and_getters.cpp index 09141a5adb..0c4e9c3be7 100644 --- a/tket/src/Circuit/setters_and_getters.cpp +++ b/tket/src/Circuit/setters_and_getters.cpp @@ -72,7 +72,9 @@ void Circuit::assert_valid() const { // VertexVec Circuit::all_inputs() const { VertexVec ins = q_inputs(); VertexVec c_ins = c_inputs(); + VertexVec w_ins = w_inputs(); ins.insert(ins.end(), c_ins.begin(), c_ins.end()); + ins.insert(ins.end(), w_ins.begin(), w_ins.end()); return ins; } @@ -107,7 +109,9 @@ VertexVec Circuit::w_inputs() const { VertexVec Circuit::all_outputs() const { VertexVec outs = q_outputs(); VertexVec c_outs = c_outputs(); + VertexVec w_outs = w_outputs(); outs.insert(outs.end(), c_outs.begin(), c_outs.end()); + outs.insert(outs.end(), w_outs.begin(), w_outs.end()); return outs; } diff --git a/tket/src/OpType/OpTypeFunctions.cpp b/tket/src/OpType/OpTypeFunctions.cpp index 698b3768f8..c4cb701778 100644 --- a/tket/src/OpType/OpTypeFunctions.cpp +++ b/tket/src/OpType/OpTypeFunctions.cpp @@ -142,6 +142,16 @@ bool is_final_q_type(OpType optype) { return optype == OpType::Output || optype == OpType::Discard; } +bool is_initial_type(OpType optype) { + return optype == OpType::Input || optype == OpType::Create || + optype == OpType::ClInput || optype == OpType::WASMInput; +} + +bool is_final_type(OpType optype) { + return optype == OpType::Output || optype == OpType::Discard || + optype == OpType::ClOutput || optype == OpType::WASMOutput; +} + bool is_boundary_type(OpType optype) { return is_boundary_q_type(optype) || is_boundary_c_type(optype) || is_boundary_w_type(optype); @@ -188,7 +198,8 @@ bool is_box_type(OpType optype) { OpType::ProjectorAssertionBox, OpType::StabiliserAssertionBox, OpType::UnitaryTableauBox, - OpType::ToffoliBox}; + OpType::ToffoliBox, + OpType::DummyBox}; return find_in_set(optype, boxes); } diff --git a/tket/src/OpType/OpTypeInfo.cpp b/tket/src/OpType/OpTypeInfo.cpp index 5227e22937..8a73aa8c38 100644 --- a/tket/src/OpType/OpTypeInfo.cpp +++ b/tket/src/OpType/OpTypeInfo.cpp @@ -157,6 +157,7 @@ const std::map& optypeinfo() { {OpType::StabiliserAssertionBox, {"StabiliserAssertionBox", "StabiliserAssertionBox", {}, std::nullopt}}, {OpType::ToffoliBox, {"ToffoliBox", "ToffoliBox", {}, std::nullopt}}, + {OpType::DummyBox, {"DummyBox", "DummyBox", {}, std::nullopt}}, {OpType::ClassicalTransform, {"ClassicalTransform", "ClassicalTransform", {}, std::nullopt}}, {OpType::WASM, {"WASM", "WASM", {}, std::nullopt}}, diff --git a/tket/test/CMakeLists.txt b/tket/test/CMakeLists.txt index 1074497da3..d5ff0bfee7 100644 --- a/tket/test/CMakeLists.txt +++ b/tket/test/CMakeLists.txt @@ -119,6 +119,7 @@ add_executable(test-tket src/Circuit/test_DiagonalBox.cpp src/Circuit/test_ToffoliBox.cpp src/Circuit/test_ConjugationBox.cpp + src/Circuit/test_DummyBox.cpp src/test_UnitaryTableau.cpp src/test_ChoiMixTableau.cpp src/test_Diagonalisation.cpp diff --git a/tket/test/src/Circuit/test_DummyBox.cpp b/tket/test/src/Circuit/test_DummyBox.cpp new file mode 100644 index 0000000000..2763680853 --- /dev/null +++ b/tket/test/src/Circuit/test_DummyBox.cpp @@ -0,0 +1,55 @@ +// 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/Circuit/Circuit.hpp" +#include "tket/Circuit/DummyBox.hpp" +#include "tket/Circuit/ResourceData.hpp" +namespace tket { + +SCENARIO("DummyBox") { + GIVEN("ResourceData") { + DummyBox dbox0( + 1, 0, + ResourceData{ + {{OpType::H, ResourceBounds(3, 4)}}, + ResourceBounds(2, 3), + {{OpType::H, ResourceBounds(3)}}, + ResourceBounds()}); + DummyBox dbox1( + 2, 0, + ResourceData{ + {{OpType::H, ResourceBounds(3, 4)}, + {OpType::CX, ResourceBounds(2, 8)}}, + ResourceBounds(2, 3), + {{OpType::CX, ResourceBounds(2, 8)}}, + ResourceBounds(4, 8)}); + Circuit c(2); + c.add_op(OpType::CX, {0, 1}); + c.add_box(dbox0, {0}); + c.add_box(dbox1, {0, 1}); + ResourceData data = c.get_resources(); + ResourceData expected{ + {{OpType::H, ResourceBounds(6, 8)}, + {OpType::CX, ResourceBounds(3, 9)}}, + ResourceBounds(5, 7), + {{OpType::H, ResourceBounds(3)}, + {OpType::CX, ResourceBounds(3, 9)}}, + ResourceBounds(5, 9)}; + CHECK(data == expected); + } +} + +} // namespace tket diff --git a/tket/test/src/test_json.cpp b/tket/test/src/test_json.cpp index 13849fcca9..91d576aaac 100644 --- a/tket/test/src/test_json.cpp +++ b/tket/test/src/test_json.cpp @@ -25,6 +25,7 @@ #include "tket/Circuit/Command.hpp" #include "tket/Circuit/ConjugationBox.hpp" #include "tket/Circuit/DiagonalBox.hpp" +#include "tket/Circuit/DummyBox.hpp" #include "tket/Circuit/Multiplexor.hpp" #include "tket/Circuit/PauliExpBoxes.hpp" #include "tket/Circuit/Simulation/CircuitSimulator.hpp" @@ -400,6 +401,23 @@ SCENARIO("Test Circuit serialization") { REQUIRE(t_b == tbox); } + GIVEN("DummyBox") { + ResourceData data{ + {{OpType::H, ResourceBounds(3, 4)}, + {OpType::CX, ResourceBounds(2, 8)}}, + ResourceBounds(2, 3), + {{OpType::CX, ResourceBounds(2, 8)}}, + ResourceBounds(4, 8)}; + DummyBox dbox(2, 0, data); + Circuit c(2); + c.add_box(dbox, {0, 1}); + nlohmann::json j_c = c; + Circuit c1 = j_c.get(); + const auto& dbox1 = + static_cast(*c1.get_commands()[0].get_op_ptr()); + REQUIRE(dbox == dbox1); + } + GIVEN("CustomGate") { Circuit setup(2); Sym a = SymTable::fresh_symbol("a");