Skip to content

Commit

Permalink
[feature] Add DummyBox (#1120)
Browse files Browse the repository at this point in the history
  • Loading branch information
cqc-alec authored Nov 14, 2023
1 parent 145a37e commit 73b44bb
Show file tree
Hide file tree
Showing 26 changed files with 1,117 additions and 21 deletions.
42 changes: 42 additions & 0 deletions pytket/binders/circuit/Circuit/add_op.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -332,6 +334,30 @@ void init_circuit_add_op(py::class_<Circuit, std::shared_ptr<Circuit>> &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<unsigned> &qubits,
const py::tket_custom::SequenceVec<unsigned> &bits,
const py::kwargs &kwargs) {
std::vector<UnitID> args;
for (unsigned i : qubits) {
args.push_back(Qubit(i));
}
for (unsigned i : bits) {
args.push_back(Bit(i));
}
return add_box_method<UnitID>(
circ, std::make_shared<DummyBox>(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,
Expand Down Expand Up @@ -603,6 +629,22 @@ void init_circuit_add_op(py::class_<Circuit, std::shared_ptr<Circuit>> &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<UnitID> args = {qubits.begin(), qubits.end()};
args.insert(args.end(), bits.begin(), bits.end());
return add_box_method<UnitID>(
circ, std::make_shared<DummyBox>(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,
Expand Down
77 changes: 77 additions & 0 deletions pytket/binders/circuit/Circuit/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -763,6 +764,17 @@ void def_circuit(py::class_<Circuit, std::shared_ptr<Circuit>> &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,
Expand Down Expand Up @@ -854,6 +866,71 @@ void def_circuit(py::class_<Circuit, std::shared_ptr<Circuit>> &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) {
Expand Down
130 changes: 130 additions & 0 deletions pytket/binders/circuit/boxes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,22 @@
#include <pybind11/eigen.h>
#include <pybind11/pybind11.h>

#include <memory>
#include <sstream>

#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"
Expand Down Expand Up @@ -453,6 +459,130 @@ void init_boxes(py::module &m) {
.def(
"get_rotation_axis", &ToffoliBox::get_rotation_axis,
":return: the rotation axis");
py::class_<ResourceBounds<unsigned>>(
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<unsigned>{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<unsigned> &resource_bounds) {
return resource_bounds.min;
},
":return: the minimum value")
.def(
"get_max",
[](const ResourceBounds<unsigned> &resource_bounds) {
return resource_bounds.max;
},
":return: the maximum value");
py::class_<ResourceData>(
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<OpType, ResourceBounds<unsigned>> op_type_count,
ResourceBounds<unsigned> gate_depth,
std::map<OpType, ResourceBounds<unsigned>> op_type_depth,
ResourceBounds<unsigned> 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_<DummyBox, std::shared_ptr<DummyBox>, 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_<QControlBox, std::shared_ptr<QControlBox>, Op>(
m, "QControlBox",
"A user-defined controlled operation specified by an "
Expand Down
3 changes: 3 additions & 0 deletions pytket/binders/circuit/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 "
Expand Down
2 changes: 1 addition & 1 deletion pytket/conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
3 changes: 3 additions & 0 deletions pytket/docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
11 changes: 10 additions & 1 deletion pytket/docs/circuit.rst
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,13 @@ pytket.circuit
:members:
.. autoclass:: pytket._tket.circuit.ConjugationBox
:special-members:
:members:
: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:
Loading

0 comments on commit 73b44bb

Please sign in to comment.