tket
Loading...
Searching...
No Matches
SingleQubitSquash.cpp
Go to the documentation of this file.
1// Copyright Quantinuum
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
16
17#include <algorithm>
18#include <cstddef>
19#include <numeric>
20#include <utility>
21
22#include "Circuit/Command.hpp"
23#include "Gate/GatePtr.hpp"
26
27namespace tket {
28
30 std::unique_ptr<AbstractSquasher> squasher, Circuit &circ, bool reversed,
31 bool always_squash_symbols)
32 : squasher_(std::move(squasher)),
33 circ_(circ),
34 reversed_(reversed),
35 always_squash_symbols_(always_squash_symbols) {}
36
38 : squasher_(other.squasher_->clone()),
39 circ_(other.circ_),
40 reversed_(other.reversed_),
41 always_squash_symbols_(other.always_squash_symbols_) {}
43 bool success = false;
44
45 VertexVec inputs = circ_.q_inputs();
46 VertexVec outputs = circ_.q_outputs();
47 for (unsigned i = 0; i < circ_.n_qubits(); ++i) {
48 Edge in = circ_.get_nth_out_edge(inputs[i], 0);
49 Edge out = circ_.get_nth_in_edge(outputs[i], 0);
50 if (reversed_) {
51 success |= squash_between(out, in);
52 } else {
53 success |= squash_between(in, out);
54 }
55 }
56
57 return success;
58}
59
60bool SingleQubitSquash::squash_between(const Edge &in, const Edge &out) {
61 squasher_->clear();
62 Edge e = in;
63 Vertex v = next_vertex(e);
64 std::vector<Gate_ptr> single_chain;
65 VertexVec bin;
66 bool success = false;
67 Condition condition = {};
68 while (true) {
69 Op_ptr v_op = circ_.get_Op_ptr_from_Vertex(v);
70 OpType v_type = v_op->get_type();
71 bool move_to_next_vertex = false;
72 bool reset_search = false;
73 Condition this_condition = {};
74
75 if (v_type == OpType::Conditional) {
76 // => deal with conditional case
77 this_condition = get_condition(v);
78 while (v_type == OpType::Conditional) {
79 v_op = static_cast<const Conditional &>(*v_op).get_op();
80 v_type = v_op->get_type();
81 }
82
83 if (single_chain.empty()) {
84 condition = this_condition;
85 }
86 }
87
88 bool is_squashable = circ_.n_in_edges_of_type(v, EdgeType::Quantum) == 1 &&
89 is_gate_type(v_type) && squasher_->accepts(v_type);
90
91 if (e != out && condition == this_condition && is_squashable) {
92 // => add gate to current squash
93 squasher_->append(as_gate_ptr(reversed_ ? v_op->dagger() : v_op));
94 move_to_next_vertex = true;
95 } else {
96 // => squash and reset
97 reset_search = true;
98 if (single_chain.empty()) {
99 // => nothing to do, move on
100 move_to_next_vertex = true;
101 } else {
102 Circuit sub;
103 std::optional<Pauli> commutation_colour = std::nullopt;
104 if (is_gate_type(v_type) && v_op->n_qubits() > 1) {
105 commutation_colour = circ_.commuting_basis(v, next_port(e));
106 move_to_next_vertex = true;
107 }
108 auto pair = squasher_->flush(commutation_colour);
109 sub = pair.first;
110 Gate_ptr left_over_gate = pair.second;
111 if (left_over_gate != nullptr) {
112 if (commute_ok(e, condition)) {
113 // Commute leftover through before squashing.
114 insert_left_over_gate(left_over_gate, next_edge(v, e), condition);
115 } else {
116 // Don't commute, just add the left-over gate to the replacement.
117 sub.add_op<unsigned>(left_over_gate, {0});
118 }
119 left_over_gate = nullptr;
120 }
121 if (reversed_) {
122 sub = sub.dagger();
123 }
124
125 // we squash if the replacement is at least as good as the original
126 // (and it's not a no-op)
127 if (sub_is_better(sub, single_chain)) {
128 substitute(sub, bin, e, condition);
129 success = true;
130 }
131 }
132 }
133 if (e == out || is_last_optype(v_type)) {
134 squasher_->clear();
135 break;
136 }
137 if (move_to_next_vertex) {
138 if (is_gate_type(v_type)) {
139 bin.push_back(v);
140 single_chain.push_back(as_gate_ptr(v_op));
141 }
142 e = next_edge(v, e);
143 v = next_vertex(e);
144 }
145 if (reset_search) {
146 bin.clear();
147 single_chain.clear();
148 squasher_->clear();
149 condition = {};
150 }
151 }
152 return success;
153}
154
155void SingleQubitSquash::substitute(
156 const Circuit &sub, const VertexVec &single_chain, Edge &e,
157 const Condition &condition) {
158 // backup edge
159 VertPort backup = {next_vertex(e), next_port(e)};
160
161 if (!condition.empty()) {
163 sub, single_chain.front(), Circuit::VertexDeletion::No);
164 } else {
165 circ_.substitute(sub, single_chain.front(), Circuit::VertexDeletion::No);
166 }
167 circ_.remove_vertices(
168 VertexSet{single_chain.begin(), single_chain.end()},
170
171 // restore backup
172 e = prev_edge(backup);
173}
174
175bool SingleQubitSquash::path_exists(
176 const Vertex &v, const VertexSet &vs) const {
177 VertexSet frontier{v};
178 while (!frontier.empty()) {
179 if (std::any_of(vs.begin(), vs.end(), [&frontier](const Vertex &v1) {
180 return frontier.contains(v1);
181 })) {
182 return true;
183 }
184 VertexSet new_frontier;
185 for (const Vertex &v1 : frontier) {
186 if (reversed_) {
187 const VertexVec &succs = circ_.get_successors(v1);
188 new_frontier.insert(succs.begin(), succs.end());
189 } else {
190 const VertexVec &preds = circ_.get_predecessors(v1);
191 new_frontier.insert(preds.begin(), preds.end());
192 }
193 }
194 frontier = std::move(new_frontier);
195 }
196 return false;
197}
198
199bool SingleQubitSquash::commute_ok(
200 const Edge &e, const Condition &condition) const {
201 VertexSet vs;
202 for (const std::pair<std::list<VertPort>, unsigned> &cond : condition) {
203 for (const VertPort &vp : cond.first) {
204 vs.insert(vp.first);
205 }
206 }
207 if (vs.empty()) return true;
208 if (reversed_) {
209 // Return true iff there is no path in the DAG from source(e) to any vertex
210 // in vs. (Such a path would introduce a cycle after the commutation.)
211 return !path_exists(circ_.source(e), vs);
212 } else {
213 // Return true iff there is no path in the DAG from any vertex in vs to
214 // target(e). (Such a path would imply the condition is no longer live when
215 // queried.)
216 return !path_exists(circ_.target(e), vs);
217 }
218}
219
220void SingleQubitSquash::insert_left_over_gate(
221 Op_ptr left_over, const Edge &e, const Condition &condition) {
222 if (reversed_) {
223 left_over = left_over->dagger();
224 }
225 EdgeVec preds;
226 op_signature_t sigs;
227 // Conditions are ordered from outermost to innermost; iterate in reverse to
228 // build back up in the same order
229 for (auto c_rit = condition.rbegin(); c_rit != condition.rend(); ++c_rit) {
230 left_over = std::make_shared<Conditional>(
231 left_over, (unsigned)c_rit->first.size(), c_rit->second);
232 }
233 Vertex new_v = circ_.add_vertex(left_over);
234 // Arguments consider outermost conditions first, so iterate in forwards order
235 for (const std::pair<std::list<VertPort>, unsigned> &c : condition) {
236 for (const VertPort &vp : c.first) {
237 preds.push_back(circ_.get_nth_out_edge(vp.first, vp.second));
238 sigs.push_back(EdgeType::Boolean);
239 }
240 }
241 preds.push_back(e);
242 sigs.push_back(EdgeType::Quantum);
243 circ_.rewire(new_v, preds, sigs);
244}
245
246bool SingleQubitSquash::sub_is_better(
247 const Circuit &sub, const std::vector<Gate_ptr> chain) const {
248 const unsigned n_gates = sub.n_gates();
249 if (n_gates > chain.size()) {
250 return false;
251 }
252 if (!sub.is_symbolic() || always_squash_symbols_) {
253 return n_gates < chain.size() || !is_equal(sub, chain, reversed_);
254 }
255 // For symbolic circuits, we don't want to squash gates if it blows up the
256 // complexity of the expressions. As a crude but adequate measure, we compare
257 // the total size of the string representations.
258 return std::accumulate(
259 sub.begin(), sub.end(), std::size_t{0},
260 [](std::size_t a, const Command &cmd) {
261 return a + cmd.get_op_ptr()->get_name().size();
262 }) <
263 std::accumulate(
264 chain.begin(), chain.end(), std::size_t{0},
265 [](std::size_t a, const Gate_ptr &gpr) {
266 return a + gpr->get_name().size();
267 });
268}
269
270// returns a description of the condition of current vertex
271SingleQubitSquash::Condition SingleQubitSquash::get_condition(Vertex v) const {
272 Op_ptr v_op = circ_.get_Op_ptr_from_Vertex(v);
273 OpType v_type = v_op->get_type();
274 if (v_type != OpType::Conditional) {
275 throw BadOpType("Cannot get condition from non-conditional OpType", v_type);
276 }
277 Condition conds = {};
278 unsigned port_offset = 0;
279 EdgeVec ins = circ_.get_in_edges(v);
280 while (v_type == OpType::Conditional) {
281 const Conditional &cond_op = static_cast<const Conditional &>(*v_op);
282 std::list<VertPort> bool_ports;
283 for (port_t p = 0; p < cond_op.get_width(); ++p) {
284 Edge in_p = ins.at(port_offset + p);
285 VertPort vp = {circ_.source(in_p), circ_.get_source_port(in_p)};
286 bool_ports.push_back(vp);
287 }
288 conds.push_back({bool_ports, cond_op.get_value()});
289 v_op = cond_op.get_op();
290 v_type = v_op->get_type();
291 }
292 return conds;
293}
294
295// simple utils respecting reversed boolean
296Vertex SingleQubitSquash::next_vertex(const Edge &e) const {
297 return reversed_ ? circ_.source(e) : circ_.target(e);
298}
299
300port_t SingleQubitSquash::next_port(const Edge &e) const {
301 return reversed_ ? circ_.get_source_port(e) : circ_.get_target_port(e);
302}
303
304Edge SingleQubitSquash::prev_edge(const VertPort &pair) const {
305 return reversed_ ? circ_.get_nth_out_edge(pair.first, pair.second)
306 : circ_.get_nth_in_edge(pair.first, pair.second);
307}
308
309Edge SingleQubitSquash::next_edge(const Vertex &v, const Edge &e) const {
310 return reversed_ ? circ_.get_last_edge(v, e) : circ_.get_next_edge(v, e);
311}
312
313bool SingleQubitSquash::is_last_optype(OpType type) const {
314 return (reversed_ && is_initial_q_type(type)) ||
315 (!reversed_ && is_final_q_type(type));
316}
317
318bool SingleQubitSquash::is_equal(
319 const Circuit &circ, const std::vector<Gate_ptr> &gates, bool reversed) {
320 if (reversed) {
321 return is_equal(circ, {gates.rbegin(), gates.rend()});
322 }
323 if (circ.n_qubits() != 1) {
324 throw CircuitInvalidity("Only circuits with one qubit are supported");
325 }
326
327 auto it1 = circ.begin();
328 auto it2 = gates.cbegin();
329
330 while (it1 != circ.end() && it2 != gates.end()) {
331 const Gate_ptr op1 = as_gate_ptr(it1->get_op_ptr());
332 const Gate_ptr op2 = as_gate_ptr(*it2);
333 if (!(*op1 == *op2)) {
334 return false;
335 }
336 ++it1;
337 ++it2;
338 }
339
340 if (it1 != circ.end() || it2 != gates.cend()) {
341 return false;
342 }
343 return true;
344}
345
346} // namespace tket
A circuit.
Definition Circuit.hpp:212
Edge get_nth_in_edge(const Vertex &vert_to, const port_t &n) const
Get the edge targeting the nth input port at vert_to.
VertexVec get_predecessors(const Vertex &vert) const
unsigned n_qubits() const
const Op_ptr get_Op_ptr_from_Vertex(const Vertex &vert) const
Edge get_next_edge(const Vertex &vert, const Edge &in_edge) const
Given a Quantum or Classical in-edge to vert, returns the corresponding out-edge, tracing the resourc...
void substitute_conditional(Circuit to_insert, const Vertex &to_replace, VertexDeletion vertex_deletion=VertexDeletion::Yes, OpGroupTransfer opgroup_transfer=OpGroupTransfer::Disallow)
Replace a conditional vertex with a new circuit.
VertexVec q_outputs() const
port_t get_target_port(const Edge &edge) const
std::optional< Pauli > commuting_basis(const Vertex &vert, port_t port) const
Which Pauli, if any, commutes with the operation at a given vertex and port.
void substitute(const Circuit &to_insert, const Subcircuit &to_replace, VertexDeletion vertex_deletion=VertexDeletion::Yes, OpGroupTransfer opgroup_transfer=OpGroupTransfer::Disallow)
Replace a subcircuit with a new circuit.
void remove_vertices(const VertexSet &surplus, GraphRewiring graph_rewiring, VertexDeletion vertex_deletion)
VertexVec q_inputs() const
Vertex source(const Edge &e) const
Definition Circuit.hpp:569
Edge get_nth_out_edge(const Vertex &vert_from, const port_t &n) const
Get the edge originated from the nth output port at vert_from.
Vertex add_op(const Op_ptr &op, const std::vector< ID > &args, std::optional< std::string > opgroup=std::nullopt)
Append an operation to the circuit.
Definition Circuit.hpp:1701
unsigned n_in_edges_of_type(const Vertex &vert, EdgeType et) const
Number of inward edges of a specific type.
Edge get_last_edge(const Vertex &vert, const Edge &out_edge) const
void rewire(const Vertex &new_vert, const EdgeVec &preds, const op_signature_t &types)
Rewires linear resources (Quantum or Classical) in place of pred Adds new edges for Boolean O(n alpha...
port_t get_source_port(const Edge &edge) const
VertexVec get_successors(const Vertex &vert) const
EdgeVec get_in_edges(const Vertex &vert) const
All inward edges, ordered by port number Every port has a single in-edge and they are numbered contig...
Vertex add_vertex(const Op_ptr op_ptr, std::optional< std::string > opgroup=std::nullopt)
Add a vertex to the DAG.
Circuit dagger() const
Vertex target(const Edge &e) const
Definition Circuit.hpp:567
Decorates another op, adding a QASM-style classical condition.
Op_ptr get_op() const
Squashes single qubit gates using given Squasher.
bool squash_between(const Edge &in, const Edge &out)
Squash everything between in-edge and out-edge.
bool squash()
Squash entire circuit, one qubit at a time.
SingleQubitSquash(std::unique_ptr< AbstractSquasher > squasher, Circuit &circ, bool reversed=false, bool always_squash_symbols=false)
Construct a new Single Qubit Squash object.
STL namespace.
Defines tket::DeviceCharacterisation, used in NoiseAwarePlacement and in commute_SQ_gates_through_SWA...
Definition Path.cpp:22
boost::graph_traits< DAG >::vertex_descriptor Vertex
Definition DAGDefs.hpp:70
Gate_ptr as_gate_ptr(Op_ptr op)
Cast a general Op (of gate type) to a Gate.
Definition GatePtr.cpp:25
OpType
Named operation types.
Definition OpType.hpp:29
@ Conditional
See Conditional.
std::shared_ptr< const Gate > Gate_ptr
Definition GatePtr.hpp:24
std::unordered_set< Vertex > VertexSet
Definition DAGDefs.hpp:72
bool is_initial_q_type(OpType optype)
Test for input or creation quantum "ops".
@ Quantum
A wire carrying quantum information, corresponding to some allocated Qubit.
@ Boolean
A wire carrying a bit of classical information from a classical output port of one op to a classical ...
bool is_gate_type(OpType optype)
Test for elementary gates.
std::pair< Vertex, port_t > VertPort
Definition DAGDefs.hpp:96
boost::graph_traits< DAG >::edge_descriptor Edge
Definition DAGDefs.hpp:88
std::shared_ptr< const Op > Op_ptr
Definition OpPtr.hpp:24
std::vector< Edge > EdgeVec
Definition DAGDefs.hpp:93
unsigned port_t
A specific entry or exit port of a vertex.
Definition Op.hpp:48
std::vector< EdgeType > op_signature_t
Definition EdgeType.hpp:61
bool is_final_q_type(OpType optype)
Test for output or discard quantum "ops".
std::vector< Vertex > VertexVec
Definition DAGDefs.hpp:73