Skip to content

Commit

Permalink
Feature/add control_state argument to QControlBox (#977)
Browse files Browse the repository at this point in the history
* Add control_state to QControlBox

* Modify constructors in binder

* Update json schema

* Update changelog

* bump tket version

* fix typo

* Change error message

* Fix command string

* get_control_state returns integer

* Update docstring

Co-authored-by: Alec Edgington <54802828+cqc-alec@users.noreply.github.com>

* add get_control_state_bits that returns a bit vect

* Update C++ docstring

* Add one more test case

* Update tket/include/tket/Circuit/Boxes.hpp

Co-authored-by: Alec Edgington <54802828+cqc-alec@users.noreply.github.com>

* Update tket/test/src/Circuit/test_Boxes.cpp

Co-authored-by: Alec Edgington <54802828+cqc-alec@users.noreply.github.com>

---------

Co-authored-by: Alec Edgington <54802828+cqc-alec@users.noreply.github.com>
  • Loading branch information
yao-cqc and cqc-alec authored Aug 22, 2023
1 parent 555a01b commit ea8b26e
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 28 deletions.
43 changes: 41 additions & 2 deletions pytket/binders/circuit/boxes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,35 @@ void init_boxes(py::module &m) {
py::class_<QControlBox, std::shared_ptr<QControlBox>, Op>(
m, "QControlBox",
"A user-defined controlled operation specified by an "
":py:class:`Op` and the number of quantum controls.")
":py:class:`Op`, the number of quantum controls, and the control state "
"expressed as an integer or a bit vector.")
.def(
py::init<Op_ptr &, unsigned, std::vector<bool> &>(),
"Construct from an :py:class:`Op`, a number of quantum "
"controls, and the control state expressed as a bit vector. The "
"controls occupy the low-index ports of the "
"resulting operation.\n\n"
":param op: the underlying operator\n"
":param n_controls: the number of control qubits. Default to 1\n"
":param control_state: the control state expressed as a bit vector. "
"Default to all 1s\n",
py::arg("op"), py::arg("n_controls") = 1,
py::arg("control_state") = std::vector<bool>())
.def(
py::init([](Op_ptr &op, unsigned n_controls,
unsigned long long control_state) {
return QControlBox(
op, n_controls, dec_to_bin(control_state, n_controls));
}),
"Construct from an :py:class:`Op`, a number of quantum "
"controls, and the control state expressed as an integer. The "
"controls occupy the low-index ports of the "
"resulting operation.\n\n"
":param op: the underlying operator\n"
":param n_controls: the number of control qubits\n"
":param control_state: the control state expressed as an integer. "
"Big-endian\n",
py::arg("op"), py::arg("n_controls"), py::arg("control_state"))
.def(
py::init<Op_ptr &, unsigned>(),
"Construct from an :py:class:`Op` and a number of quantum "
Expand All @@ -293,7 +321,18 @@ void init_boxes(py::module &m) {
.def("get_op", &QControlBox::get_op, ":return: the underlying operator")
.def(
"get_n_controls", &QControlBox::get_n_controls,
":return: the number of control qubits");
":return: the number of control qubits")
.def(
"get_control_state",
[](QControlBox &qcbox) {
return bin_to_dec(qcbox.get_control_state());
},
":return: the control state as an integer (big-endian binary "
"representation)")
.def(
"get_control_state_bits",
[](QControlBox &qcbox) { return qcbox.get_control_state(); },
":return: the control state as a bit vector");

py::class_<CompositeGateDef, composite_def_ptr_t>(
m, "CustomGateDef",
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.35@tket/stable")
self.requires("tket/1.2.36@tket/stable")
self.requires("tklog/0.3.3@tket/stable")
self.requires("tkrng/0.3.3@tket/stable")
self.requires("tkassert/0.3.3@tket/stable")
Expand Down
1 change: 1 addition & 0 deletions pytket/docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Minor new features:
would increase the overall complexity of the expressions. This behaviour can
be overridden using the ``always_squash_symbols`` parameter to
``SquashCustom``.
* Add ``control_state`` argument to ``QControlBox``.

Fixes:

Expand Down
16 changes: 16 additions & 0 deletions pytket/tests/circuit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1155,6 +1155,22 @@ def test_counting_n_qubit_gates() -> None:
assert c.n_nqb_gates(5) == 1


def test_qcontrol_box_constructors() -> None:
# only one argument
qcbox1 = QControlBox(Op.create(OpType.S))
# two arguments
qcbox2 = QControlBox(Op.create(OpType.S), 1)
# all arguments. state expressed as an integer
qcbox3 = QControlBox(Op.create(OpType.S), 2, 1)
# all arguments. state expressed as a bit vector
qcbox4 = QControlBox(Op.create(OpType.S), 2, [0, 1])
assert qcbox1 == qcbox2
assert qcbox3 == qcbox4
assert qcbox1.get_control_state() == 1
assert qcbox3.get_control_state() == 1
assert qcbox3.get_control_state_bits() == [0, 1]


def test_error_wrong_parameters() -> None:
circ = Circuit(1, 1)
with pytest.raises(RuntimeError):
Expand Down
8 changes: 7 additions & 1 deletion schemas/circuit_v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,11 @@
"minimum": 0,
"description": "Number of qubits a QControlBox is conditional on."
},
"control_state": {
"type": "integer",
"minimum": 0,
"description": "Control state expressed as an integer."
},
"n_i": {
"type": "integer",
"minimum": 0
Expand Down Expand Up @@ -882,7 +887,8 @@
"then": {
"required": [
"n_controls",
"op"
"op",
"control_state"
]
}
},
Expand Down
2 changes: 1 addition & 1 deletion tket/conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

class TketConan(ConanFile):
name = "tket"
version = "1.2.35"
version = "1.2.36"
package_type = "library"
license = "Apache 2"
homepage = "https://github.com/CQCL/tket"
Expand Down
15 changes: 13 additions & 2 deletions tket/include/tket/Circuit/Boxes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -498,8 +498,13 @@ class QControlBox : public Box {
*
* @param op op to control
* @param n_controls number of qubit controls to add
* @param control_state control state expressed as a bit vector.
* If control_state is non-empty, its size should match n_controls.
* An empty vector is converted to an all-1s vector of length n_controls.
*/
explicit QControlBox(const Op_ptr &op, unsigned n_controls = 1);
explicit QControlBox(
const Op_ptr &op, unsigned n_controls = 1,
const std::vector<bool> &control_state = {});

/**
* Copy constructor
Expand Down Expand Up @@ -528,6 +533,7 @@ class QControlBox : public Box {

Op_ptr get_op() const { return op_; }
unsigned get_n_controls() const { return n_controls_; }
std::vector<bool> get_control_state() const { return control_state_; }

static Op_ptr from_json(const nlohmann::json &j);

Expand All @@ -536,12 +542,17 @@ class QControlBox : public Box {
protected:
void generate_circuit() const override;
QControlBox()
: Box(OpType::QControlBox), op_(), n_controls_(0), n_inner_qubits_(0) {}
: Box(OpType::QControlBox),
op_(),
n_controls_(0),
n_inner_qubits_(0),
control_state_() {}

private:
const Op_ptr op_;
const unsigned n_controls_;
unsigned n_inner_qubits_;
const std::vector<bool> control_state_;
};

class ProjectorAssertionBox : public Box {
Expand Down
53 changes: 41 additions & 12 deletions tket/src/Circuit/Boxes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "tket/Ops/OpPtr.hpp"
#include "tket/Utils/EigenConfig.hpp"
#include "tket/Utils/Expression.hpp"
#include "tket/Utils/HelperFunctions.hpp"
#include "tket/Utils/Json.hpp"
#include "tket/Utils/PauliStrings.hpp"

Expand Down Expand Up @@ -341,8 +342,19 @@ bool CustomGate::is_clifford() const {
return true;
}

QControlBox::QControlBox(const Op_ptr &op, unsigned n_controls)
: Box(OpType::QControlBox), op_(op), n_controls_(n_controls) {
QControlBox::QControlBox(
const Op_ptr &op, unsigned n_controls,
const std::vector<bool> &control_state)
: Box(OpType::QControlBox),
op_(op),
n_controls_(n_controls),
control_state_(
control_state.empty() ? std::vector<bool>(n_controls, true)
: control_state) {
if (n_controls != control_state_.size()) {
throw CircuitInvalidity(
"The size of control_state doesn't match the argument n_controls");
}
op_signature_t inner_sig = op_->get_signature();
n_inner_qubits_ = inner_sig.size();
if (std::count(inner_sig.begin(), inner_sig.end(), EdgeType::Quantum) !=
Expand All @@ -357,7 +369,8 @@ QControlBox::QControlBox(const QControlBox &other)
: Box(other),
op_(other.op_),
n_controls_(other.n_controls_),
n_inner_qubits_(other.n_inner_qubits_) {}
n_inner_qubits_(other.n_inner_qubits_),
control_state_(other.control_state_) {}

Op_ptr QControlBox::symbol_substitution(
const SymEngine::map_basic_basic &sub_map) const {
Expand All @@ -371,9 +384,9 @@ std::string QControlBox::get_command_str(const unit_vector_t &args) const {
std::stringstream out;
out << "qif (";
if (n_controls_ > 0) {
out << args.at(0).repr();
out << args.at(0).repr() << " = " << control_state_.at(0);
for (unsigned i = 1; i < n_controls_; ++i) {
out << ", " << args.at(i).repr();
out << ", " << args.at(i).repr() << " = " << control_state_.at(i);
}
}
unit_vector_t inner_args(args.begin() + n_controls_, args.end());
Expand All @@ -387,32 +400,42 @@ void QControlBox::generate_circuit() const {
std::iota(qbs.begin(), qbs.end(), 0);
c.add_op(op_, qbs);
c.decompose_boxes_recursively();
Circuit x_circ(n_controls_ + n_inner_qubits_);
for (unsigned i = 0; i < n_controls_; i++) {
if (!control_state_.at(i)) {
x_circ.add_op<unsigned>(OpType::X, {i});
}
}
c = with_controls(c, n_controls_);
circ_ = std::make_shared<Circuit>(c);
circ_ = std::make_shared<Circuit>(x_circ >> c >> x_circ);
}

Op_ptr QControlBox::dagger() const {
const Op_ptr inner_dagger = op_->dagger();
return std::make_shared<QControlBox>(inner_dagger, n_controls_);
return std::make_shared<QControlBox>(
inner_dagger, n_controls_, control_state_);
}

Op_ptr QControlBox::transpose() const {
const Op_ptr inner_transpose = op_->transpose();
return std::make_shared<QControlBox>(inner_transpose, n_controls_);
return std::make_shared<QControlBox>(
inner_transpose, n_controls_, control_state_);
}

std::optional<Eigen::MatrixXcd> QControlBox::get_box_unitary() const {
const unsigned inner_sz = 1u << n_inner_qubits_;
const unsigned sz = inner_sz << n_controls_;
Eigen::MatrixXcd u = Eigen::MatrixXcd::Identity(sz, sz);
u.bottomRightCorner(inner_sz, inner_sz) = op_->get_unitary();
unsigned long long block_pos = bin_to_dec(control_state_) * inner_sz;
u.block(block_pos, block_pos, inner_sz, inner_sz) = op_->get_unitary();
return u;
}

bool QControlBox::is_equal(const Op &op_other) const {
const QControlBox &other = dynamic_cast<const QControlBox &>(op_other);
if (id_ == other.get_id()) return true;
return n_controls_ == other.n_controls_ && *op_ == *other.op_;
return n_controls_ == other.n_controls_ &&
control_state_ == other.control_state_ && *op_ == *other.op_;
}

ProjectorAssertionBox::ProjectorAssertionBox(
Expand Down Expand Up @@ -627,13 +650,19 @@ nlohmann::json QControlBox::to_json(const Op_ptr &op) {
const auto &box = static_cast<const QControlBox &>(*op);
nlohmann::json j = core_box_json(box);
j["n_controls"] = box.get_n_controls();
j["control_state"] = bin_to_dec(box.get_control_state());
j["op"] = box.get_op();
return j;
}

Op_ptr QControlBox::from_json(const nlohmann::json &j) {
QControlBox box =
QControlBox(j.at("op").get<Op_ptr>(), j.at("n_controls").get<unsigned>());
unsigned n_controls = j.at("n_controls").get<unsigned>();
std::vector<bool> control_state;
if (j.contains("control_state")) {
control_state =
dec_to_bin(j.at("control_state").get<unsigned>(), n_controls);
}
QControlBox box(j.at("op").get<Op_ptr>(), n_controls, control_state);
return set_box_id(
box,
boost::lexical_cast<boost::uuids::uuid>(j.at("id").get<std::string>()));
Expand Down
57 changes: 53 additions & 4 deletions tket/test/src/Circuit/test_Boxes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,49 @@ SCENARIO("QControlBox", "[boxes]") {
unit_vector_t barrier_args2{Qubit(3)};
REQUIRE(it->get_args() == barrier_args2);
}
GIVEN("Unitary2qBox controlled by state") {
Circuit c0(2);
c0.add_op<unsigned>(OpType::Rx, 0.2, {0});
c0.add_op<unsigned>(OpType::Ry, 1.2, {1});
c0.add_op<unsigned>(OpType::CX, {0, 1});
c0.add_op<unsigned>(OpType::Rz, 0.4, {1});
c0.add_op<unsigned>(OpType::H, {0});
c0.add_op<unsigned>(OpType::CX, {1, 0});
Eigen::Matrix4cd m0 = get_matrix_from_2qb_circ(c0);
Unitary2qBox ubox(m0);
Op_ptr op = std::make_shared<Unitary2qBox>(ubox);
QControlBox qcbox(op, 2, {0, 1});
std::shared_ptr<Circuit> c = qcbox.to_circuit();
const Eigen::MatrixXcd U = tket_sim::get_unitary(*c);
Eigen::MatrixXcd V = Eigen::MatrixXcd::Identity(16, 16);
for (unsigned i = 0; i < 4; i++) {
for (unsigned j = 0; j < 4; j++) {
V(4 + i, 4 + j) = m0(i, j);
}
}
REQUIRE(U.isApprox(V));
// check get_box_unitary is correct
std::optional<Eigen::MatrixXcd> box_u = qcbox.get_box_unitary();
REQUIRE(V.isApprox(box_u.value()));
}
GIVEN("Random unitary box controlled by state") {
Eigen::MatrixXcd u = random_unitary(8, 1);
Unitary3qBox ubox(u);
Op_ptr op = std::make_shared<Unitary3qBox>(ubox);
QControlBox qcbox(op, 3, {1, 0, 0});
std::shared_ptr<Circuit> c = qcbox.to_circuit();
const Eigen::MatrixXcd circ_u = tket_sim::get_unitary(*c);
Eigen::MatrixXcd V = Eigen::MatrixXcd::Identity(64, 64);
for (unsigned i = 0; i < 8; i++) {
for (unsigned j = 0; j < 8; j++) {
V(32 + i, 32 + j) = u(i, j);
}
}
REQUIRE(circ_u.isApprox(V));
// check get_box_unitary is correct
std::optional<Eigen::MatrixXcd> box_u = qcbox.get_box_unitary();
REQUIRE(V.isApprox(box_u.value()));
}
}

SCENARIO("Unitary3qBox", "[boxes]") {
Expand Down Expand Up @@ -937,20 +980,26 @@ SCENARIO("Checking equality", "[boxes]") {
Circuit u(2);
u.add_op<unsigned>(OpType::CX, {0, 1});
Op_ptr op = std::make_shared<CircBox>(CircBox(u));
QControlBox qcbox(op);
QControlBox qcbox(op, 3, {1, 0, 1});
WHEN("both arguments are equal") { REQUIRE(qcbox == qcbox); }
WHEN("different ids but equivalent ops") {
Circuit u2(2);
u2.add_op<unsigned>(OpType::CX, {0, 1});
Op_ptr op2 = std::make_shared<CircBox>(CircBox(u2));
QControlBox qcbox2(op2);
QControlBox qcbox2(op2, 3, {1, 0, 1});
REQUIRE(qcbox == qcbox2);
}
WHEN("different ids, equivalent ops, but different types") {
Op_ptr op3 = get_op_ptr(OpType::CX);
REQUIRE(qcbox != QControlBox(op3));
REQUIRE(qcbox != QControlBox(op3, 3, {1, 0, 1}));
}
WHEN("both arguments are different") {
WHEN("different control states") {
REQUIRE(qcbox != QControlBox(op, 3, {0, 0, 1}));
}
WHEN("equivalent control states") {
REQUIRE(QControlBox(op, 3) == QControlBox(op, 3, {1, 1, 1}));
}
WHEN("all arguments are different") {
Op_ptr op4 = get_op_ptr(OpType::Y);
QControlBox qcbox4(op4);
REQUIRE(qcbox != qcbox4);
Expand Down
Loading

0 comments on commit ea8b26e

Please sign in to comment.