Skip to content

Commit

Permalink
[feature] Methods for adding a CircBox registerwise (#1260)
Browse files Browse the repository at this point in the history
  • Loading branch information
cqc-alec authored Feb 22, 2024
1 parent cef4c06 commit 5e21d78
Show file tree
Hide file tree
Showing 14 changed files with 352 additions and 74 deletions.
1 change: 1 addition & 0 deletions pytket/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
157 changes: 151 additions & 6 deletions pytket/binders/circuit/Circuit/add_op.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -41,6 +42,8 @@ namespace tket {
typedef py::tket_custom::SequenceVec<UnitID> py_unit_vector_t;
typedef py::tket_custom::SequenceVec<Bit> py_bit_vector_t;
typedef py::tket_custom::SequenceVec<Qubit> py_qubit_vector_t;
typedef py::tket_custom::SequenceVec<QubitRegister> py_qreg_vector_t;
typedef py::tket_custom::SequenceVec<BitRegister> py_creg_vector_t;

const bit_vector_t no_bits;

Expand Down Expand Up @@ -219,9 +222,13 @@ void init_circuit_add_op(py::class_<Circuit, std::shared_ptr<Circuit>> &c) {
return add_box_method(
circ, std::make_shared<CircBox>(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(
Expand Down Expand Up @@ -517,11 +524,149 @@ void init_circuit_add_op(py::class_<Circuit, std::shared_ptr<Circuit>> &c) {
return add_box_method(
circ, std::make_shared<CircBox>(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<UnitID> 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<CircBox>(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<std::string, std::string> &qregmap,
const std::map<std::string, std::string> &cregmap,
const py::kwargs &kwargs) {
// Get registers of circuit:
std::vector<QubitRegister> circ_qregs =
get_unit_registers<QubitRegister>(*circ);
std::vector<BitRegister> circ_cregs =
get_unit_registers<BitRegister>(*circ);

// Map name -> size for circuit registers:
std::map<std::string, std::size_t> circ_qreg_sizes;
for (const QubitRegister &qreg : circ_qregs) {
circ_qreg_sizes[qreg.name()] = qreg.size();
}
std::map<std::string, unsigned> circ_creg_sizes;
for (const BitRegister &creg : circ_cregs) {
circ_creg_sizes[creg.name()] = creg.size();
}

// Get box circuit:
std::shared_ptr<Circuit> 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<QubitRegister> box_qregs =
get_unit_registers<QubitRegister>(*box_circ);
std::vector<BitRegister> box_cregs =
get_unit_registers<BitRegister>(*box_circ);

// Map name -> size for box registers
std::map<std::string, std::size_t> box_qreg_sizes;
for (const QubitRegister &qreg : box_qregs) {
box_qreg_sizes[qreg.name()] = qreg.size();
}
std::map<std::string, unsigned> 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<UnitID> 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<CircBox>(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,
Expand Down
50 changes: 5 additions & 45 deletions pytket/binders/circuit/Circuit/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -58,43 +59,6 @@ UnitID to_cpp_unitid(const PyUnitID &py_unitid) {
return get<Bit>(py_unitid);
}

template <typename T>
std::vector<T> get_unit_registers(Circuit &circ) {
static_assert(
std::is_same<T, QubitRegister>::value ||
std::is_same<T, BitRegister>::value,
"T must be either QubitRegister or BitRegister");
using T2 = typename std::conditional<
std::is_same<T, QubitRegister>::value, Qubit, Bit>::type;
std::vector<T2> unitids;
if constexpr (std::is_same<T, QubitRegister>::value) {
unitids = circ.all_qubits();
} else if constexpr (std::is_same<T, BitRegister>::value) {
unitids = circ.all_bits();
}
// map from register name to unsigned indices
std::map<std::string, std::set<unsigned>> unit_map;
std::vector<T> 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_<Circuit, std::shared_ptr<Circuit>> &c);
void init_circuit_add_classical_op(
py::class_<Circuit, std::shared_ptr<Circuit>> &c);
Expand Down Expand Up @@ -291,10 +255,8 @@ void def_circuit(py::class_<Circuit, std::shared_ptr<Circuit>> &pyCircuit) {
.def_property_readonly(
"c_registers", &get_unit_registers<BitRegister>,
"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",
Expand All @@ -313,10 +275,8 @@ void def_circuit(py::class_<Circuit, std::shared_ptr<Circuit>> &pyCircuit) {
.def_property_readonly(
"q_registers", &get_unit_registers<QubitRegister>,
"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,
Expand Down
59 changes: 59 additions & 0 deletions pytket/binders/include/circuit_registers.hpp
Original file line number Diff line number Diff line change
@@ -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 <typename T>
std::vector<T> get_unit_registers(Circuit &circ) {
static_assert(
std::is_same<T, QubitRegister>::value ||
std::is_same<T, BitRegister>::value,
"T must be either QubitRegister or BitRegister");
using T2 = typename std::conditional<
std::is_same<T, QubitRegister>::value, Qubit, Bit>::type;
std::vector<T2> unitids;
if constexpr (std::is_same<T, QubitRegister>::value) {
unitids = circ.all_qubits();
} else if constexpr (std::is_same<T, BitRegister>::value) {
unitids = circ.all_bits();
}
// map from register name to unsigned indices
std::map<std::string, std::set<unsigned>> unit_map;
std::vector<T> 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
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.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")
Expand Down
11 changes: 11 additions & 0 deletions pytket/docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -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)
----------------------

Expand Down
Loading

0 comments on commit 5e21d78

Please sign in to comment.