tket
Loading...
Searching...
No Matches
Multiplexor.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 <complex>
18
21#include "tket/Gate/GatePtr.hpp"
27
28namespace tket {
29
30// to limit the time to decompose MultiplexedRotationBox and MultiplexedU2Box.
31// can be relaxed in the future
32static const unsigned MAX_N_CONTROLS = 32;
33
42static Circuit multiplexor_sequential_decomp(
43 const ctrl_op_map_t &op_map, unsigned n_controls, unsigned n_targets) {
44 Circuit c(n_controls + n_targets);
45 std::vector<unsigned> qubits(n_controls + n_targets);
46 std::iota(std::begin(qubits), std::end(qubits), 0);
47 for (auto it = op_map.begin(); it != op_map.end(); it++) {
48 std::vector<unsigned> zero_ctrls;
49 for (unsigned i = 0; i < it->first.size(); i++) {
50 if (!it->first[i]) {
51 zero_ctrls.push_back(i);
52 c.add_op<unsigned>(OpType::X, {i});
53 }
54 }
55 QControlBox qcbox(it->second, n_controls);
56 c.add_box(qcbox, qubits);
57 for (unsigned i : zero_ctrls) {
58 c.add_op<unsigned>(OpType::X, {i});
59 }
60 }
61 return c;
62}
63
69enum class RecursionNodeType { Left = 0, Right = 1, Root = 2 };
70
100static void recursive_demultiplex_rotation(
101 const std::vector<Expr> &angles, const OpType &axis, unsigned total_qubits,
102 std::vector<GateSpec> &commands, const RecursionNodeType &node_type) {
103 unsigned n_rotations = angles.size();
104 unsigned n_qubits = (unsigned)log2(n_rotations) + 1;
105 unsigned mid = (unsigned)(n_rotations / 2);
106 std::vector<Expr> p_angles;
107 std::vector<Expr> q_angles;
108 for (unsigned i = 0; i < mid; i++) {
109 p_angles.push_back((angles[i] - angles[mid + i]) / 2);
110 q_angles.push_back((angles[i] + angles[mid + i]) / 2);
111 }
112 // UCR = CX P CX Q = Q CX P CX
113 // the left recursion child implements P CX Q, and the
114 // right recursion child implements Q CX P to cancel the CXs
115 if (node_type == RecursionNodeType::Right) {
116 std::swap(p_angles, q_angles);
117 }
118 if (q_angles.size() == 1) {
119 // base step
120 commands.push_back(GateSpec(axis, q_angles[0]));
121 } else {
122 recursive_demultiplex_rotation(
123 q_angles, axis, total_qubits, commands, RecursionNodeType::Left);
124 }
125 commands.push_back(GateSpec(OpType::CX, total_qubits - n_qubits));
126 if (p_angles.size() == 1) {
127 // base step
128 commands.push_back(GateSpec(axis, p_angles[0]));
129 } else {
130 recursive_demultiplex_rotation(
131 p_angles, axis, total_qubits, commands, RecursionNodeType::Right);
132 }
133 if (node_type == RecursionNodeType::Root) {
134 // for the root step, we implement UCR = CX P CX Q
135 commands.push_back(GateSpec(OpType::CX, total_qubits - n_qubits));
136 }
137}
138
149static std::tuple<Eigen::Matrix2cd, Eigen::Matrix2cd, double, double>
150constant_demultiplex(const Eigen::Matrix2cd &a, const Eigen::Matrix2cd &b) {
151 Eigen::Matrix2cd X = a * b.adjoint();
152 // decompose X using eq(19)
153 std::vector<double> tk1_params = tk1_angles_from_unitary(X);
154 Eigen::Matrix2cd tk1_su2 = get_matrix_from_tk1_angles(
155 {tk1_params[0], tk1_params[1], tk1_params[2], 0.0});
156 Complex x0 = tk1_su2(0, 0);
157 double phi = tk1_params[3] * PI * 2;
158 // compute r matrix using eq(11) and eq(12)
159 double a0 = -PI / 2 - phi / 2 - std::arg(x0);
160 double a1 = PI / 2 - phi / 2 + std::arg(x0);
161 Complex r0 = std::exp(0.5 * i_ * a0);
162 Complex r1 = std::exp(0.5 * i_ * a1);
163 Eigen::Matrix2cd r = Eigen::Matrix2cd::Zero();
164 r(0, 0) = r0;
165 r(1, 1) = r1;
166 // a0 and a1 defines the R matrix in eq(3). And they are the z rotation angles
167 // activated by 0 and 1 respectively.
168 a0 = a0 / PI;
169 a1 = a1 / PI;
170 Eigen::ComplexEigenSolver<Eigen::Matrix2cd> eigen_solver(r * X * r);
171 Eigen::Matrix2cd u = eigen_solver.eigenvectors();
172 // the eigenvalues are guaranteed to be {i, -i}. We permute u so its
173 // eigenvalues have the order [i, -i].
174 if (std::abs(eigen_solver.eigenvalues()[0] + i_) < EPS) {
175 u.col(0).swap(u.col(1));
176 }
177 Eigen::Matrix2cd d = Eigen::Matrix2cd::Zero();
178 d(0, 0) = std::sqrt(i_);
179 d(1, 1) = std::sqrt(-i_);
180 Eigen::Matrix2cd v = d * u.adjoint() * r.adjoint() * b;
181 return {u, v, a0, a1};
182}
183
191static std::vector<Eigen::Matrix2cd> ucrz_angles_to_diagonal(
192 const std::vector<double> &angles) {
193 std::vector<Eigen::Matrix2cd> diag;
194 unsigned mid = (unsigned)(angles.size() / 2);
195 for (unsigned i = 0; i < mid; i++) {
196 Eigen::Matrix2cd u = Eigen::Matrix2cd::Zero();
197 u(0, 0) = std::exp(-0.5 * angles[i] * i_ * PI);
198 u(1, 1) = std::exp(-0.5 * angles[i + mid] * i_ * PI);
199 diag.push_back(u);
200 }
201 for (unsigned i = 0; i < mid; i++) {
202 Eigen::Matrix2cd u = Eigen::Matrix2cd::Zero();
203 u(0, 0) = std::exp(0.5 * angles[i] * i_ * PI);
204 u(1, 1) = std::exp(0.5 * angles[i + mid] * i_ * PI);
205 diag.push_back(u);
206 }
207 return diag;
208}
209
210static void op_map_validate(const ctrl_op_map_t &op_map) {
211 unsigned n_controls = 0;
212 unsigned n_targets = 0;
213 for (auto it = op_map.begin(); it != op_map.end(); it++) {
214 op_signature_t op_sig = it->second->get_signature();
215 if ((unsigned long)std::count(
216 op_sig.begin(), op_sig.end(), EdgeType::Quantum) != op_sig.size()) {
217 throw BadOpType(
218 "Multiplexed operations cannot have classical wires.",
219 it->second->get_type());
220 }
221 if (it == op_map.begin()) {
222 n_controls = (unsigned)it->first.size();
223 n_targets = (unsigned)op_sig.size();
224 } else {
225 if (it->first.size() != n_controls) {
226 throw std::invalid_argument(
227 "The bitstrings passed to the multiplexor must have the same "
228 "width.");
229 }
230 if (op_sig.size() != n_targets) {
231 throw std::invalid_argument(
232 "Multiplexed operations must have the same width.");
233 }
234 }
235 }
236}
237
271static void recursive_demultiplex_u2(
272 std::vector<Eigen::Matrix2cd> &unitaries, unsigned total_qubits,
273 std::vector<GateSpec> &commands, float &phase,
274 std::vector<std::vector<double>> &ucrzs,
275 const Eigen::Matrix2cd &left_compose,
276 const Eigen::Matrix2cd &right_compose) {
277 // The following two constant matrices are the bottom SQ unitaries resulted
278 // from decomposing the D gate (i.e. ZZPhase(-0.5)) using CX
279 const static Eigen::Matrix2cd U_MULT =
280 get_matrix_from_tk1_angles({0.5, 0.5, 0.5, 0.0});
281 const static Eigen::Matrix2cd V_MULT =
282 get_matrix_from_tk1_angles({0.5, 0.5, 0, 0.0});
283 unsigned n_unitaries = unitaries.size();
284 unsigned n_qubits = (unsigned)log2(n_unitaries) + 1;
285 unsigned mid = (unsigned)(n_unitaries / 2);
286 // We generalise eq(3) for n controls, demultiplex the multiplexor
287 // by demultiplexing all pairs {unitaries[i], unitaries[mid+i]} 0<=i<mid.
288 // i.e. I tensor diag(u) = I tensor diag(u_list)
289 // I tensor diag(v) = I tensor diag(v_list)
290 // D = ZZPhase(-0.5)
291 // R = UCRz(rz_list, [q_{n-1}, q_{1}, q_{2}, ..., q_{n-2}, q_0])
292 std::vector<Eigen::Matrix2cd> u_list;
293 std::vector<Eigen::Matrix2cd> v_list;
294 std::vector<double> rz_list(n_unitaries);
295
296 // merge previous UCRz gate into the multiplexor
297 std::vector<Eigen::Matrix2cd> ucrz_diag =
298 ucrz_angles_to_diagonal(ucrzs[n_qubits - 2]);
299 for (unsigned i = 0; i < unitaries.size(); i++) {
300 unitaries[i] = unitaries[i] * ucrz_diag[i];
301 }
302 // demultiplex pairs (unitaries[i], unitaries[mid+i])
303 for (unsigned i = 0; i < mid; i++) {
304 auto [u, v, a0, a1] =
305 constant_demultiplex(unitaries[i], unitaries[mid + i]);
306 u_list.push_back(u);
307 v_list.push_back(v);
308 rz_list[i] = a0;
309 rz_list[i + mid] = a1;
310 }
311
312 // update the ucrzs with the 1.5 angle resulted from decomposing ZZPhase(-0.5)
313 std::for_each(rz_list.begin(), rz_list.end(), [](double &f) { f += 1.5; });
314 ucrzs[n_qubits - 2] = rz_list;
315
316 // adding gates to the circuit
317 // add v
318 if (v_list.size() == 1) {
319 Eigen::Matrix2cd v_prime = V_MULT * v_list[0] * left_compose;
320 commands.push_back(GateSpec(OpType::U1, v_prime));
321 } else {
322 recursive_demultiplex_u2(
323 v_list, total_qubits, commands, phase, ucrzs, left_compose, V_MULT);
324 }
325 // add CX
326 commands.push_back(GateSpec(OpType::CX, total_qubits - n_qubits));
327
328 phase += 1.75;
329 // add u
330 if (u_list.size() == 1) {
331 Eigen::Matrix2cd u_prime = right_compose * u_list[0] * U_MULT;
332 commands.push_back(GateSpec(OpType::U1, u_prime));
333 } else {
334 recursive_demultiplex_u2(
335 u_list, total_qubits, commands, phase, ucrzs, U_MULT, right_compose);
336 }
337 return;
338}
339
340static ctrl_op_map_t op_map_symbol_sub(
341 const SymEngine::map_basic_basic &sub_map, const ctrl_op_map_t &op_map) {
342 ctrl_op_map_t new_op_map;
343 for (auto it = op_map.begin(); it != op_map.end(); it++) {
344 new_op_map.insert({it->first, it->second->symbol_substitution(sub_map)});
345 }
346 return new_op_map;
347}
348
349static SymSet op_map_free_symbols(const ctrl_op_map_t &op_map) {
350 SymSet all_symbols;
351 for (auto it = op_map.begin(); it != op_map.end(); it++) {
352 SymSet op_symbols = it->second->free_symbols();
353 all_symbols.insert(op_symbols.begin(), op_symbols.end());
354 }
355 return all_symbols;
356}
357
358static ctrl_op_map_t op_map_dagger(const ctrl_op_map_t &op_map) {
359 ctrl_op_map_t new_op_map;
360 for (auto it = op_map.begin(); it != op_map.end(); it++) {
361 new_op_map.insert({it->first, it->second->dagger()});
362 }
363 return new_op_map;
364}
365
366static ctrl_op_map_t op_map_transpose(const ctrl_op_map_t &op_map) {
367 ctrl_op_map_t new_op_map;
368 for (auto it = op_map.begin(); it != op_map.end(); it++) {
369 new_op_map.insert({it->first, it->second->transpose()});
370 }
371 return new_op_map;
372}
373
374static bool opmap_it_equal(
375 const std::pair<std::vector<bool>, Op_ptr> &lhs,
376 const std::pair<std::vector<bool>, Op_ptr> &rhs) {
377 return lhs.first == rhs.first && *lhs.second == *rhs.second;
378}
379static bool tensored_opmap_it_equal(
380 const std::pair<std::vector<bool>, std::vector<Op_ptr>> &lhs,
381 const std::pair<std::vector<bool>, std::vector<Op_ptr>> &rhs) {
382 return lhs.first == rhs.first &&
383 std::equal(
384 lhs.second.begin(), lhs.second.end(), rhs.second.begin(),
385 rhs.second.end(),
386 [](const Op_ptr &a, const Op_ptr &b) { return *a == *b; });
387}
388
389// Check if two ctrl_op_map_t are semantically equal.
390static bool opmap_compare(
391 const ctrl_op_map_t &map1, const ctrl_op_map_t &map2) {
392 return std::equal(
393 map1.begin(), map1.end(), map2.begin(), map2.end(), opmap_it_equal);
394}
395// Check if two ctrl_tensored_op_map_t are semantically equal.
396static bool opmap_compare(
397 const ctrl_tensored_op_map_t &map1, const ctrl_tensored_op_map_t &map2) {
398 return std::equal(
399 map1.begin(), map1.end(), map2.begin(), map2.end(),
400 tensored_opmap_it_equal);
401}
402
404 : Box(OpType::MultiplexorBox), op_map_(op_map) {
405 auto it = op_map.begin();
406 if (it == op_map.end()) {
407 throw std::invalid_argument(
408 "The op_map argument passed to MultiplexorBox cannot be empty.");
409 }
410 n_controls_ = (unsigned)it->first.size();
411 n_targets_ = it->second->n_qubits();
412 op_map_validate(op_map);
413}
414
416 : Box(other),
417 n_controls_(other.n_controls_),
418 n_targets_(other.n_targets_),
419 op_map_(other.op_map_) {}
420
422 const SymEngine::map_basic_basic &sub_map) const {
423 ctrl_op_map_t new_op_map = op_map_symbol_sub(sub_map, op_map_);
424 return std::make_shared<MultiplexorBox>(new_op_map);
425}
426
428 return op_map_free_symbols(op_map_);
429}
430
432 return std::make_shared<MultiplexorBox>(op_map_dagger(op_map_));
433}
434
436 return std::make_shared<MultiplexorBox>(op_map_transpose(op_map_));
437}
438
440 op_signature_t qubits(n_controls_ + n_targets_, EdgeType::Quantum);
441 return qubits;
442}
443
444bool MultiplexorBox::is_equal(const Op &op_other) const {
445 const MultiplexorBox &other = dynamic_cast<const MultiplexorBox &>(op_other);
446 if (id_ == other.get_id()) return true;
447 return opmap_compare(op_map_, other.op_map_);
448}
449
450nlohmann::json MultiplexorBox::to_json(const Op_ptr &op) {
451 const auto &box = static_cast<const MultiplexorBox &>(*op);
452 nlohmann::json j = core_box_json(box);
453 j["op_map"] = box.get_op_map();
454 return j;
455}
456
457Op_ptr MultiplexorBox::from_json(const nlohmann::json &j) {
458 MultiplexorBox box = MultiplexorBox(j.at("op_map").get<ctrl_op_map_t>());
459 return set_box_id(
460 box,
461 boost::lexical_cast<boost::uuids::uuid>(j.at("id").get<std::string>()));
462}
463
465 circ_ = std::make_shared<Circuit>(
466 multiplexor_sequential_decomp(op_map_, n_controls_, n_targets_));
467}
468
470 : Box(OpType::MultiplexedRotationBox), op_map_(op_map) {
471 auto it = op_map.begin();
472 if (it == op_map.end()) {
473 throw std::invalid_argument(
474 "The op_map argument passed to MultiplexedRotationBox cannot be "
475 "empty.");
476 }
477 for (; it != op_map.end(); it++) {
478 if (it == op_map.begin()) {
479 n_controls_ = (unsigned)it->first.size();
480 if (n_controls_ > MAX_N_CONTROLS) {
481 throw std::invalid_argument(
482 "MultiplexedRotationBox only supports bitstrings up to " +
483 std::to_string(MAX_N_CONTROLS) + " bits.");
484 }
485 axis_ = it->second->get_type();
486 if (axis_ != OpType::Rx && axis_ != OpType::Ry && axis_ != OpType::Rz) {
487 throw BadOpType(
488 "Ops passed to MultiplexedRotationBox must be either Rx, Ry, or "
489 "Rz.",
490 axis_);
491 }
492 } else {
493 if (it->second->get_type() != axis_) {
494 throw std::invalid_argument(
495 "Ops passed to MultiplexedRotationBox must have the same rotation "
496 "type.");
497 }
498 }
499 }
500 op_map_validate(op_map);
501}
502
504 const MultiplexedRotationBox &other)
505 : Box(other),
506 n_controls_(other.n_controls_),
507 op_map_(other.op_map_),
508 axis_(other.axis_) {}
509
511 const SymEngine::map_basic_basic &sub_map) const {
512 ctrl_op_map_t new_op_map = op_map_symbol_sub(sub_map, op_map_);
513 return std::make_shared<MultiplexedRotationBox>(new_op_map);
514}
515
517 return op_map_free_symbols(op_map_);
518}
519
521 return std::make_shared<MultiplexedRotationBox>(op_map_dagger(op_map_));
522}
523
525 return std::make_shared<MultiplexedRotationBox>(op_map_transpose(op_map_));
526}
527
529 op_signature_t qubits(n_controls_ + 1, EdgeType::Quantum);
530 return qubits;
531}
532
533bool MultiplexedRotationBox::is_equal(const Op &op_other) const {
534 const MultiplexedRotationBox &other =
535 dynamic_cast<const MultiplexedRotationBox &>(op_other);
536 if (id_ == other.get_id()) return true;
537 return opmap_compare(op_map_, other.op_map_);
538}
539
540nlohmann::json MultiplexedRotationBox::to_json(const Op_ptr &op) {
541 const auto &box = static_cast<const MultiplexedRotationBox &>(*op);
542 nlohmann::json j = core_box_json(box);
543 j["op_map"] = box.get_op_map();
544 return j;
545}
546
549 MultiplexedRotationBox(j.at("op_map").get<ctrl_op_map_t>());
550 return set_box_id(
551 box,
552 boost::lexical_cast<boost::uuids::uuid>(j.at("id").get<std::string>()));
553}
554
555std::vector<GateSpec> MultiplexedRotationBox::decompose() const {
556 unsigned long long n_rotations = 1ULL << n_controls_;
557 std::vector<Expr> rotations(n_rotations);
558 // convert op_map to a vector of 2^n_controls_ angles
559 for (unsigned long long i = 0; i < n_rotations; i++) {
560 auto it = op_map_.find(dec_to_bin(i, n_controls_));
561 if (it == op_map_.end()) {
562 rotations[i] = 0;
563 } else {
564 rotations[i] = it->second->get_params()[0];
565 }
566 }
567 std::vector<GateSpec> commands;
568 OpType axis = axis_;
569 if (axis_ == OpType::Rx) {
570 commands.push_back(GateSpec(OpType::H, n_controls_));
571 axis = OpType::Rz;
572 }
573 recursive_demultiplex_rotation(
574 rotations, axis, n_controls_ + 1, commands, RecursionNodeType::Root);
575 if (axis_ == OpType::Rx) {
576 commands.push_back(GateSpec(OpType::H, n_controls_));
577 }
578 return commands;
579}
580
582 Circuit circ(n_controls_ + 1);
583 if (n_controls_ == 0) {
584 auto it = op_map_.begin();
585 circ.add_op<unsigned>(it->second, {0});
586 circ_ = std::make_shared<Circuit>(circ);
587 return;
588 }
589
590 for (const GateSpec &gs : this->decompose()) {
591 switch (gs.type) {
592 case OpType::CX:
593 circ.add_op<unsigned>(OpType::CX, {*gs.qubit, n_controls_});
594 break;
595 case OpType::Rx:
596 circ.add_op<unsigned>(OpType::Rx, *gs.angle, {n_controls_});
597 break;
598 case OpType::Ry:
599 circ.add_op<unsigned>(OpType::Ry, *gs.angle, {n_controls_});
600 break;
601 case OpType::Rz:
602 circ.add_op<unsigned>(OpType::Rz, *gs.angle, {n_controls_});
603 break;
604 case OpType::H:
605 circ.add_op<unsigned>(OpType::H, {n_controls_});
606 break;
607 default:
608 // this should never be hit
609 TKET_ASSERT(false);
610 }
611 }
612
613 circ_ = std::make_shared<Circuit>(circ);
614}
615
617 : Box(OpType::MultiplexedU2Box), op_map_(op_map), impl_diag_(impl_diag) {
618 auto it = op_map.begin();
619 if (it == op_map.end()) {
620 throw std::invalid_argument(
621 "The op_map argument passed to MultiplexedU2Box cannot be empty.");
622 }
623 n_controls_ = (unsigned)it->first.size();
624 if (n_controls_ > MAX_N_CONTROLS) {
625 throw std::invalid_argument(
626 "MultiplexedU2Box only supports bitstrings up to " +
627 std::to_string(MAX_N_CONTROLS) + " bits.");
628 }
629 for (; it != op_map.end(); it++) {
630 OpType optype = it->second->get_type();
631 if (!is_single_qubit_unitary_type(optype) &&
632 optype != OpType::Unitary1qBox) {
633 throw BadOpType(
634 "Ops passed to MultiplexedU2Box must be single-qubit unitary gate "
635 "types or Unitary1qBox.",
636 optype);
637 }
638 }
639 op_map_validate(op_map);
640}
641
643 : Box(other),
644 n_controls_(other.n_controls_),
645 op_map_(other.op_map_),
646 impl_diag_(other.impl_diag_) {}
647
649 const SymEngine::map_basic_basic &sub_map) const {
650 ctrl_op_map_t new_op_map = op_map_symbol_sub(sub_map, op_map_);
651 return std::make_shared<MultiplexedU2Box>(new_op_map, impl_diag_);
652}
653
655 return op_map_free_symbols(op_map_);
656}
657
659 return std::make_shared<MultiplexedU2Box>(op_map_dagger(op_map_), impl_diag_);
660}
661
663 return std::make_shared<MultiplexedU2Box>(
664 op_map_transpose(op_map_), impl_diag_);
665}
666
668 op_signature_t qubits(n_controls_ + 1, EdgeType::Quantum);
669 return qubits;
670}
671
672bool MultiplexedU2Box::is_equal(const Op &op_other) const {
673 const MultiplexedU2Box &other =
674 dynamic_cast<const MultiplexedU2Box &>(op_other);
675 if (id_ == other.get_id()) return true;
676 return impl_diag_ == other.impl_diag_ &&
677 opmap_compare(op_map_, other.op_map_);
678}
679
680nlohmann::json MultiplexedU2Box::to_json(const Op_ptr &op) {
681 const auto &box = static_cast<const MultiplexedU2Box &>(*op);
682 nlohmann::json j = core_box_json(box);
683 j["op_map"] = box.get_op_map();
684 j["impl_diag"] = box.get_impl_diag();
685 return j;
686}
687
688Op_ptr MultiplexedU2Box::from_json(const nlohmann::json &j) {
690 j.at("op_map").get<ctrl_op_map_t>(), j.at("impl_diag").get<bool>());
691 return set_box_id(
692 box,
693 boost::lexical_cast<boost::uuids::uuid>(j.at("id").get<std::string>()));
694}
695
697 unsigned long long n_unitaries = 1ULL << n_controls_;
698 std::vector<Eigen::Matrix2cd> unitaries(n_unitaries);
699 // convert op_map to a vector of 2^n_controls_ unitaries
700 for (unsigned long long i = 0; i < n_unitaries; i++) {
701 auto it = op_map_.find(dec_to_bin(i, n_controls_));
702 if (it == op_map_.end()) {
703 unitaries[i] = Eigen::Matrix2cd::Identity();
704 } else {
705 if (it->second->get_type() == OpType::Unitary1qBox) {
706 std::shared_ptr<const Unitary1qBox> u1box =
707 std::dynamic_pointer_cast<const Unitary1qBox>(it->second);
708 unitaries[i] = u1box->get_matrix();
709 } else {
710 if (!it->second->free_symbols().empty()) {
711 throw Unsupported("Can't decompose symbolic MultiplexedU2Box.");
712 }
713 unitaries[i] = GateUnitaryMatrix::get_unitary(*as_gate_ptr(it->second));
714 }
715 }
716 }
717
718 // initialise the ucrz list
719 std::vector<std::vector<double>> ucrzs(n_controls_);
720 for (unsigned i = 0; i < n_controls_; i++) {
721 ucrzs[i] = std::vector<double>(1ULL << (i + 1), 0.0);
722 }
723
724 std::vector<GateSpec> commands = {};
725 float phase = 0;
726 recursive_demultiplex_u2(
727 unitaries, n_controls_ + 1, commands, phase, ucrzs,
728 Eigen::Matrix2cd::Identity(), Eigen::Matrix2cd::Identity());
729 // convert the ucrzs to a diagonal matrix
730 Eigen::VectorXcd diag =
731 Eigen::VectorXcd::Constant(1ULL << (n_controls_ + 1), 1);
732 for (unsigned i = 0; i < n_controls_; i++) {
733 // ith ucrzs acts on i+2 qubits
734 // which has n_controls_ + 1 - (i+2) identities its the tensor product
735 // therefore (n_controls_ + 1 - (i+2))^2 copies in the diagonal
736 for (unsigned long long offset = 0;
737 offset < (1ULL << (n_controls_ + 1 - (i + 2))); offset++) {
738 for (unsigned long long j = 0; j < (1ULL << (i + 1)); j++) {
739 // the bitstrings in a ucrz are mapped to qubits not in the standard
740 // order
741 unsigned long long diag_idx =
742 (j >= (1ULL << i)) ? (j - (1ULL << i)) * 2 + 1 : j * 2;
743 diag[diag_idx + offset * (1ULL << (i + 2))] *=
744 std::exp(-0.5 * i_ * PI * ucrzs[i][j]);
745 diag[diag_idx + offset * (1ULL << (i + 2)) + (1ULL << (i + 1))] *=
746 std::exp(0.5 * i_ * PI * ucrzs[i][j]);
747 }
748 }
749 }
750 return MultiplexedU2Commands(commands, diag, phase);
751}
752
754 Circuit circ(n_controls_ + 1);
755 Eigen::VectorXcd diag_vec;
756
757 if (n_controls_ == 0) {
758 auto it = op_map_.begin();
759 circ.add_op<unsigned>(it->second, {0});
760 circ_ = std::make_shared<Circuit>(circ);
761 return;
762 }
763
764 MultiplexedU2Commands decomp = this->decompose();
765 for (unsigned i = 0; i < decomp.commands.size(); i++) {
766 GateSpec gc = decomp.commands[i];
767 // n.b. with zero indexing "n_controls" corresponds to the target qubit
768 switch (gc.type) {
769 case OpType::CX:
770 circ.add_op<unsigned>(OpType::CX, {*gc.qubit, n_controls_});
771 break;
772 case OpType::U1:
773 circ.add_box(Unitary1qBox(*gc.matrix), {n_controls_});
774 break;
775 default:
776 // this should never be hit
777 TKET_ASSERT(false);
778 }
779 }
780
781 circ.add_phase(decomp.phase);
782
783 if (impl_diag_ &&
784 (decomp.diag - Eigen::VectorXcd::Constant(1ULL << circ.n_qubits(), 1))
785 .cwiseAbs()
786 .sum() > EPS) {
787 std::vector<unsigned> args(circ.n_qubits());
788 std::iota(std::begin(args), std::end(args), 0);
789 circ.add_box(DiagonalBox(decomp.diag), args);
790 }
791 circ_ = std::make_shared<Circuit>(circ);
792}
793
795 const ctrl_tensored_op_map_t &op_map)
796 : Box(OpType::MultiplexedTensoredU2Box), op_map_(op_map) {
797 auto it = op_map.begin();
798 if (it == op_map.end()) {
799 throw std::invalid_argument(
800 "The op_map argument passed to MultiplexedTensoredU2Box cannot be "
801 "empty.");
802 }
803 n_controls_ = (unsigned)it->first.size();
804 n_targets_ = (unsigned)it->second.size();
805 if (n_controls_ > MAX_N_CONTROLS) {
806 throw std::invalid_argument(
807 "MultiplexedTensoredU2Box only supports bitstrings up to " +
808 std::to_string(MAX_N_CONTROLS) + " bits.");
809 }
810 for (; it != op_map.end(); it++) {
811 if (it->first.size() != n_controls_) {
812 throw std::invalid_argument(
813 "The bitstrings passed to MultiplexedTensoredU2Box must have the "
814 "same width.");
815 ;
816 }
817 if (it->second.size() != n_targets_) {
818 throw std::invalid_argument(
819 "Each tensored operation passed to MultiplexedTensoredU2Box must "
820 "have the same number of U2 components");
821 }
822 for (auto op : it->second) {
823 OpType optype = op->get_type();
824 if (!is_single_qubit_unitary_type(optype) &&
825 optype != OpType::Unitary1qBox) {
826 throw BadOpType(
827 "Ops passed to MultiplexedTensoredU2Box must be single-qubit "
828 "unitary gate types or Unitary1qBox.",
829 optype);
830 }
831 }
832 }
833}
834
836 const MultiplexedTensoredU2Box &other)
837 : Box(other),
838 n_controls_(other.n_controls_),
839 n_targets_(other.n_targets_),
840 op_map_(other.op_map_) {}
841
843 const SymEngine::map_basic_basic &sub_map) const {
844 ctrl_tensored_op_map_t new_op_map;
845 for (auto it = op_map_.begin(); it != op_map_.end(); it++) {
846 std::vector<Op_ptr> ops;
847 for (auto op : it->second) {
848 ops.push_back(op->symbol_substitution(sub_map));
849 }
850 new_op_map.insert({it->first, ops});
851 }
852 return std::make_shared<MultiplexedTensoredU2Box>(new_op_map);
853}
854
856 SymSet all_symbols;
857 for (auto it = op_map_.begin(); it != op_map_.end(); it++) {
858 for (auto op : it->second) {
859 SymSet op_symbols = op->free_symbols();
860 all_symbols.insert(op_symbols.begin(), op_symbols.end());
861 }
862 }
863 return all_symbols;
864}
865
867 ctrl_tensored_op_map_t new_op_map;
868 for (auto it = op_map_.begin(); it != op_map_.end(); it++) {
869 std::vector<Op_ptr> ops;
870 for (auto op : it->second) {
871 ops.push_back(op->dagger());
872 }
873 new_op_map.insert({it->first, ops});
874 }
875 return std::make_shared<MultiplexedTensoredU2Box>(new_op_map);
876}
877
879 ctrl_tensored_op_map_t new_op_map;
880 for (auto it = op_map_.begin(); it != op_map_.end(); it++) {
881 std::vector<Op_ptr> ops;
882 for (auto op : it->second) {
883 ops.push_back(op->transpose());
884 }
885 new_op_map.insert({it->first, ops});
886 }
887 return std::make_shared<MultiplexedTensoredU2Box>(new_op_map);
888}
889
891 op_signature_t qubits(n_controls_ + n_targets_, EdgeType::Quantum);
892 return qubits;
893}
894
895bool MultiplexedTensoredU2Box::is_equal(const Op &op_other) const {
896 const MultiplexedTensoredU2Box &other =
897 dynamic_cast<const MultiplexedTensoredU2Box &>(op_other);
898 if (id_ == other.get_id()) return true;
899 return opmap_compare(op_map_, other.op_map_);
900}
901
902nlohmann::json MultiplexedTensoredU2Box::to_json(const Op_ptr &op) {
903 const auto &box = static_cast<const MultiplexedTensoredU2Box &>(*op);
904 nlohmann::json j = core_box_json(box);
905 j["op_map"] = box.get_op_map();
906 return j;
907}
908
912 return set_box_id(
913 box,
914 boost::lexical_cast<boost::uuids::uuid>(j.at("id").get<std::string>()));
915}
916
918 Circuit &circ, const std::vector<MultiplexedU2Commands> &m_u2_decomps,
919 unsigned n_controls_, unsigned n_targets_) {
920 TKET_ASSERT(m_u2_decomps.size() == n_targets_);
921 // Each Multiplexor Decomposition will be up to some global phase
922 // First we add these phase contributions to the circuit
923 unsigned reference_size = m_u2_decomps[0].commands.size();
924 for (unsigned i = 0; i < m_u2_decomps.size(); i++) {
925 circ.add_phase(m_u2_decomps[i].phase);
926 // We also confirm that each Multiplexor decomposition has the
927 // same number of commands
928 TKET_ASSERT(reference_size == m_u2_decomps[i].commands.size());
929 }
930
931 // we now iterate through all the commands, adding them to the circuit
932 // in an interleaved manner
933 for (unsigned i = 0; i < reference_size; i++) {
934 for (unsigned target = 0; target < m_u2_decomps.size(); target++) {
935 GateSpec gate = m_u2_decomps[target].commands[i];
936 unsigned rotated_index;
937 switch (gate.type) {
938 case OpType::CX:
939 // we also need to map gate.qubit to the correct qubit
940 // we know that the bitstrings for the "target"th target have been
941 // left rotated by "target", so:
942 rotated_index = (*gate.qubit + (target % n_targets_)) % n_controls_;
943 TKET_ASSERT(i % 2 == 1);
944 circ.add_op<unsigned>(
945 OpType::CX, {rotated_index, n_controls_ + target});
946 break;
947 case OpType::U1:
948 TKET_ASSERT(i % 2 == 0);
949 circ.add_box(Unitary1qBox(*gate.matrix), {n_controls_ + target});
950 break;
951 default:
952 // this should never be hit
953 TKET_ASSERT(false);
954 }
955 }
956 }
957 return;
958}
959
960std::pair<ctrl_op_map_t, Eigen::VectorXcd>
962 const Eigen::VectorXcd &full_diag, unsigned n_controls_) {
963 // disentangle one qubit from the diagonal
964 // results in a multiplexed-Rz targeting the target j
965 Eigen::VectorXcd diag_vec =
966 Eigen::VectorXcd::Constant(1ULL << n_controls_, 1);
967 ctrl_op_map_t multip_rz;
968 for (unsigned long long j = 0; j < (1ULL << n_controls_); j++) {
969 // As full_diag as produced by MultiplexedU2Box::decompose
970 // adds an extra qubit for the compensating diagonal, we know
971 // that the control bitstring corresponding to j should match
972 // up with the rotated bitstrings
973 Complex a = full_diag[2 * j];
974 Complex b = full_diag[2 * j + 1];
975 // convert diag[a,b] into a p*Rz(alpha)
976 double a_phase = std::arg(a);
977 double b_phase = std::arg(b);
978 double alpha = (b_phase - a_phase) / PI;
979 Complex p = std::exp((b_phase + a_phase) * 0.5 * i_);
980 if (std::abs(alpha) > EPS) {
981 // bitstr should innately correspond to the rotated controls
982 // when being constructed from diagonal provided
983 // by MultiplexedU2Box::decompose
984 multip_rz.insert(
985 {dec_to_bin(j, n_controls_), get_op_ptr(OpType::Rz, alpha)});
986 }
987 diag_vec[j] *= p;
988 }
989 return std::make_pair(multip_rz, diag_vec);
990}
991
993 Circuit &circ, const std::vector<ctrl_op_map_t> &all_multiplexed_rz,
994 unsigned n_controls_, unsigned n_targets_) {
995 TKET_ASSERT(all_multiplexed_rz.size() == n_targets_);
996 std::vector<std::vector<GateSpec>> all_decomps;
997 // First get all GateSpec by constructing and decomposing
998 // MultiplexedRotationBox
999 for (unsigned target = 0; target < n_targets_; target++) {
1000 ctrl_op_map_t map = all_multiplexed_rz[target];
1001 if (!map.empty()) {
1002 all_decomps.push_back(
1003 MultiplexedRotationBox(all_multiplexed_rz[target]).decompose());
1004 } else {
1005 all_decomps.push_back({});
1006 }
1007 }
1008 TKET_ASSERT(!all_decomps.empty());
1009 unsigned reference_size = 0;
1010 for (unsigned i = 0; i < all_decomps.size(); i++) {
1011 if (!all_decomps[i].empty() && !reference_size) {
1012 reference_size = all_decomps[i].size();
1013 }
1014 TKET_ASSERT(reference_size == all_decomps[i].size());
1015 }
1016
1017 // => no MultiplexedRz so we can carry on
1018 if (!reference_size) return;
1019
1020 // Then iterate through all the commands, adding them to the circuit
1021 // in an interleaved manner
1022 for (unsigned i = 0; i < reference_size; i++) {
1023 for (unsigned target = 0; target < all_decomps.size(); target++) {
1024 if (!all_decomps[target].empty()) {
1025 GateSpec gate = all_decomps[target][i];
1026 unsigned rotated_index;
1027 switch (gate.type) {
1028 case OpType::CX:
1029 // we also need to map gate.qubit to the correct qubit
1030 // we know that the bitstrings for the "target"th target have been
1031 // left rotated by "target", so:
1032 rotated_index = (*gate.qubit + (target % n_targets_)) % n_controls_;
1033 circ.add_op<unsigned>(
1034 OpType::CX, {rotated_index, n_controls_ + target});
1035 break;
1036 case OpType::Rz:
1037 circ.add_op<unsigned>(
1038 OpType::Rz, *gate.angle, {n_controls_ + target});
1039 break;
1040 default:
1041 // this should never be hit
1042 TKET_ASSERT(false);
1043 }
1044 }
1045 }
1046 }
1047 return;
1048}
1049
1050Eigen::VectorXcd combine_diagonals(
1051 const std::vector<Eigen::VectorXcd> &all_diags, unsigned n_controls_,
1052 unsigned n_targets_) {
1053 Eigen::VectorXcd combined_diag_vec =
1054 Eigen::VectorXcd::Constant(1ULL << n_controls_, 1);
1055 TKET_ASSERT(all_diags.size() == n_targets_);
1056 for (unsigned rotate = 0; rotate < n_targets_; rotate++) {
1057 Eigen::VectorXcd diag_vec = all_diags[rotate];
1058 TKET_ASSERT(diag_vec.size() == combined_diag_vec.size());
1059 // the "rotate" indexed diagonal vector in all_diags
1060 // will have indexing corresponding to a left rotation
1061 // of the input bit strings of "rotate"
1062 for (unsigned index = 0; index < diag_vec.size(); index++) {
1063 // to construct the diagonal vector correctly, we take
1064 // the value "index", convert it to a bitstring, right
1065 // rotate it by "rotate" and convert it back an integer.
1066 unsigned rotate_value = rotate % n_controls_;
1067 std::vector<bool> as_bits = dec_to_bin(index, n_controls_);
1068 // right rotate
1069 std::rotate(
1070 as_bits.begin(), as_bits.begin() + (as_bits.size() - rotate_value),
1071 as_bits.end());
1072 unsigned rotated_index = bin_to_dec(as_bits);
1073 TKET_ASSERT(rotated_index <= combined_diag_vec.size());
1074 // This gives a new index for updating the correct element of the
1075 // diagonal vector
1076 combined_diag_vec[rotated_index] *= diag_vec[index];
1077 }
1078 }
1079 return combined_diag_vec;
1080}
1081
1091 // Break the input into separate Multiplexors
1092 std::vector<MultiplexedU2Commands> m_u2_decomps;
1093 for (unsigned target = 0; target < n_targets_; target++) {
1094 ctrl_op_map_t u2_op_map;
1095 for (auto it = op_map_.begin(); it != op_map_.end(); it++) {
1096 // by rotating the control condition we change the order of CX gates for
1097 // each decomposition we can later use this to interleave the multiplexor
1098 // decompositions and so reduce depth
1099 std::vector<bool> control_condition = it->first;
1100 std::rotate(
1101 control_condition.begin(),
1102 control_condition.begin() + (target % n_controls_),
1103 control_condition.end());
1104 u2_op_map.insert({control_condition, it->second[target]});
1105 }
1106 m_u2_decomps.push_back(MultiplexedU2Box(u2_op_map).decompose());
1107 }
1108
1109 // Next we split each diagonal vector for each MultiplexedU2 into a
1110 // Multiplexed-Rz on the target qubit and a Diagonal gate over the control
1111 // register
1112 std::vector<ctrl_op_map_t> all_multiplexed_rz;
1113 std::vector<Eigen::VectorXcd> all_diags;
1114 for (unsigned i = 0; i < m_u2_decomps.size(); i++) {
1115 std::pair<ctrl_op_map_t, Eigen::VectorXcd> disentangled =
1117 m_u2_decomps[i].diag, n_controls_);
1118 all_multiplexed_rz.push_back(disentangled.first);
1119 all_diags.push_back(disentangled.second);
1120 }
1121
1122 // Final we merge the diagonals over the same qubits into a combined operator
1123 Eigen::VectorXcd combined_diag_vec =
1124 combine_diagonals(all_diags, n_controls_, n_targets_);
1125
1126 // Now we can construct the circuit - first we add the U1 + CX segment of the
1127 // circuit construction with interleaving
1128 Circuit circ(n_controls_ + n_targets_);
1129 add_cx_u1(circ, m_u2_decomps, n_controls_, n_targets_);
1130
1131 // Then add Multiplexed-Rz gates to circ
1132 add_multi_rz(circ, all_multiplexed_rz, n_controls_, n_targets_);
1133
1134 // Finally add the combined diagonal vector to the circuit
1135 if ((combined_diag_vec - Eigen::VectorXcd::Constant(1ULL << n_controls_, 1))
1136 .cwiseAbs()
1137 .sum() > EPS) {
1138 std::vector<unsigned> control_qubits(n_controls_);
1139 std::iota(std::begin(control_qubits), std::end(control_qubits), 0);
1140 circ.add_box(DiagonalBox(combined_diag_vec), control_qubits);
1141 }
1142 circ_ = std::make_shared<Circuit>(circ);
1143}
1144
1149
1150} // namespace tket
Generally useful typedefs and constants.
#define REGISTER_OPFACTORY(type, opclass)
When an OpType needs custom JSON conversion methods (as is the case for box types),...
Operation type not valid in the current context.
Abstract class for an operation from which a circuit can be extracted.
Definition Boxes.hpp:38
friend Op_ptr set_box_id(BoxT &b, boost::uuids::uuid newid)
Set explicit ID on a box.
Definition Boxes.hpp:125
std::shared_ptr< Circuit > circ_
Definition Boxes.hpp:103
boost::uuids::uuid get_id() const
Unique identifier (preserved on copy)
Definition Boxes.hpp:91
boost::uuids::uuid id_
Definition Boxes.hpp:104
A circuit.
Definition Circuit.hpp:212
unsigned n_qubits() const
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
Vertex add_box(const BoxT &box, const std::vector< ID > &args, std::optional< std::string > opgroup=std::nullopt)
Append a box to the circuit.
Definition Circuit.hpp:793
void add_phase(Expr a)
Adds a global phase to the circuit.
Definition Circuit.cpp:148
Box to synthesise a diagonal operator.
Multiplexed single-axis rotations.
Op_ptr symbol_substitution(const SymEngine::map_basic_basic &sub_map) const override
Operation with values for symbols substituted.
SymSet free_symbols() const override
Set of all free symbols occurring in operation parameters.
Op_ptr transpose() const override
Transpose of a unitary operation.
void generate_circuit() const override
Implement multiplexed rotation (i.e.
static Op_ptr from_json(const nlohmann::json &j)
MultiplexedRotationBox(const ctrl_op_map_t &op_map)
Construct from a op_map.
op_signature_t get_signature() const override
Vector specifying type of data for each port on op.
std::vector< GateSpec > decompose() const
Op_ptr dagger() const override
Inverse (of a unitary operation)
static nlohmann::json to_json(const Op_ptr &op)
bool is_equal(const Op &op_other) const override
Equality check between two MultiplexedRotationBox instances.
Multiplexed-Tensored-U2 gate.
static Op_ptr from_json(const nlohmann::json &j)
bool is_equal(const Op &op_other) const override
Equality check between two MultiplexedTensoredU2Box instances.
op_signature_t get_signature() const override
Vector specifying type of data for each port on op.
MultiplexedTensoredU2Box(const ctrl_tensored_op_map_t &op_map)
Construct from a op_map.
Op_ptr symbol_substitution(const SymEngine::map_basic_basic &sub_map) const override
Operation with values for symbols substituted.
static nlohmann::json to_json(const Op_ptr &op)
Op_ptr transpose() const override
Transpose of a unitary operation.
SymSet free_symbols() const override
Set of all free symbols occurring in operation parameters.
Op_ptr dagger() const override
Inverse (of a unitary operation)
void generate_circuit() const override
Implement multiplexed-tensored-U2 gate by decomposing a sequence of MultiplexedU2 gate and moving the...
Multiplexed U2 gate.
static Op_ptr from_json(const nlohmann::json &j)
Op_ptr transpose() const override
Transpose of a unitary operation.
Op_ptr dagger() const override
Inverse (of a unitary operation)
SymSet free_symbols() const override
Set of all free symbols occurring in operation parameters.
void generate_circuit() const override
Implement multiplexed U2 gate (i.e.
bool is_equal(const Op &op_other) const override
Equality check between two MultiplexedU2Box instances.
static nlohmann::json to_json(const Op_ptr &op)
op_signature_t get_signature() const override
Vector specifying type of data for each port on op.
MultiplexedU2Commands decompose() const
Decompose the multiplexor into a sequence of interleaving CX and single qubit gates followed by a dia...
MultiplexedU2Box(const ctrl_op_map_t &op_map, bool impl_diag=true)
Construct from a op_map.
Op_ptr symbol_substitution(const SymEngine::map_basic_basic &sub_map) const override
Operation with values for symbols substituted.
Multiplexed ops.
void generate_circuit() const override
Implement the multiplexor naively using X gates and QControlBoxes.
Op_ptr dagger() const override
Inverse (of a unitary operation)
Op_ptr transpose() const override
Transpose of a unitary operation.
static nlohmann::json to_json(const Op_ptr &op)
static Op_ptr from_json(const nlohmann::json &j)
op_signature_t get_signature() const override
Vector specifying type of data for each port on op.
SymSet free_symbols() const override
Set of all free symbols occurring in operation parameters.
bool is_equal(const Op &op_other) const override
Equality check between two MultiplexorBox instances.
Op_ptr symbol_substitution(const SymEngine::map_basic_basic &sub_map) const override
Operation with values for symbols substituted.
Abstract class representing an operation type.
Definition Op.hpp:53
One-qubit operation defined as a unitary matrix.
Definition Boxes.hpp:195
Defines tket::DeviceCharacterisation, used in NoiseAwarePlacement and in commute_SQ_gates_through_SWA...
Definition Path.cpp:22
bool is_single_qubit_unitary_type(OpType optype)
Test for single-qubit gates that can be expressed as TK1.
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
@ QControlBox
See QControlBox.
@ Unitary1qBox
See Unitary1qBox.
@ CX
Controlled OpType::X.
Op_ptr get_op_ptr(OpType chosen_type, const Expr &param, unsigned n_qubits)
Get an operation with a given type, single parameter and qubit count.
std::vector< double > tk1_angles_from_unitary(const Eigen::Matrix2cd &U)
Construct TK1 angles and phase from matrix.
Definition Rotation.cpp:385
constexpr Complex i_(0, 1)
A fixed square root of -1.
@ Quantum
A wire carrying quantum information, corresponding to some allocated Qubit.
std::complex< double > Complex
Complex number.
Definition Constants.hpp:29
std::vector< bool > dec_to_bin(unsigned long long dec, unsigned width)
convert an unsigned to its binary representation big-endian
Eigen::Matrix2cd get_matrix_from_tk1_angles(std::vector< Expr > params)
Construct matrix from TK1 angles and phase.
Definition Rotation.cpp:504
std::shared_ptr< const Op > Op_ptr
Definition OpPtr.hpp:24
RecursionNodeType
Indicates whether a recursion step in recursive_demultiplex_rotation is either a left child,...
unsigned long long bin_to_dec(const std::vector< bool > &bin)
convert an bit vector to its decimal representation big-endian
void add_multi_rz(Circuit &circ, const std::vector< ctrl_op_map_t > &all_multiplexed_rz, unsigned n_controls_, unsigned n_targets_)
Eigen::VectorXcd combine_diagonals(const std::vector< Eigen::VectorXcd > &all_diags, unsigned n_controls_, unsigned n_targets_)
void add_cx_u1(Circuit &circ, const std::vector< MultiplexedU2Commands > &m_u2_decomps, unsigned n_controls_, unsigned n_targets_)
std::map< std::vector< bool >, std::vector< Op_ptr > > ctrl_tensored_op_map_t
Map bitstrings to tensored Ops.
constexpr double EPS
Default tolerance for floating-point comparisons.
Definition Constants.hpp:38
std::pair< ctrl_op_map_t, Eigen::VectorXcd > disentangle_final_qubit_from_diagonal(const Eigen::VectorXcd &full_diag, unsigned n_controls_)
std::set< Sym, SymCompareLess > SymSet
std::vector< EdgeType > op_signature_t
Definition EdgeType.hpp:61
constexpr double PI
Definition Constants.hpp:41
nlohmann::json core_box_json(const Box &box)
Definition Boxes.cpp:595
std::map< std::vector< bool >, Op_ptr > ctrl_op_map_t
Map bitstrings to Ops.
std::optional< Eigen::Matrix2cd > matrix
std::optional< unsigned > qubit
std::optional< Expr > angle
static Eigen::MatrixXcd get_unitary(const Gate &gate)
The gate object knows how many qubits, and the (symbolic) parameters.
std::vector< GateSpec > commands