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

update remove blank wires to keep classical bits #1435

Merged
merged 20 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion pytket/binders/circuit/Circuit/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,9 @@ void def_circuit(py::class_<Circuit, std::shared_ptr<Circuit>> &pyCircuit) {
"from the circuit. This may occur when optimisations "
"recognise that the operations on a qubit reduce to the "
"identity, or when routing adds wires to \"fill out\" the "
"architecture.")
"architecture.\n\n:param remove_classical_wire: select if "
"empty classical wires should be removed, too",
cqc-melf marked this conversation as resolved.
Show resolved Hide resolved
py::arg("remove_classical_wire"))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest having a keep_blank_classical_wires argument that defaults to false.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 3b4bb6f

.def(
"add_blank_wires", &Circuit::add_blank_wires,
"Adds a number of new qubits to the circuit. These will be "
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.3.2@tket/stable")
self.requires("tket/1.3.3@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
2 changes: 2 additions & 0 deletions pytket/docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ Features:
* Add ``OpType.CnRx`` and ``OpType.CnRz``.
* Add ``AutoRebase`` and ``AutoSquash`` passes.
Deprecate ``auto_rebase_pass`` and ``auto_squash_pass``.
* Add new parameter added to `remove_blank_wires` to allow to keep empty classical bits
cqc-melf marked this conversation as resolved.
Show resolved Hide resolved

Fixes:

* Allow barriers when dagger or transpose a circuit.
* Keep blank classical wires when running `FlattenRelabelRegistersPass`


1.28.0 (May 2024)
Expand Down
4 changes: 3 additions & 1 deletion pytket/pytket/_tket/circuit.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -2263,9 +2263,11 @@ class Circuit:
"""
Query whether a qubit has its final state discarded
"""
def remove_blank_wires(self) -> None:
def remove_blank_wires(self, remove_classical_wire: bool) -> None:
"""
Removes any Input-Output pairs in the DAG with no intervening operations, i.e. removes untouched qubits/bits from the circuit. This may occur when optimisations recognise that the operations on a qubit reduce to the identity, or when routing adds wires to "fill out" the architecture.

:param remove_classical_wire: select if empty classical wires should be removed, too
"""
def rename_units(self, map: dict[pytket._tket.unit_id.UnitID | pytket._tket.unit_id.Qubit | pytket._tket.unit_id.Bit, pytket._tket.unit_id.UnitID | pytket._tket.unit_id.Qubit | pytket._tket.unit_id.Bit]) -> bool:
"""
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.3.2"
version = "1.3.3"
package_type = "library"
license = "Apache 2"
homepage = "https://github.com/CQCL/tket"
Expand Down
9 changes: 7 additions & 2 deletions tket/include/tket/Circuit/Circuit.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -762,8 +762,13 @@ class Circuit {
// Basic Circuit Manipulation//
//////////////////////////////

// O(V), `V` the number of vertices
void remove_blank_wires();
/**
* O(V), `V` the number of vertices
* removes all blank wires
* @param remove_classical_wire option to choose if empty classical wire
* should be removed as well
*/
void remove_blank_wires(bool remove_classical_wire = true);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be more intuitive to have a keep_blank_classical_wires argument that defaults to false.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 3b4bb6f


/**
* Remove all gates in the circuits that are identities
Expand Down
22 changes: 12 additions & 10 deletions tket/src/Circuit/basic_circ_manip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,22 @@ namespace tket {
// if there are any blank wires in the circuit,
// this method removes them and removes the vertices
// from boundaries
void Circuit::remove_blank_wires() {
void Circuit::remove_blank_wires(bool remove_classical_wire) {
VertexList bin;
unit_vector_t unused_units;
const Op_ptr noop = get_op_ptr(OpType::noop);
for (const BoundaryElement& el : boundary.get<TagID>()) {
Vertex in = el.in_;
Vertex out = el.out_;
VertexVec succs = get_successors(in);
if (succs.size() == 1 && succs.front() == out) {
dag[in].op = noop;
bin.push_back(in);
dag[out].op = noop;
bin.push_back(out);
unused_units.push_back(el.id_);
if (remove_classical_wire || el.type() == UnitType::Qubit) {
Vertex in = el.in_;
Vertex out = el.out_;
VertexVec succs = get_successors(in);
if (succs.size() == 1 && succs.front() == out) {
dag[in].op = noop;
bin.push_back(in);
dag[out].op = noop;
bin.push_back(out);
unused_units.push_back(el.id_);
}
}
}
for (const UnitID& u : unused_units) {
Expand Down
2 changes: 1 addition & 1 deletion tket/src/Predicates/PassGenerators.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ PassPtr gen_flatten_relabel_registers_pass(const std::string& label) {
Transform t =
Transform([=](Circuit& circuit, std::shared_ptr<unit_bimaps_t> maps) {
unsigned n_qubits = circuit.n_qubits();
circuit.remove_blank_wires();
circuit.remove_blank_wires(false);
bool changed = circuit.n_qubits() < n_qubits;
std::map<Qubit, Qubit> relabelling_map;
std::vector<Qubit> all_qubits = circuit.all_qubits();
Expand Down
23 changes: 23 additions & 0 deletions tket/test/src/Circuit/test_Circ.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1020,6 +1020,29 @@ SCENARIO(
test.assert_valid();
}

SCENARIO(
"Test a circuit with blank wires can have the blank wires removed keeping "
"classical",
"[blank_wires]") {
Circuit test(4, 2);
test.add_op<unsigned>(OpType::CX, {0, 1});
test.add_op<unsigned>(OpType::Z, {0});

WHEN("Check Commands work correctly") {
std::vector<Command> coms = test.get_commands();
REQUIRE(*coms[0].get_op_ptr() == *get_op_ptr(OpType::CX));
REQUIRE(*coms[1].get_op_ptr() == *get_op_ptr(OpType::Z));
}

test.add_blank_wires(8);
int n = test.n_vertices();
test.remove_blank_wires(false);
int m = test.n_vertices();
REQUIRE(n == 30);
REQUIRE(m == 10);
test.assert_valid();
}

SCENARIO(
"Test that the copy constructor and copy assignment operator work "
"correctly",
Expand Down
Loading