22#define _USE_MATH_DEFINES
45template <
typename Time = Types::time_type>
49 std::unordered_map<std::vector<bool>,
61 std::vector<OperationPtr>;
63 using value_type =
typename OperationsVector::value_type;
65 using pointer =
typename OperationsVector::pointer;
67 using reference =
typename OperationsVector::reference;
69 using size_type =
typename OperationsVector::size_type;
72 using iterator =
typename OperationsVector::iterator;
76 typename OperationsVector::const_reverse_iterator;
96 void Execute(
const std::shared_ptr<Simulators::ISimulator> &sim,
101 for (
const auto &op : operations) op->Execute(sim, state);
132 if (index >= operations.size())
return;
133 operations[index] = op;
153 operations.insert(operations.end(), ops.begin(), ops.end());
180 void Clear() { operations.clear(); }
191 for (
auto &op : operations) newops.emplace_back(op->Clone());
193 return std::make_shared<Circuit<Time>>(newops);
206 for (
auto &op : operations) newops.push_back(op);
208 return std::make_shared<Circuit<Time>>(newops);
222 const BitMapping &bitsMap = {})
const override {
225 for (
const auto &op : operations)
226 newops.emplace_back(op->
Remap(qubitsMap, bitsMap));
228 return std::make_shared<Circuit<Time>>(newops);
245 size_t &nrCbits)
const {
253 for (
const auto &op : operations) {
254 const auto affectedBits = op->AffectedBits();
255 const auto affectedQubits = op->AffectedQubits();
257 for (
const auto qubit : affectedQubits) {
258 const auto it = newQubitsMap.find(qubit);
259 if (it == newQubitsMap.end()) {
260 newQubitsMap[qubit] = nrQubits;
265 for (
const auto bit : affectedBits) {
266 const auto it = newBitsMap.find(bit);
267 if (it == newBitsMap.end()) {
268 newBitsMap[bit] = nrCbits;
269 reverseBitsMap[nrCbits] = bit;
274 newops.emplace_back(op->Remap(newQubitsMap, newBitsMap));
277 return std::make_shared<Circuit<Time>>(newops);
296 bool ignoreNotMapped =
false,
300 if (!ignoreNotMapped && sz == 0) {
301 for (
const auto &[from, to] : bitsMap)
302 if (to > sz) sz = to;
307 for (
const auto &res : results) {
310 mappedState.Remap(bitsMap, ignoreNotMapped,
311 ignoreNotMapped ? bitsMap.size() : sz);
312 newResults[mappedState.GetAllBits()] += res.second;
328 for (
const auto &res : newResults) results[res.first] += res.second;
348 bool ignoreNotMapped =
true,
350 if (!ignoreNotMapped && sz == 0) {
351 for (
const auto &[from, to] : bitsMap)
352 if (to > sz) sz = to;
357 for (
const auto &res : newResults) {
360 mappedState.Remap(bitsMap, ignoreNotMapped,
361 ignoreNotMapped ? bitsMap.size() : sz);
362 results[mappedState.GetAllBits()] += res.second;
391 ReplaceThreeQubitAndSwapGates();
427 for (
size_t i = 0; i < operations.size(); ++i) {
428 const auto op = operations[i];
430 newops.emplace_back(op);
436 std::unordered_set<size_t> bits;
437 std::unordered_map<size_t, Types::qubit_t> measQubits;
438 std::unordered_map<size_t, Time> measDelays;
440 auto affectedBits = op->AffectedBits();
441 auto affectedQubits = op->AffectedQubits();
443 for (
size_t q = 0; q < affectedQubits.size(); ++q) {
444 bits.insert(affectedBits[q]);
445 measQubits[affectedBits[q]] = affectedQubits[q];
446 measDelays[affectedBits[q]] = op->GetDelay();
450 for (; j < operations.size(); ++j) {
451 const auto op2 = operations[j];
455 affectedQubits = op2->AffectedQubits();
458 std::static_pointer_cast<MeasurementOperation<Time>>(op2);
459 affectedBits = meas->GetBitsIndices();
460 for (
size_t q = 0; q < affectedBits.size(); ++q) {
461 bits.insert(affectedBits[q]);
462 measQubits[affectedBits[q]] = affectedQubits[q];
463 measDelays[affectedBits[q]] = op2->GetDelay();
471 for (; j < operations.size(); ++j) {
472 const auto op2 = operations[j];
477 std::static_pointer_cast<IConditionalOperation<Time>>(op2);
478 const auto condbits = condop->AffectedBits();
479 for (
const auto bit : condbits)
480 if (bits.find(bit) != bits.end()) {
482 std::vector{std::make_pair(measQubits[bit], bit)},
487 if (bits.empty())
break;
491 for (
auto bit : bits)
493 std::vector{std::make_pair(measQubits[bit], bit)},
497 operations.swap(newops);
509 for (
const auto &op : operations) {
510 const auto qbits = op->AffectedQubits();
526 size_t mn = std::numeric_limits<size_t>::max();
527 for (
const auto &op : operations) {
528 const auto qbits = op->AffectedQubits();
545 for (
const auto &op : operations) {
546 const auto cbits = op->AffectedBits();
562 size_t mn = std::numeric_limits<size_t>::max();
563 for (
const auto &op : operations) {
564 const auto cbits = op->AffectedBits();
582 std::set<size_t> qubits;
583 for (
const auto &op : operations) {
584 const auto qbits = op->AffectedQubits();
585 qubits.insert(qbits.begin(), qbits.end());
599 std::set<size_t> cbits;
600 for (
const auto &op : operations) {
601 const auto bits = op->AffectedBits();
602 cbits.insert(bits.begin(), bits.end());
618 qubitsVec.reserve(qubits.size());
620 for (
auto q : qubits) qubitsVec.emplace_back(q);
634 std::vector<size_t> bitsVec;
635 bitsVec.reserve(bits.size());
637 for (
auto b : bits) bitsVec.emplace_back(b);
652 for (
const auto &op : operations)
653 if (op->NeedsEntanglementForDistribution())
return true;
666 for (
const auto &op : operations)
667 if (op->CanAffectQuantumState())
return true;
680 std::unordered_map<size_t, OperationPtr> lastOps;
682 for (
const auto &op : operations) {
683 const auto qbits = op->AffectedQubits();
684 for (
auto q : qbits) lastOps[q] = op;
698 std::unordered_map<size_t, OperationPtr> firstOps;
700 for (
const auto &op : operations) {
701 const auto qbits = op->AffectedQubits();
702 for (
auto q : qbits) {
703 if (firstOps.find(q) == firstOps.end()) firstOps[q] = op;
721 for (
const auto &[q, op] : GetLastOps)
725 operations.emplace_back(
740 for (
const auto &[q, op] : GetFirstOps)
816 std::vector<std::shared_ptr<IOperation<Time>>> newops;
817 newops.reserve(operations.size());
819 for (
int i = 0; i < static_cast<int>(operations.size()); ++i) {
820 const std::shared_ptr<IOperation<Time>> &op = operations[i];
822 const auto type = op->GetType();
826 std::shared_ptr<IQuantumGate<Time>> gate =
827 std::static_pointer_cast<IQuantumGate<Time>>(op);
828 const auto qubits = gate->AffectedQubits();
830 if (qubits.size() == 1) {
833 auto gateType = gate->GetGateType();
834 bool replace =
false;
847 if (!optimizeRotationGates) {
848 newops.push_back(op);
893 for (
size_t j = i + 1; j < operations.size(); ++j) {
894 auto &nextOp = operations[j];
895 if (!nextOp->CanAffectQuantumState())
continue;
897 const auto nextQubits = nextOp->AffectedQubits();
898 bool hasQubit =
false;
900 for (
auto q : nextQubits)
901 if (q == qubits[0]) {
909 else if (nextQubits.size() != 1)
913 const auto nextType = nextOp->GetType();
917 const auto &nextGate =
918 std::static_pointer_cast<SingleQubitGate<Time>>(nextOp);
919 if (nextGate->GetGateType() == gateType) {
921 const auto params1 = gate->GetParams();
922 const auto params2 = nextGate->GetParams();
924 const double param = params1[0] + params2[0];
926 gate->GetDelay() + nextGate->GetDelay();
930 qubits[0], param, delay));
933 qubits[0], param, delay));
936 qubits[0], param, delay));
939 qubits[0], param, delay));
941 nextOp = std::make_shared<NoOperation<Time>>();
946 nextGate->GetGateType() ==
949 nextGate->GetGateType() ==
954 const auto delay = gate->GetDelay() + nextGate->GetDelay();
957 nextOp = std::make_shared<NoOperation<Time>>();
962 nextGate->GetGateType() ==
965 nextGate->GetGateType() ==
970 const auto delay = gate->GetDelay() + nextGate->GetDelay();
973 nextOp = std::make_shared<NoOperation<Time>>();
978 nextGate->GetGateType() ==
982 const auto delay = gate->GetDelay() + nextGate->GetDelay();
985 nextOp = std::make_shared<NoOperation<Time>>();
990 nextGate->GetGateType() ==
994 const auto delay = gate->GetDelay() + nextGate->GetDelay();
997 nextOp = std::make_shared<NoOperation<Time>>();
1002 (nextGate->GetGateType() ==
1004 nextGate->GetGateType() ==
1006 nextGate->GetGateType() ==
1008 nextGate->GetGateType() ==
1010 const auto delay = gate->GetDelay() + nextGate->GetDelay();
1013 param2 = 0.5 * M_PI;
1014 else if (nextGate->GetGateType() ==
1016 param2 = -0.5 * M_PI;
1017 else if (nextGate->GetGateType() ==
1019 param2 = 0.25 * M_PI;
1021 param2 = -0.25 * M_PI;
1023 const auto param = gate->GetParams()[0] + param2;
1025 qubits[0], param, delay));
1026 nextOp = std::make_shared<NoOperation<Time>>();
1030 }
else if (nextGate->GetGateType() ==
1036 const auto delay = gate->GetDelay() + nextGate->GetDelay();
1039 param1 = -0.5 * M_PI;
1041 param1 = 0.5 * M_PI;
1043 param1 = -0.25 * M_PI;
1045 param1 = 0.25 * M_PI;
1047 const auto param = nextGate->GetParams()[0] + param1;
1049 qubits[0], param, delay));
1050 nextOp = std::make_shared<NoOperation<Time>>();
1059 if (!found) newops.push_back(op);
1063 newops.push_back(op);
1066 }
else if (qubits.size() == 2) {
1067 auto gateType = gate->GetGateType();
1068 bool replace =
false;
1081 if (!optimizeRotationGates) {
1082 newops.push_back(op);
1113 for (
size_t j = i + 1; j < operations.size(); ++j) {
1114 auto &nextOp = operations[j];
1115 if (!nextOp->CanAffectQuantumState())
continue;
1117 const auto nextQubits = nextOp->AffectedQubits();
1119 bool hasQubit =
false;
1121 for (
auto q : nextQubits)
1122 if (q == qubits[0] || q == qubits[1]) {
1130 else if (nextQubits.size() != 2)
1135 !((qubits[0] == nextQubits[0] &&
1136 qubits[1] == nextQubits[1]) ||
1137 (qubits[0] == nextQubits[1] &&
1138 qubits[1] == nextQubits[0])))
1140 else if (!(qubits[0] == nextQubits[0] &&
1141 qubits[1] == nextQubits[1]))
1144 const auto nextType = nextOp->GetType();
1148 const auto &nextGate =
1149 std::static_pointer_cast<TwoQubitsGate<Time>>(nextOp);
1150 if (nextGate->GetGateType() == gateType) {
1152 const auto params1 = gate->GetParams();
1153 const auto params2 = nextGate->GetParams();
1154 const double param = params1[0] + params2[0];
1156 gate->GetDelay() + nextGate->GetDelay();
1160 qubits[0], qubits[1], param, delay));
1163 qubits[0], qubits[1], param, delay));
1166 qubits[0], qubits[1], param, delay));
1169 qubits[0], qubits[1], param, delay));
1171 nextOp = std::make_shared<NoOperation<Time>>();
1182 if (!found) newops.push_back(op);
1186 newops.push_back(op);
1189 }
else if (qubits.size() == 3) {
1190 auto gateType = gate->GetGateType();
1203 for (
size_t j = i + 1; j < operations.size(); ++j) {
1204 auto &nextOp = operations[j];
1205 if (!nextOp->CanAffectQuantumState())
continue;
1207 const auto nextQubits = nextOp->AffectedQubits();
1209 bool hasQubit =
false;
1211 for (
auto q : nextQubits)
1212 if (q == qubits[0] || q == qubits[1] || q == qubits[2]) {
1220 else if (nextQubits.size() != 3)
1225 (qubits[0] != nextQubits[0] ||
1226 !((qubits[1] == nextQubits[1] &&
1227 qubits[2] == nextQubits[2]) ||
1228 (qubits[1] == nextQubits[2] &&
1229 qubits[2] == nextQubits[1]))))
1232 (qubits[2] != nextQubits[2] ||
1233 !(qubits[1] == nextQubits[1] &&
1234 qubits[2] == nextQubits[2]) ||
1235 !(qubits[1] == nextQubits[2] &&
1236 qubits[2] == nextQubits[1])))
1239 const auto nextType = nextOp->GetType();
1243 const auto &nextGate =
1244 std::static_pointer_cast<ThreeQubitsGate<Time>>(nextOp);
1245 if (nextGate->GetGateType() == gateType) {
1246 nextOp = std::make_shared<NoOperation<Time>>();
1255 if (!found) newops.push_back(op);
1259 newops.push_back(op);
1263 newops.push_back(op);
1265 newops.push_back(op);
1268 operations.swap(newops);
1280 newops.reserve(operations.size());
1284 std::unordered_map<Types::qubit_t, std::vector<OperationPtr>> qubitOps;
1286 std::vector<OperationPtr> lastOps(qubitsNo);
1288 std::unordered_map<OperationPtr, std::unordered_set<OperationPtr>>
1291 for (
const auto &op : operations) {
1292 std::unordered_set<OperationPtr> dependencies;
1294 const auto cbits = op->AffectedBits();
1295 for (
auto c : cbits) {
1296 const auto lastOp = lastOps[c];
1297 if (lastOp) dependencies.insert(lastOp);
1300 const auto qubits = op->AffectedQubits();
1301 for (
auto q : qubits) {
1302 qubitOps[q].push_back(op);
1304 const auto lastOp = lastOps[q];
1305 if (lastOp) dependencies.insert(lastOp);
1310 for (
auto c : cbits) lastOps[c] = op;
1312 dependenciesMap[op] = dependencies;
1316 std::vector<Types::qubit_t> indices(qubitsNo, 0);
1318 while (!dependenciesMap.empty()) {
1323 for (
size_t q = 0; q < qubitsNo; ++q) {
1324 if (qubitOps.find(q) ==
1329 const auto &ops = qubitOps[q];
1330 const auto &op = ops[indices[q]];
1335 bool hasDependencies =
false;
1337 for (
const auto &opd : dependenciesMap[op])
1338 if (dependenciesMap.find(opd) != dependenciesMap.end()) {
1339 hasDependencies =
true;
1343 if (!hasDependencies) {
1351 dependenciesMap.erase(nextOp);
1353 const auto qubits = nextOp->AffectedQubits();
1354 for (
auto q : qubits) {
1356 if (indices[q] >= qubitOps[q].
size()) qubitOps.erase(q);
1359 newops.emplace_back(std::move(nextOp));
1365 if (qubitOps.find(q) ==
1370 const auto &ops = qubitOps[q];
1371 const auto &op = ops[indices[q]];
1373 bool hasDependencies =
false;
1375 for (
const auto &opd : dependenciesMap[op])
1376 if (dependenciesMap.find(opd) != dependenciesMap.end()) {
1377 hasDependencies =
true;
1381 if (!hasDependencies) {
1388 dependenciesMap.erase(nextOp);
1390 const auto qubits = nextOp->AffectedQubits();
1391 for (
auto q : qubits) {
1393 if (indices[q] >= qubitOps[q].
size()) qubitOps.erase(q);
1396 newops.emplace_back(std::move(nextOp));
1400 assert(newops.size() == operations.size());
1402 operations.swap(newops);
1414 std::pair<std::vector<size_t>, std::vector<Time>>
GetDepth()
const {
1419 std::vector<Time> qubitTimes(qubitsNo, 0);
1420 std::vector<size_t> qubitDepths(qubitsNo, 0);
1422 std::unordered_map<size_t, size_t> fromQubits;
1424 for (
const auto &op : operations) {
1425 const auto qbits = op->AffectedQubits();
1426 const auto delay = op->GetDelay();
1430 for (
auto q : qbits) {
1431 qubitTimes[q] += delay;
1433 if (qubitTimes[q] > maxTime) maxTime = qubitTimes[q];
1434 if (qubitDepths[q] > maxDepth) maxDepth = qubitDepths[q];
1437 const auto t = op->GetType();
1438 std::vector<size_t> condbits;
1446 condbits = op->AffectedBits();
1448 for (
auto bit : condbits) {
1449 if (fromQubits.find(bit) != fromQubits.end()) bit = fromQubits[bit];
1452 for (
auto q : qbits) {
1458 if (found || bit >= qubitsNo)
continue;
1460 qubitTimes[bit] += delay;
1462 if (qubitTimes[bit] > maxTime) maxTime = qubitTimes[bit];
1463 if (qubitDepths[bit] > maxDepth) maxDepth = qubitDepths[bit];
1467 const auto condMeas =
1468 std::static_pointer_cast<ConditionalMeasurement<Time>>(op);
1469 const auto meas = condMeas->GetOperation();
1470 const auto measQubits = meas->AffectedQubits();
1471 const auto measBits = meas->AffectedBits();
1473 for (
size_t i = 0; i < measQubits.size(); ++i) {
1474 if (i < measBits.size())
1475 fromQubits[measBits[i]] = measQubits[i];
1477 fromQubits[measQubits[i]] = measQubits[i];
1481 condbits = op->AffectedBits();
1483 for (
size_t i = 0; i < qbits.size(); ++i) {
1484 if (i < condbits.size())
1485 fromQubits[condbits[i]] = qbits[i];
1487 fromQubits[qbits[i]] = qbits[i];
1491 for (
auto q : qbits) {
1492 qubitTimes[q] = maxTime;
1493 qubitDepths[q] = maxDepth;
1496 for (
auto bit : condbits) {
1497 qubitTimes[bit] = maxTime;
1498 qubitDepths[bit] = maxDepth;
1502 return std::make_pair(qubitDepths, qubitTimes);
1515 auto [qubitDepths, qubitTimes] =
GetDepth();
1518 size_t maxDepth = 0;
1519 for (
size_t qubit = 0; qubit < qubitDepths.size(); ++qubit) {
1520 if (qubitTimes[qubit] > maxTime) maxTime = qubitTimes[qubit];
1521 if (qubitDepths[qubit] > maxDepth) maxDepth = qubitDepths[qubit];
1524 return std::make_pair(maxDepth, maxTime);
1544 if (pos >= operations.size())
return nullptr;
1546 return operations[pos];
1565 newops.reserve(operations.size());
1567 for (
const auto &op : operations) {
1568 const auto qubits = op->AffectedQubits();
1569 bool containsOutsideQubits =
false;
1570 bool containsInsideQubits =
false;
1571 for (
const auto q : qubits) {
1572 if (q < startQubit || q > endQubit) {
1573 containsOutsideQubits =
true;
1574 if (containsInsideQubits)
break;
1576 containsInsideQubits =
true;
1577 if (containsOutsideQubits)
break;
1581 if (containsInsideQubits) {
1582 if (containsOutsideQubits)
1583 throw std::runtime_error(
1584 "Cannot cut the circuit with the specified interval");
1585 newops.emplace_back(op->Clone());
1589 return std::make_shared<Circuit<Time>>(newops);
1603 std::unordered_set<Types::qubit_t> measuredQubits;
1604 std::unordered_set<Types::qubit_t> affectedQubits;
1605 std::unordered_set<Types::qubit_t> resetQubits;
1607 for (
const auto &op : operations) {
1608 const auto qubits = op->AffectedQubits();
1611 for (
const auto qbit : qubits)
1612 if (resetQubits.find(qbit) !=
1626 measuredQubits.insert(qubits.begin(), qubits.end());
1634 for (
const auto qbit : qubits) {
1639 if (affectedQubits.find(qbit) != affectedQubits.end() ||
1640 measuredQubits.find(qbit) != measuredQubits.end())
1641 resetQubits.insert(qbit);
1643 affectedQubits.insert(qbit);
1646 for (
const auto qbit : qubits) {
1647 if (measuredQubits.find(qbit) !=
1652 if (resetQubits.find(qbit) !=
1657 affectedQubits.insert(qbit);
1678 const std::shared_ptr<Simulators::ISimulator> &sim,
1680 std::vector<bool> executedOps;
1681 executedOps.reserve(operations.size());
1683 std::unordered_set<Types::qubit_t> measuredQubits;
1684 std::unordered_set<Types::qubit_t> affectedQubits;
1686 bool executionStopped =
false;
1688 for (
size_t i = 0; i < operations.size(); ++i) {
1689 auto &op = operations[i];
1690 const auto qubits = op->AffectedQubits();
1692 bool executed =
false;
1698 measuredQubits.insert(qubits.begin(), qubits.end());
1703 for (
auto qubit : qubits)
1704 if (affectedQubits.find(qubit) != affectedQubits.end()) {
1710 if (sim) op->Execute(sim, state);
1712 measuredQubits.insert(qubits.begin(), qubits.end());
1715 const auto bits = op->AffectedBits();
1728 for (
auto bit : bits)
1729 if (measuredQubits.find(bit) != measuredQubits.end()) {
1734 for (
auto qubit : qubits)
1735 if (measuredQubits.find(qubit) != measuredQubits.end()) {
1742 if (sim) op->Execute(sim, state);
1747 measuredQubits.insert(bits.begin(), bits.end());
1748 measuredQubits.insert(qubits.begin(), qubits.end());
1752 affectedQubits.insert(qubits.begin(), qubits.end());
1754 if (!executed) executionStopped =
true;
1755 if (executionStopped) executedOps.emplace_back(executed);
1776 const std::vector<bool> &executedOps)
const {
1783 const size_t dif = operations.size() - executedOps.size();
1785 for (
size_t i = dif; i < operations.size(); ++i)
1786 if (!executedOps[i - dif]) operations[i]->Execute(sim, state);
1794 const std::vector<bool> &executedOps,
bool sort =
true)
const {
1795 const size_t dif = operations.size() - executedOps.size();
1796 std::vector<std::pair<Types::qubit_t, size_t>> measurements;
1797 measurements.reserve(dif);
1799 for (
size_t i = dif; i < operations.size(); ++i)
1800 if (!executedOps[i - dif] &&
1803 std::static_pointer_cast<MeasurementOperation<Time>>(operations[i]);
1804 const auto &qubits = measOp->GetQubits();
1805 const auto &bits = measOp->GetBitsIndices();
1807 for (
size_t j = 0; j < qubits.size(); ++j)
1808 measurements.emplace_back(qubits[j], bits[j]);
1814 measurements.begin(), measurements.end(),
1815 [](
const auto &p1,
const auto &p2) { return p1.first < p2.first; });
1817 return std::make_shared<MeasurementOperation<Time>>(measurements);
1829 for (
const auto &op : operations)
1852 for (
const auto &op : operations) {
1853 const auto qubits = op->AffectedQubits();
1854 if (qubits.size() <= 1)
continue;
1856 if (qubits.size() == 2) {
1857 if (std::abs(qubits[0] - qubits[1]) != 1)
return false;
1862 for (
size_t i = 1; i < qubits.size(); ++i) {
1863 if (qubits[i] < minQubit)
1864 minQubit = qubits[i];
1865 else if (qubits[i] > maxQubit)
1866 maxQubit = qubits[i];
1869 if (maxQubit - minQubit >= qubits.size())
return false;
1884 std::unordered_map<Types::qubit_t, size_t> qubits;
1885 std::unordered_map<Types::qubit_t, Types::qubits_vector> lastQubits;
1887 for (
const auto &op : operations) {
1888 const auto q = op->AffectedQubits();
1891 if (q.size() <= 1)
continue;
1893 bool allInTheLastQubits =
true;
1895 for (
const auto qubit : q) {
1896 if (lastQubits.find(qubit) == lastQubits.end()) {
1897 allInTheLastQubits =
false;
1900 const auto &lastQ = lastQubits[qubit];
1902 for (
const auto q1 : q)
1903 if (std::find(lastQ.cbegin(), lastQ.cend(), q1) == lastQ.cend()) {
1904 allInTheLastQubits =
false;
1908 if (!allInTheLastQubits)
break;
1912 if (allInTheLastQubits)
continue;
1914 for (
const auto qubit : q) {
1915 if (qubits[qubit] > 1)
1920 lastQubits[qubit] = q;
1937 for (
const auto &op : operations)
1938 if (!op->IsClifford())
return false;
1953 size_t cliffordOps = 0;
1954 for (
const auto &op : operations)
1955 if (op->IsClifford()) ++cliffordOps;
1957 return static_cast<double>(cliffordOps) / operations.size();
1970 std::unordered_set<Types::qubit_t> cliffordQubits;
1971 std::unordered_set<Types::qubit_t> nonCliffordQubits;
1973 for (
const auto &op : operations) {
1974 const auto qubits = op->AffectedQubits();
1975 if (op->IsClifford()) {
1976 for (
const auto q : qubits) cliffordQubits.insert(q);
1978 for (
const auto q : qubits) nonCliffordQubits.insert(q);
1982 for (
const auto q : nonCliffordQubits) cliffordQubits.erase(q);
1984 return cliffordQubits;
1997 std::vector<std::shared_ptr<Circuit<Time>>> circuits;
2001 std::unordered_map<Types::qubit_t, std::unordered_set<Types::qubit_t>>
2004 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitCircuitMap;
2009 for (
auto qubit : allQubits) {
2010 circuitsMap[qubit] = std::unordered_set<Types::qubit_t>{qubit};
2011 qubitCircuitMap[qubit] = qubit;
2016 for (
const auto &op : operations) {
2017 const auto qubits = op->AffectedQubits();
2019 if (qubits.empty())
continue;
2021 auto qubitIt = qubits.cbegin();
2022 auto firstQubit = *qubitIt;
2024 auto firstQubitCircuit = qubitCircuitMap[firstQubit];
2028 for (; qubitIt != qubits.cend(); ++qubitIt) {
2029 auto qubit = *qubitIt;
2032 auto qubitCircuit = qubitCircuitMap[qubit];
2036 if (firstQubitCircuit != qubitCircuit) {
2038 circuitsMap[firstQubitCircuit].insert(
2039 circuitsMap[qubitCircuit].
begin(),
2040 circuitsMap[qubitCircuit].end());
2043 for (
auto q : circuitsMap[qubitCircuit])
2044 qubitCircuitMap[q] = firstQubitCircuit;
2047 circuitsMap.erase(qubitCircuit);
2052 size_t circSize = 1ULL;
2054 circSize = std::max(circSize,
static_cast<size_t>(circuitsMap.size()));
2055 circuits.resize(circSize);
2057 for (
size_t i = 0; i < circuits.size(); ++i)
2060 std::unordered_map<Types::qubit_t, size_t> qubitsSetsToCircuit;
2062 size_t circuitNo = 0;
2063 for (
const auto &[
id, qubitSet] : circuitsMap) {
2064 qubitsSetsToCircuit[id] = circuitNo;
2071 for (
const auto &op : operations) {
2074 if (qubits.empty()) {
2075 circuits[0]->AddOperation(op->Clone());
2079 const auto circ = qubitsSetsToCircuit[qubitCircuitMap[*qubits.cbegin()]];
2081 circuits[circ]->AddOperation(op->Clone());
2094 std::vector<std::shared_ptr<Circuits::Circuit<Time>>>
ToLayers()
const {
2095 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> layers;
2098 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitsUsed;
2106 if (op->CanAffectQuantumState()) {
2107 const auto qubits = op->AffectedQubits();
2108 size_t maxLevel = 0;
2112 maxLevel = std::max(maxLevel,
static_cast<size_t>(qubitsUsed[qbit]));
2114 if (layers.size() < qubitsUsed[qbit]) {
2115 auto circ = std::make_shared<Circuits::Circuit<Time>>();
2116 layers.push_back(std::move(circ));
2123 layers[maxLevel - 1]->AddOperation(op->Clone());
2126 layers.back()->AddOperation(op->Clone());
2142 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> layers;
2145 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitsUsed;
2153 if (op->CanAffectQuantumState()) {
2154 const auto qubits = op->AffectedQubits();
2155 size_t maxLevel = 0;
2159 maxLevel = std::max(maxLevel,
static_cast<size_t>(qubitsUsed[qbit]));
2161 if (layers.size() < qubitsUsed[qbit]) {
2162 auto circ = std::make_shared<Circuits::Circuit<Time>>();
2163 layers.push_back(std::move(circ));
2170 layers[maxLevel - 1]->AddOperation(op);
2173 layers.back()->AddOperation(op);
2191 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> layers;
2194 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitsUsed;
2202 if (op->CanAffectQuantumState()) {
2203 const auto qubits = op->AffectedQubits();
2204 size_t maxLevel = 0;
2207 if (qubits.size() > 1) ++qubitsUsed[qbit];
2208 maxLevel = std::max(maxLevel,
static_cast<size_t>(qubitsUsed[qbit]));
2210 if (layers.size() < qubitsUsed[qbit]) {
2211 auto circ = std::make_shared<Circuits::Circuit<Time>>();
2212 layers.push_back(std::move(circ));
2219 layers[maxLevel > 0 ? maxLevel - 1 : 0]->AddOperation(op->Clone());
2222 layers.back()->AddOperation(op->Clone());
2238 std::vector<std::shared_ptr<Circuits::Circuit<Time>>>
2240 std::vector<std::shared_ptr<Circuits::Circuit<Time>>> layers;
2243 std::unordered_map<Types::qubit_t, Types::qubit_t> qubitsUsed;
2251 if (op->CanAffectQuantumState()) {
2252 const auto qubits = op->AffectedQubits();
2253 size_t maxLevel = 0;
2256 if (qubits.size() > 1) ++qubitsUsed[qbit];
2257 maxLevel = std::max(maxLevel,
static_cast<size_t>(qubitsUsed[qbit]));
2259 if (layers.size() < qubitsUsed[qbit]) {
2260 auto circ = std::make_shared<Circuits::Circuit<Time>>();
2261 layers.push_back(std::move(circ));
2268 layers[maxLevel > 0 ? maxLevel - 1 : 0]->AddOperation(op);
2271 layers.back()->AddOperation(op);
2288 auto circuit{std::make_shared<Circuits::Circuit<Time>>()};
2290 for (
const auto &layer : layers)
2291 circuit->AddOperations(layer->GetOperations());
2351 return operations.crbegin();
2368 auto size()
const {
return operations.size(); }
2376 auto empty()
const {
return operations.empty(); }
2394 const auto &
operator[](
size_t pos)
const {
return operations[pos]; }
2404 if (
size < operations.size()) operations.resize(
size);
2418 void ReplaceThreeQubitAndSwapGates(
bool onlyThreeQubits =
false) {
2427 std::vector<std::shared_ptr<IOperation<Time>>> newops;
2428 newops.reserve(operations.size());
2432 std::shared_ptr<IQuantumGate<Time>> gate =
2433 std::static_pointer_cast<IQuantumGate<Time>>(op);
2435 if (NeedsConversion(gate, onlyThreeQubits)) {
2436 std::vector<std::shared_ptr<IGateOperation<Time>>> newgates =
2437 ConvertGate(gate, onlyThreeQubits);
2438 newops.insert(newops.end(), newgates.begin(), newgates.end());
2440 newops.push_back(op);
2442 std::shared_ptr<ConditionalGate<Time>> condgate =
2443 std::static_pointer_cast<ConditionalGate<Time>>(op);
2444 std::shared_ptr<IQuantumGate<Time>> gate =
2445 std::static_pointer_cast<IQuantumGate<Time>>(
2446 condgate->GetOperation());
2448 if (NeedsConversion(gate, onlyThreeQubits)) {
2449 std::vector<std::shared_ptr<IGateOperation<Time>>> newgates =
2450 ConvertGate(gate, onlyThreeQubits);
2451 std::shared_ptr<ICondition> cond = condgate->GetCondition();
2453 for (
auto gate : newgates)
2455 std::make_shared<ConditionalGate<Time>>(gate, cond));
2457 newops.push_back(op);
2459 newops.push_back(op);
2462 operations.swap(newops);
2474 static bool NeedsConversion(
const std::shared_ptr<IQuantumGate<Time>> &gate,
2475 bool onlyThreeQubits =
false) {
2476 const bool hasThreeQubits = gate->GetNumQubits() == 3;
2477 if (onlyThreeQubits)
return hasThreeQubits;
2479 return hasThreeQubits ||
2495 static std::vector<std::shared_ptr<IGateOperation<Time>>> ConvertGate(
2496 std::shared_ptr<IQuantumGate<Time>> &gate,
bool onlyThreeQubits =
false) {
2499 std::vector<std::shared_ptr<IGateOperation<Time>>> newops;
2501 if (gate->GetNumQubits() == 3) {
2504 const size_t q1 = gate->GetQubit(0);
2505 const size_t q2 = gate->GetQubit(1);
2506 const size_t q3 = gate->GetQubit(2);
2509 newops.push_back(std::make_shared<CSxGate<Time>>(q2, q3));
2510 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2511 newops.push_back(std::make_shared<CSxDagGate<Time>>(q2, q3));
2512 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2513 newops.push_back(std::make_shared<CSxGate<Time>>(q1, q3));
2515 const size_t q1 = gate->GetQubit(0);
2516 const size_t q2 = gate->GetQubit(1);
2517 const size_t q3 = gate->GetQubit(2);
2521 newops.push_back(std::make_shared<CXGate<Time>>(q3, q2));
2523 newops.push_back(std::make_shared<CSxGate<Time>>(q2, q3));
2524 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2525 newops.push_back(std::make_shared<PhaseGate<Time>>(q3, M_PI));
2527 newops.push_back(std::make_shared<PhaseGate<Time>>(q2, -M_PI_2));
2529 newops.push_back(std::make_shared<CSxGate<Time>>(q2, q3));
2530 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2531 newops.push_back(std::make_shared<PhaseGate<Time>>(q3, M_PI));
2533 newops.push_back(std::make_shared<CSxGate<Time>>(q1, q3));
2535 newops.push_back(std::make_shared<CXGate<Time>>(q3, q2));
2537 newops.push_back(gate);
2538 }
else if (!onlyThreeQubits &&
2541 const size_t q1 = gate->GetQubit(0);
2542 const size_t q2 = gate->GetQubit(1);
2546 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2547 newops.push_back(std::make_shared<CXGate<Time>>(q2, q1));
2548 newops.push_back(std::make_shared<CXGate<Time>>(q1, q2));
2550 newops.push_back(gate);
2571template <
typename Time = Types::time_type>
2579 std::vector<OperationPtr>;
2633 std::static_pointer_cast<IQuantumGate<Time>>(
2639 if (approximateParamsCheck) {
2640 const auto params1 = std::static_pointer_cast<IQuantumGate<Time>>(
2643 const auto params2 = std::static_pointer_cast<IQuantumGate<Time>>(
2646 if (params1.size() != params2.size())
return false;
2648 for (
size_t j = 0; j < params1.size(); ++j)
2649 if (std::abs(params1[j] - params2[j]) > paramsEpsilon)
2675 const auto leftCondition =
2676 std::static_pointer_cast<IConditionalOperation<Time>>(
2679 const auto rightCondition =
2680 std::static_pointer_cast<IConditionalOperation<Time>>(
2683 if (leftCondition->GetBitsIndices() !=
2684 rightCondition->GetBitsIndices())
2687 const auto leftEqCondition =
2688 std::static_pointer_cast<EqualCondition>(leftCondition);
2689 const auto rightEqCondition =
2690 std::static_pointer_cast<EqualCondition>(rightCondition);
2691 if (!leftEqCondition || !rightEqCondition)
return false;
2693 if (leftEqCondition->GetAllBits() != rightEqCondition->GetAllBits())
2698 std::static_pointer_cast<IConditionalOperation<Time>>(
2701 const auto rightOp =
2702 std::static_pointer_cast<IConditionalOperation<Time>>(
2712 if (leftCircuit != rightCircuit)
return false;
2783 bool approximateParamsCheck =
2785 double paramsEpsilon = 1e-8;
The controlled x rotation gate.
The controlled y rotation gate.
The controlled z rotation gate.
Circuit class for holding the sequence of operations.
iterator begin() noexcept
Get the begin iterator for the operations.
typename OperationsVector::const_reverse_iterator const_reverse_iterator
void ConvertForCutting()
Converts the circuit for distributed computing.
typename OperationsVector::reverse_iterator reverse_iterator
void Execute(const std::shared_ptr< Simulators::ISimulator > &sim, OperationState &state) const override
Execute the circuit on the given simulator.
typename OperationsVector::allocator_type allocator_type
std::unordered_map< Types::qubit_t, Types::qubit_t > BitMapping
The (qu)bit mapping for remapping.
const_iterator cbegin() const noexcept
Get the const begin iterator for the operations.
double CliffordPercentage() const
Get the percentage of Clifford operations in the circuit.
auto size() const
Get the number of operations in the circuit.
bool IsClifford() const override
Checks if the circuit is a Clifford circuit.
iterator end() noexcept
Get the end iterator for the operations.
std::unordered_set< Types::qubit_t > GetCliffordQubits() const
Get the qubits that are acted on by Clifford operations.
typename OperationsVector::iterator iterator
void AddOperation(const OperationPtr &op)
Adds an operation to the circuit.
std::set< size_t > GetBits() const
Returns the classical bits affected by the operations.
void Optimize(bool optimizeRotationGates=true)
Circuit optimization.
void EnsureProperOrderForMeasurements()
std::pair< size_t, Time > GetMaxDepth() const
Get max circuit depth.
std::vector< std::shared_ptr< Circuits::Circuit< Time > > > ToMultipleQubitsLayersNoClone() const
Converts the circuit to layers oriented on multiple qubit gates.
std::set< size_t > GetQubits() const
Returns the qubits affected by the operations.
std::vector< OperationPtr > OperationsVector
The vector of operations.
void AddResetsAtBeginningIfNeeded(Time delay=0)
Add resets at the beginning of the circuit.
std::vector< std::shared_ptr< Circuits::Circuit< Time > > > ToMultipleQubitsLayers() const
Converts the circuit to layers oriented on multiple qubit gates.
void Clear()
Clears the operations from the circuit.
typename OperationsVector::value_type value_type
std::shared_ptr< Operation > OperationPtr
The shared pointer to the operation type.
typename OperationsVector::reference reference
bool CanAffectQuantumState() const override
Find if the circuit can affect the quantum state.
auto & operator[](size_t pos)
Get the operation at a given position.
typename OperationsVector::difference_type difference_type
std::pair< std::vector< size_t >, std::vector< Time > > GetDepth() const
Get circuit depth.
std::unordered_map< size_t, OperationPtr > GetLastOperationsOnQubits() const
Returns the last operations on circuit's qubits.
const_reverse_iterator crend() const noexcept
Get the const reverse end iterator for the operations.
std::shared_ptr< MeasurementOperation< Time > > GetLastMeasurements(const std::vector< bool > &executedOps, bool sort=true) const
bool ActsOnlyOnAdjacentQubits() const
Checks if the circuit has only operations that act on adjacent qubits.
Circuit(const OperationsVector &ops={})
Construct a new Circuit object.
typename OperationsVector::pointer pointer
std::unordered_map< std::vector< bool >, size_t > ExecuteResults
The results of the execution of the circuit.
typename OperationsVector::size_type size_type
auto empty() const
Check if the circuit is empty.
OperationPtr Remap(const BitMapping &qubitsMap, const BitMapping &bitsMap={}) const override
Get a shared pointer to a circuit remapped.
static void AccumulateResults(ExecuteResults &results, const ExecuteResults &newResults)
Accumulate the results of a circuit execution to already existing results.
std::shared_ptr< Circuit< Time > > GetCircuitCut(Types::qubit_t startQubit, Types::qubit_t endQubit) const
Get the circuit cut.
void ConvertForDistribution()
Converts the circuit for distributed computing.
Types::qubits_vector AffectedQubits() const override
Returns the affected qubits.
bool HasOpsAfterMeasurements() const
Checks if the circuit has measurements that are followed by operations that affect the measured qubit...
IOperation< Time > Operation
The operation type.
OperationPtr CloneFlyweight() const
Get a shared pointer to a clone of this object, but without cloning the operations.
bool HasConditionalOperations() const
Checks if the circuit has clasically conditional operations.
void AddResetsIfNeeded(Time delay=0)
Add resets at the end of the circuit.
static void AccumulateResultsWithRemapBack(ExecuteResults &results, const ExecuteResults &newResults, const BitMapping &bitsMap={}, bool ignoreNotMapped=true, size_t sz=0)
Accumulate the results of a circuit execution to already existing results with remapping.
const_reverse_iterator crbegin() const noexcept
Get the const reverse begin iterator for the operations.
OperationPtr GetOperation(size_t pos) const
Get an operation at a given position.
const_iterator cend() const noexcept
Get the const end iterator for the operations.
void MoveMeasurementsAndResets()
Move the measurements and resets closer to the beginning of the circuit.
std::shared_ptr< Circuit< Time > > RemapToContinuous(BitMapping &newQubitsMap, BitMapping &reverseBitsMap, size_t &nrQubits, size_t &nrCbits) const
Get a shared pointer to a circuit remapped to a continuous interval starting from zero.
typename OperationsVector::const_iterator const_iterator
std::vector< std::shared_ptr< Circuits::Circuit< Time > > > ToLayers() const
Converts the circuit to layers.
bool NeedsEntanglementForDistribution() const override
Find if the circuit needs entanglement for distribution.
std::unordered_map< size_t, OperationPtr > GetFirstOperationsOnQubits() const
Returns the first operations on circuit's qubits.
reverse_iterator rbegin() noexcept
Get the reverse begin iterator for the operations.
void SetOperations(const OperationsVector &ops)
Set the operations in the circuit.
typename OperationsVector::const_pointer const_pointer
void AddOperations(const OperationsVector &ops)
Adds operations to the circuit.
size_t GetMaxQubitIndex() const
Returns the max qubit id for all operations.
OperationPtr Clone() const override
Get a shared pointer to a clone of this object.
size_t GetMaxCbitIndex() const
Returns the max classical bit id for all operations.
void AddCircuit(const std::shared_ptr< Circuit< Time > > &circuit)
Adds operations from another circuit to the circuit.
typename OperationsVector::const_reference const_reference
static ExecuteResults RemapResultsBack(const ExecuteResults &results, const BitMapping &bitsMap={}, bool ignoreNotMapped=false, size_t sz=0)
Map back the results for a remapped circuit.
static std::shared_ptr< Circuits::Circuit< Time > > LayersToCircuit(const std::vector< std::shared_ptr< Circuits::Circuit< Time > > > &layers)
Converts the layers back to a circuit.
const auto & operator[](size_t pos) const
Get the operation at a given position.
std::vector< std::shared_ptr< Circuits::Circuit< Time > > > ToLayersNoClone() const
Converts the circuit to layers.
size_t GetMinCbitIndex() const
Returns the min classical bit id for all operations.
size_t GetNumberOfOperations() const
Get the number of operations in the circuit.
std::vector< size_t > AffectedBits() const override
Returns the affected bits.
size_t GetMinQubitIndex() const
Returns the min qubit id for all operations.
void resize(size_t size)
Resizes the circuit.
std::vector< bool > ExecuteNonMeasurements(const std::shared_ptr< Simulators::ISimulator > &sim, OperationState &state) const
Execute the non-measurements operations from the circuit on the given simulator.
OperationType GetType() const override
Get the type of the circuit.
void ExecuteMeasurements(const std::shared_ptr< Simulators::ISimulator > &sim, OperationState &state, const std::vector< bool > &executedOps) const
Execute the measurement operations from the circuit on the given simulator.
void ReplaceOperation(size_t index, const OperationPtr &op)
Replaces an operation in the circuit.
reverse_iterator rend() noexcept
Get the reverse end iterator for the operations.
bool IsForest() const
Checks if the circuit is a forest circuit.
std::vector< std::shared_ptr< Circuit< Time > > > SplitCircuit() const
Splits a circuit that has disjoint subcircuits in it into separate circuits.
const OperationsVector & GetOperations() const
Get the operations in the circuit.
Circuit class for holding the sequence of operations that can be compared with another circuit.
std::shared_ptr< Operation > OperationPtr
The shared pointer to the operation type.
double GetParamsEpsilon() const
Gets the epsilon used for checking approximate equality of gate parameters.
std::vector< OperationPtr > OperationsVector
The vector of operations.
void SetApproximateParamsCheck(bool check)
Sets whether to check approximate equality of gate parameters.
ComparableCircuit & operator=(const BaseClass &circ)
Assignment operator.
bool operator==(const BaseClass &rhs) const
Comparison operator.
bool GetApproximateParamsCheck() const
Gets whether to check approximate equality of gate parameters.
ComparableCircuit(const BaseClass &circ)
Construct a new ComparableCircuit object.
bool operator!=(const BaseClass &rhs) const
Comparison operator.
Circuit< Time > BaseClass
The base class type.
ComparableCircuit(const OperationsVector &ops={})
Construct a new ComparableCircuit object.
void SetParamsEpsilon(double eps)
Sets the epsilon used for checking approximate equality of gate parameters.
Time GetDelay() const
Get the delay of the operation.
The interface for quantum gates.
virtual QuantumGateType GetGateType() const =0
Get the type of the quantum gate.
virtual std::vector< double > GetParams() const
Get the gate parameters.
Measurement operation class.
The state class that stores the classical state of a quantum circuit execution.
void Reset(bool value=false)
Set the classical bits with the specified value.
const std::vector< bool > & GetResetTargets() const
Get the values to reset the qubits to.
OperationType
The type of operations.
@ kConditionalGate
conditional gate, similar with gate, but conditioned on something from 'OperationState'
@ kNoOp
no operation, just a placeholder, could be used to erase some operation from a circuit
@ kComposite
a composite operation, contains other operations - should not be used in the beginning,...
@ kRandomGen
random classical bit generator, result in 'OperationState'
@ kConditionalRandomGen
conditional random generator, similar with random gen, but conditioned on something from 'OperationSt...
@ kConditionalMeasurement
conditional measurement, similar with measurement, but conditioned on something from 'OperationState'
@ kMeasurement
measurement, result in 'OperationState'
@ kGate
the usual quantum gate, result stays in simulator's state
@ kReset
reset, no result in 'state', just apply measurement, then apply not on all qubits that were measured ...
uint_fast64_t qubit_t
The type of a qubit.
std::vector< qubit_t > qubits_vector
The type of a vector of qubits.