Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/add control_state argument to QControlBox #977

Merged
merged 16 commits into from
Aug 22, 2023
Merged
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 = {});
cqc-alec marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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});
cqc-alec marked this conversation as resolved.
Show resolved Hide resolved
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
Loading