Skip to content

Commit

Permalink
BarrierOp and Circuit._add_conditional_barrier (#988)
Browse files Browse the repository at this point in the history
* Add new `BarrierOp`

Remove `OpType::Barrier` from `MetaOp`

* Add Circuit::add_conditional_gate

unsigned and UnitID constructors

* Add tests to confirm DAG is wired suitably for conditional barrier gates

* Add tests and case handling for conditional barrier over different circumstances

* add `_add_conditonal_barrier` method to Circuit class

* bump

* black format

* Update test_Circ.cpp

* Update add_op.cpp

* bump

* Update add_op.cpp

* Expose BarrierOp to python

* Make `barr` signature accurately reflect internal UnitID, add test

* reformat tests

* Update main.cpp

* Update qasm.py

* Update circuit.pyi

* Update test_Circ.cpp

* requested changes from review

* fix types

* Update basic_circ_manip.cpp

* Update basic_circ_manip.cpp

* Update qasm_test.py

* Update qasm_test.py

* Update circuit.pyi

* Add symbolic substituion test for barrier op

* Update  serialization/deserialization for MetaOp/BarrierOp

* add MetaOp::is_equal

* Add back data type

* Update add_op.cpp

* bump

* bump

* bump

* update with requested changes
  • Loading branch information
sjdilkes authored Sep 6, 2023
1 parent ea4ffbf commit 99debd3
Show file tree
Hide file tree
Showing 43 changed files with 578 additions and 109 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 @@ -173,6 +173,28 @@ void init_circuit_add_op(py::class_<Circuit, std::shared_ptr<Circuit>> &c) {
"\n\n:param data: additional data stored in the barrier"
"\n:return: the new :py:class:`Circuit`",
py::arg("qubits"), py::arg("bits") = no_bits, py::arg("data") = "")
.def(
"add_conditional_barrier",
[](Circuit *circ, const std::vector<unsigned> &barrier_qubits,
const std::vector<unsigned> &barrier_bits,
const std::vector<unsigned> &condition_bits, unsigned value,
const std::string &_data) {
circ->add_conditional_barrier(
barrier_qubits, barrier_bits, condition_bits, value, _data);
return circ;
},
"Append a Conditional Barrier on the given barrier qubits and "
"barrier bits, conditioned on the given condition bits."
"\n\n:param barrier_qubits: Qubit in Barrier operation."
"\n:param barrier_bits: Bit in Barrier operation."
"\n:param condition_bits: Bit covering classical control condition "
"of barrier operation."
"\n:param value: Value that classical condition must have to "
"hold (little-endian)."
"\n:param data: Additional data stored in Barrier operation."
"\n:return: the new :py:class:`Circuit`",
py::arg("barrier_qubits"), py::arg("barrier_bits"),
py::arg("condition_bits"), py::arg("value"), py::arg("data") = "")
.def(
"add_circbox",
[](Circuit *circ, const CircBox &box,
Expand Down Expand Up @@ -415,6 +437,26 @@ void init_circuit_add_op(py::class_<Circuit, std::shared_ptr<Circuit>> &c) {
"\n\n:param data: additional data stored in the barrier"
"\n:return: the new :py:class:`Circuit`",
py::arg("units"), py::arg("data") = "")
.def(
"add_conditional_barrier",
[](Circuit *circ, const unit_vector_t &barrier_args,
const bit_vector_t &condition_bits, unsigned value,
const std::string &_data) {
circ->add_conditional_barrier(
barrier_args, condition_bits, value, _data);
return circ;
},
"Append a Conditional Barrier on the given barrier qubits and "
"barrier bits, conditioned on the given condition bits."
"\n\n:param barrier_args: Qubit and Bit in Barrier operation."
"\n:param condition_bits: Bit covering classical control "
" condition of barrier operation."
"\n:param value: Value that classical condition must have to "
"hold (little-endian)."
"\n:param data: Additional data stored in Barrier operation."
"\n:return: the new :py:class:`Circuit`",
py::arg("barrier_args"), py::arg("condition_bits"), py::arg("value"),
py::arg("data") = "")
.def(
"add_circbox",
[](Circuit *circ, const CircBox &box, const unit_vector_t &args,
Expand Down
14 changes: 13 additions & 1 deletion pytket/binders/circuit/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "tket/Gate/Gate.hpp"
#include "tket/Gate/OpPtrFunctions.hpp"
#include "tket/Gate/SymTable.hpp"
#include "tket/Ops/BarrierOp.hpp"
#include "tket/Ops/MetaOp.hpp"
#include "tket/Ops/Op.hpp"
#include "tket/Utils/Constants.hpp"
Expand Down Expand Up @@ -615,7 +616,7 @@ PYBIND11_MODULE(circuit, m) {
":return: set of symbolic parameters for the command");

py::class_<MetaOp, std::shared_ptr<MetaOp>, Op>(
m, "MetaOp", "Meta operation, for example used as barrier")
m, "MetaOp", "Meta operation, such as input or output vertices.")
.def(
py::init<OpType, op_signature_t, const std::string &>(),
"Construct MetaOp with optype, signature and additional data string"
Expand All @@ -625,6 +626,17 @@ PYBIND11_MODULE(circuit, m) {
py::arg("type"), py::arg("signature"), py::arg("data"))
.def_property_readonly("data", &MetaOp::get_data, "Get data from MetaOp");

py::class_<BarrierOp, std::shared_ptr<BarrierOp>, Op>(
m, "BarrierOp", "Barrier operations.")
.def(
py::init<op_signature_t, const std::string &>(),
"Construct BarrierOp with signature and additional data string"
"\n:param signature: signature for the op"
"\n:param data: additional string stored in the op",
py::arg("signature"), py::arg("data"))
.def_property_readonly(
"data", &BarrierOp::get_data, "Get data from BarrierOp");

auto pyCircuit = py::class_<Circuit, std::shared_ptr<Circuit>>(
m, "Circuit", py::dynamic_attr(),
"Encapsulates a quantum circuit using a DAG representation.\n\n>>> "
Expand Down
7 changes: 5 additions & 2 deletions pytket/binders/include/add_gate.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@ static Circuit *add_gate_method(
Circuit *circ, const Op_ptr &op, const std::vector<ID> &args,
const py::kwargs &kwargs) {
if (op->get_desc().is_meta()) {
throw CircuitInvalidity("Cannot add metaop to a circuit.");
}
if (op->get_desc().is_barrier()) {
throw CircuitInvalidity(
"Cannot add metaop. Please use `add_barrier` to add a "
"barrier.");
"Please use `add_barrier` to add a "
"barrier to a circuit.");
}
static const std::set<std::string> allowed_kwargs = {
"opgroup", "condition", "condition_bits", "condition_value"};
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.39@tket/stable")
self.requires("tket/1.2.40@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
3 changes: 2 additions & 1 deletion pytket/docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ Unreleased

Minor new features:

* Add ``apply_clifford_basis_change_tensor``.
* ``Circuit.add_conditional_barrier``
* Add ``apply_clifford_basis_change_tensor`` method

Fixes:

Expand Down
9 changes: 9 additions & 0 deletions pytket/pytket/_tket/circuit.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import pytket._tket.pauli
import pytket._tket.unit_id
import sympy

class BarrierOp(Op):
def __init__(self, signature: List[EdgeType], data: str) -> None: ...
@property
def data(self) -> str: ...

class BasisOrder:
__members__: ClassVar[dict] = ... # read-only
__entries: ClassVar[dict] = ...
Expand Down Expand Up @@ -348,6 +353,10 @@ class Circuit:
def add_classicalexpbox_bit(self, expression: object, target: List[pytket._tket.unit_id.Bit], **kwargs: Any) -> Circuit: ...
def add_classicalexpbox_register(self, expression: object, target: List[pytket._tket.unit_id.Bit], **kwargs: Any) -> Circuit: ...
@overload
def add_conditional_barrier(self, barrier_qubits: List[int], barrier_bits: List[int], condition_bits: List[int], value: int, data: str = ...) -> Circuit: ...
@overload
def add_conditional_barrier(self, barrier_args: List[pytket._tket.unit_id.UnitID], condition_bits: List[pytket._tket.unit_id.Bit], value: int, data: str = ...) -> Circuit: ...
@overload
def add_conjugation_box(self, box: ConjugationBox, args: List[pytket._tket.unit_id.UnitID], **kwargs: Any) -> Circuit: ...
@overload
def add_conjugation_box(self, box: ConjugationBox, args: List[int], **kwargs: Any) -> Circuit: ...
Expand Down
2 changes: 0 additions & 2 deletions pytket/pytket/backends/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ def valid_circuit(self, circuit: Circuit) -> bool:
def _check_all_circuits(
self, circuits: Iterable[Circuit], nomeasure_warn: Optional[bool] = None
) -> bool:

if nomeasure_warn is None:
nomeasure_warn = not (
self._supports_state
Expand Down Expand Up @@ -267,7 +266,6 @@ def process_circuits(
valid_check: bool = True,
**kwargs: KwargTypes,
) -> List[ResultHandle]:

"""
Submit circuits to the backend for running. The results will be stored
in the backend's result cache to be retrieved by the corresponding
Expand Down
2 changes: 1 addition & 1 deletion pytket/pytket/circuit/decompose_classical.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ def _decompose_expressions(circ: Circuit) -> Tuple[Circuit, bool]:
modified = True
continue
if optype == OpType.Barrier:
# add_gate doesn't work for metaops like barrier
# add_gate doesn't work for metaops
newcirc.add_barrier(args)
else:
newcirc.add_gate(op, args, **kwargs)
Expand Down
17 changes: 13 additions & 4 deletions pytket/pytket/qasm/qasm.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
MultiBitOp,
WASMOp,
CustomGate,
MetaOp,
BarrierOp,
)
from pytket._tket.unit_id import _TEMP_BIT_NAME
from pytket.circuit import (
Expand Down Expand Up @@ -362,9 +362,19 @@ def meas(self, tree: List[Token]) -> Iterable[CommandDict]:

def barr(self, tree: List[Arg]) -> Iterable[CommandDict]:
args = [q for qs in self.unroll_all_args(tree[0]) for q in qs]
signature: List[str] = []
for arg in args:
if arg[0] in self.c_registers:
signature.append("C")
elif arg[0] in self.q_registers:
signature.append("Q")
else:
raise QASMParseError(
"UnitID " + str(arg) + " in Barrier arguments is not declared."
)
yield {
"args": args,
"op": {"signature": ["Q"] * len(args), "type": "Barrier"},
"op": {"signature": signature, "type": "Barrier"},
}

def reset(self, tree: List[Token]) -> Iterable[CommandDict]:
Expand All @@ -379,7 +389,6 @@ def mixedcall(self, tree: List) -> Iterator[CommandDict]:

optoken = next(child_iter)
opstr = optoken.value

next_tree = next(child_iter)
try:
args = next(child_iter)
Expand Down Expand Up @@ -1321,7 +1330,7 @@ def circuit_to_qasm_io(
param = -2 + param
params = [param] # type: ignore
elif optype == OpType.Barrier and header == "hqslib1_dev":
assert isinstance(op, MetaOp)
assert isinstance(op, BarrierOp)
if op.data == "":
opstr = _tk_to_qasm_noparams[optype]
else:
Expand Down
1 change: 0 additions & 1 deletion pytket/tests/classical_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,6 @@ def qubit_register(
index=strategies.integers(min_value=0, max_value=32),
)
def test_registers(reg: Union[BitRegister, QubitRegister], index: int) -> None:

unit_type = Qubit if type(reg) is QubitRegister else Bit
if index < reg.size:
assert reg[index] == unit_type(reg.name, index)
Expand Down
18 changes: 17 additions & 1 deletion pytket/tests/qasm_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,13 +216,29 @@ def test_conditional_gates() -> None:
circ.Measure(0, 0)
circ.Measure(1, 1)
circ.Z(0, condition_bits=[0, 1], condition_value=2)
circ.add_conditional_barrier([0, 1], [], [0, 1], 1)
circ.Measure(0, 0, condition_bits=[0, 1], condition_value=1)
qasm_out = str(curr_file_path / "qasm_test_files/testout5.qasm")
circuit_to_qasm(circ, qasm_out)
c2 = circuit_from_qasm(qasm_out)
assert circ == c2


def test_named_conditional_barrier() -> None:
circ = Circuit(2, 2)
circ.add_bit(Bit("test", 3))
circ.Z(0, condition_bits=[0, 1], condition_value=2)
circ.add_conditional_barrier(
[Qubit("q", 0), Bit("test", 3)],
[Bit("c", 0), Bit("c", 1)],
0,
data="cond_barrier",
)
qs_str: str = circuit_to_qasm_str(circ)
c_from_qs: Circuit = circuit_from_qasm_str(qs_str)
assert qs_str == circuit_to_qasm_str(c_from_qs)


def test_hqs_conditional() -> None:
c = Circuit(1)
a = c.add_c_register("a", 8)
Expand Down Expand Up @@ -273,7 +289,6 @@ def test_barrier() -> None:
c.H(0)
c.H(2)
c.add_barrier([0], [0], "comment")

result = """OPENQASM 2.0;\ninclude "hqslib1_dev.inc";\n\nqreg q[3];
creg c[3];\nh q[0];\nh q[2];\ncomment q[0],c[0];\n"""
assert result == circuit_to_qasm_str(c, header="hqslib1_dev")
Expand Down Expand Up @@ -773,6 +788,7 @@ def test_rxxyyzz_conversion() -> None:
test_extended_qasm()
test_register_commands()
test_conditional_gates()
test_named_conditional_barrier()
test_hqs_conditional()
test_hqs_conditional_params()
test_barrier()
Expand Down
2 changes: 2 additions & 0 deletions tket/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ target_sources(tket
src/Clifford/ChoiMixTableau.cpp
src/Clifford/SymplecticTableau.cpp
src/Clifford/UnitaryTableau.cpp
src/Ops/BarrierOp.cpp
src/Ops/FlowOp.cpp
src/Ops/MetaOp.cpp
src/Ops/Op.cpp
Expand Down Expand Up @@ -278,6 +279,7 @@ target_sources(tket
include/tket/Clifford/SymplecticTableau.hpp
include/tket/Clifford/UnitaryTableau.hpp
include/tket/Ops/ClassicalOps.hpp
include/tket/Ops/BarrierOp.hpp
include/tket/Ops/FlowOp.hpp
include/tket/Ops/MetaOp.hpp
include/tket/Ops/Op.hpp
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.39"
version = "1.2.40"
package_type = "library"
license = "Apache 2"
homepage = "https://github.com/CQCL/tket"
Expand Down
21 changes: 19 additions & 2 deletions tket/include/tket/Circuit/Circuit.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -836,9 +836,9 @@ class Circuit {
Vertex add_op(
OpType type, const std::vector<Expr> &params, const std::vector<ID> &args,
std::optional<std::string> opgroup = std::nullopt) {
if (is_metaop_type(type)) {
if (is_metaop_type(type) || is_barrier_type(type)) {
throw CircuitInvalidity(
"Cannot add metaop. Please use `add_barrier` to add a "
"Cannot add metaop or barrier. Please use `add_barrier` to add a "
"barrier.");
}
return add_op(get_op_ptr(type, params, args.size()), args, opgroup);
Expand Down Expand Up @@ -904,6 +904,11 @@ class Circuit {
if (is_metaop_type(type)) {
throw CircuitInvalidity("Cannot add a conditional metaop.");
}
if (is_barrier_type(type)) {
throw CircuitInvalidity(
"Please use 'add_conditional_barrier' to add a conditional barrier "
"gate.");
}
Op_ptr cond = std::make_shared<Conditional>(
get_op_ptr(type, params, (unsigned)args.size()), (unsigned)bits.size(),
value);
Expand All @@ -918,6 +923,18 @@ class Circuit {

Vertex add_barrier(const unit_vector_t &args, const std::string &_data = "");

Vertex add_conditional_barrier(
const std::vector<unsigned> &barrier_qubits,
const std::vector<unsigned> &barrier_bits,
const std::vector<unsigned> &condition_bits, unsigned value,
const std::string &_data,
std::optional<std::string> opgroup = std::nullopt);

Vertex add_conditional_barrier(
const unit_vector_t &barrier_args, const bit_vector_t &condition_bits,
unsigned value, const std::string &_data,
std::optional<std::string> opgroup = std::nullopt);

/**
* Add a postfix to a classical register name if the register exists
* Example: tket_c results in tket_c_2 if tket_c and tket_c_1 both exist
Expand Down
8 changes: 4 additions & 4 deletions tket/include/tket/Gate/OpPtrFunctions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ namespace tket {
*
* @param chosen_type operation type
* @param param operation parameter
* @param n_qubits number of qubits (only necessary for gates and metaops
* with variable quantum arity)
* @param n_qubits number of qubits (only necessary for gates, barrier
* and metaops with variable quantum arity)
*/
Op_ptr get_op_ptr(OpType chosen_type, const Expr &param, unsigned n_qubits = 0);

Expand All @@ -35,8 +35,8 @@ Op_ptr get_op_ptr(OpType chosen_type, const Expr &param, unsigned n_qubits = 0);
*
* @param chosen_type operation type
* @param params operation parameters
* @param n_qubits number of qubits (only necessary for gates and metaops
* with variable quantum arity)
* @param n_qubits number of qubits (only necessary for gates, barrier
* and metaops with variable quantum arity)
*/
Op_ptr get_op_ptr(
OpType chosen_type, const std::vector<Expr> &params = {},
Expand Down
6 changes: 5 additions & 1 deletion tket/include/tket/OpType/OpDesc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,12 @@ class OpDesc {
/** Number of classical bits written to */
OptUInt n_classical() const;

/** Whether the 'operation' is actually an input or output or barrier */
/** Whether the 'operation' is actually an input or output */
bool is_meta() const;

/** Whether the operation is a Barrier */
bool is_barrier() const;

/** Whether the operation is a box of some kind */
bool is_box() const;

Expand Down Expand Up @@ -111,6 +114,7 @@ class OpDesc {
const OpType type_;
const OpTypeInfo info_;
const bool is_meta_;
const bool is_barrier_;
const bool is_box_;
const bool is_gate_;
const bool is_flowop_;
Expand Down
5 changes: 4 additions & 1 deletion tket/include/tket/OpType/OpTypeFunctions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,12 @@ const OpTypeSet &all_controlled_gate_types();
/** Set of all classical gates */
const OpTypeSet &all_classical_types();

/** Test for initial, final and barrier "ops" */
/** Test for initial and final "ops" */
bool is_metaop_type(OpType optype);

/** Test for Barrier "ops" */
bool is_barrier_type(OpType optype);

/** Test for input or creation quantum "ops" */
bool is_initial_q_type(OpType optype);

Expand Down
Loading

0 comments on commit 99debd3

Please sign in to comment.