Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 Fix strip idle qubits #394

Merged
merged 19 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
7518d2a
:bug: Strip idle qubit if it either is idle in both circuits or idle …
TeWas May 2, 2024
3ea8267
:white_check_mark: Add tests
TeWas May 2, 2024
d27d722
:sparkles: :white_check_mark: Consider case that idle qubits appear i…
TeWas May 8, 2024
402f153
Merge branch 'main' into fix-strip-idle-qubit
TeWas May 8, 2024
47e178a
:art: Improve readability
TeWas May 8, 2024
fdf2e1d
♻️ make `decrementLogicalQubitsInLayoutAboveIndex` an internal functi…
burgholzer May 19, 2024
5711388
🚨 fix a compiler warning thrown by gcc-13
burgholzer May 19, 2024
9a4d1b6
♻️ refactor idle qubit removal logic and generalize it a little
burgholzer May 19, 2024
defb6b2
🐛 skip garbage qubits in ZX Checker permutation postprocessing check
burgholzer May 19, 2024
fea5f61
🐛 properly complete permutations in ZX Checker permutation handling
burgholzer May 19, 2024
8f9259f
🚨 ignore new Qiskit deprecation warning for Python 3.8 support
burgholzer May 19, 2024
12ce4c2
🚨 eliminate unused variable warning
burgholzer May 19, 2024
26c1ada
🔥 remove `fixOutputPermutation` option that did not actually fix anyt…
burgholzer May 19, 2024
cc03ffb
🍱 update gate profiles for Qiskit 1.1
burgholzer May 19, 2024
0d7d805
🩹 properly seed RNG for parameters of multi-controlled gates
burgholzer May 19, 2024
6686b84
:fire: Remove redundant function parameters
TeWas May 21, 2024
f52abd0
:sparkles: Augment Results class to contain information on the number…
TeWas May 21, 2024
5d828f9
♻️ add a comment header to gate cost profiles
burgholzer May 23, 2024
cd0898e
♻️ refactor logic for running profile checks
burgholzer May 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion include/EquivalenceCheckingManager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ class EquivalenceCheckingManager {

void reset() {
stateGenerator.clear();
results = Results{};
results = {};
checkers.clear();
}

Expand All @@ -103,6 +103,10 @@ class EquivalenceCheckingManager {
[[nodiscard]] Configuration getConfiguration() const { return configuration; }
[[nodiscard]] Results getResults() const { return results; }

// Getter functions provided for testing purposes
std::size_t getNumAncillae1() { return qc1.getNancillae(); }
std::size_t getNumAncillae2() { return qc2.getNancillae(); }
burgholzer marked this conversation as resolved.
Show resolved Hide resolved

// convenience functions for changing the configuration after the manager has
// been constructed: Execution: These settings may be changed to influence
// what is executed during `run`
Expand Down Expand Up @@ -255,6 +259,13 @@ class EquivalenceCheckingManager {

Results results{};

/// Strip away qubits with no operations applied to them and which do not
/// occur in the output permutation if they are either idle in both circuits
/// or idle in one and do not exist (logically) in the other circuit.
/// \param force if true, also strip away idle
/// qubits occurring in the output permutation
void stripIdleQubits(bool force = false, bool reduceIOpermutations = true);

TeWas marked this conversation as resolved.
Show resolved Hide resolved
/// Given that one circuit has more qubits than the other, the difference is
/// assumed to arise from ancillary qubits. This function changes the
/// additional qubits in the larger circuit to ancillary qubits. Furthermore
Expand Down
161 changes: 159 additions & 2 deletions src/EquivalenceCheckingManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@
#include "CircuitOptimizer.hpp"
#include "Definitions.hpp"
#include "EquivalenceCriterion.hpp"
#include "Permutation.hpp"
#include "QuantumComputation.hpp"
#include "checker/dd/DDAlternatingChecker.hpp"
#include "checker/dd/DDConstructionChecker.hpp"
#include "checker/dd/DDSimulationChecker.hpp"
#include "checker/dd/simulation/StateType.hpp"
#include "checker/zx/ZXChecker.hpp"
#include "zx/FunctionalityConstruction.hpp"

#include <algorithm>
#include <cassert>
#include <chrono>
#include <cstddef>
Expand All @@ -28,6 +31,161 @@
#include <vector>

namespace ec {

// Decrement logical qubit indices in the layout that exceed logicalQubitIndex
void decrementLogicalQubitsInLayoutAboveIndex(
qc::Permutation& layout, const qc::Qubit logicalQubitIndex) {
for (auto& [physical, logical] : layout) {
if (logical > logicalQubitIndex) {
--logical;
}
}
}

void EquivalenceCheckingManager::stripIdleQubits(bool force,
bool reduceIOpermutations) {
auto& largerCircuit = qc1.getNqubits() > qc2.getNqubits() ? qc1 : qc2;
auto& smallerCircuit = qc1.getNqubits() > qc2.getNqubits() ? qc2 : qc1;
auto qubitDifference =
largerCircuit.getNqubits() - smallerCircuit.getNqubits();
auto largerCircuitLayoutCopy = largerCircuit.initialLayout;

// Iterate over the initialLayout of largerCircuit and remove an idle logical
// qubit together with the physical qubit it is mapped to
for (auto physicalQubitIt = largerCircuitLayoutCopy.rbegin();
physicalQubitIt != largerCircuitLayoutCopy.rend(); ++physicalQubitIt) {
const auto physicalQubitIndex = physicalQubitIt->first;

if (!largerCircuit.isIdleQubit(physicalQubitIndex)) {
continue;
}

const auto logicalQubitIndex =
largerCircuit.initialLayout.at(physicalQubitIndex);

bool removedFromSmallerCircuit = false;

// Remove idle logical qubit present exclusively in largerCircuit
if (qubitDifference > 0 &&
((smallerCircuit.getNqubits() == 0) ||
logicalQubitIndex >
qc::QuantumComputation::getHighestLogicalQubitIndex(
smallerCircuit.initialLayout))) {
const bool physicalUsedInOutputPermutation =
largerCircuit.outputPermutation.find(physicalQubitIndex) !=
largerCircuit.outputPermutation.end();
const bool logicalUsedInOutputPermutation =
std::any_of(largerCircuit.outputPermutation.begin(),
largerCircuit.outputPermutation.end(),
[logicalQubitIndex](const auto& pair) {
return pair.second == logicalQubitIndex;
});

// a qubit can only be removed if it is not used in the output permutation
// or if it is used in the output permutation and the logical qubit index
// matches the logical qubit index in the output permutation for the
// physical qubit index in question, which indicates that nothing has
// happened to the qubit in the larger circuit.
const bool safeToRemoveInLargerCircuit =
force ||
(!physicalUsedInOutputPermutation &&
!logicalUsedInOutputPermutation) ||
(physicalUsedInOutputPermutation &&
largerCircuit.outputPermutation.at(physicalQubitIndex) ==
logicalQubitIndex);
if (!safeToRemoveInLargerCircuit) {
continue;
}
largerCircuit.removeQubit(logicalQubitIndex);
--qubitDifference;

} else {
// Remove logical qubit that is idle in both circuits

// find the corresponding logical qubit in the smaller circuit
const auto it = std::find_if(smallerCircuit.initialLayout.begin(),
smallerCircuit.initialLayout.end(),
[logicalQubitIndex](const auto& pair) {
return pair.second == logicalQubitIndex;
});
// the logical qubit has to be present in the smaller circuit, otherwise
// this would indicate a bug in the circuit IO initialization.
assert(it != smallerCircuit.initialLayout.end());
const auto& [physicalSmaller, logicalSmaller] = *it;
Fixed Show fixed Hide fixed

// if the qubit is not idle in the second circuit, it cannot be removed
// from either circuit.
if (!smallerCircuit.isIdleQubit(physicalSmaller)) {
continue;
}

const bool physicalLargerUsedInOutputPermutation =
(largerCircuit.outputPermutation.find(physicalQubitIndex) !=
largerCircuit.outputPermutation.end());

const bool logicalLargerUsedInOutputPermutation =
std::any_of(largerCircuit.outputPermutation.begin(),
largerCircuit.outputPermutation.end(),
[logicalQubitIndex](const auto& pair) {
return pair.second == logicalQubitIndex;
});

const bool safeToRemoveInLargerCircuit =
force ||
(!physicalLargerUsedInOutputPermutation &&
!logicalLargerUsedInOutputPermutation) ||
(physicalLargerUsedInOutputPermutation &&
largerCircuit.outputPermutation.at(physicalQubitIndex) ==
logicalQubitIndex);
if (!safeToRemoveInLargerCircuit) {
continue;
}

const bool physicalSmallerUsedInOutputPermutation =
(smallerCircuit.outputPermutation.find(physicalSmaller) !=
smallerCircuit.outputPermutation.end());

const bool logicalSmallerUsedInOutputPermutation =
std::any_of(smallerCircuit.outputPermutation.begin(),
smallerCircuit.outputPermutation.end(),
[logicalQubitIndex](const auto& pair) {
return pair.second == logicalQubitIndex;
});

const bool safeToRemoveInSmallerCircuit =
force ||
(!physicalSmallerUsedInOutputPermutation &&
!logicalSmallerUsedInOutputPermutation) ||
(physicalSmallerUsedInOutputPermutation &&
smallerCircuit.outputPermutation.at(physicalSmaller) ==
logicalQubitIndex);
if (!safeToRemoveInSmallerCircuit) {
continue;
}

// only remove the qubit from both circuits if it is safe to do so in both
// circuits (i.e., the qubit is not used in the output permutation or if
// it is used, the logical qubit index matches the logical qubit index in
// the output permutation for the physical qubit index in question).
largerCircuit.removeQubit(logicalQubitIndex);
smallerCircuit.removeQubit(logicalQubitIndex);
removedFromSmallerCircuit = true;
}
if (reduceIOpermutations) {
decrementLogicalQubitsInLayoutAboveIndex(largerCircuit.initialLayout,
logicalQubitIndex);
decrementLogicalQubitsInLayoutAboveIndex(largerCircuit.outputPermutation,
logicalQubitIndex);
if (removedFromSmallerCircuit) {
decrementLogicalQubitsInLayoutAboveIndex(smallerCircuit.initialLayout,
logicalQubitIndex);
decrementLogicalQubitsInLayoutAboveIndex(
smallerCircuit.outputPermutation, logicalQubitIndex);
}
}
}
}

void EquivalenceCheckingManager::setupAncillariesAndGarbage() {
auto& largerCircuit =
qc1.getNqubits() > qc2.getNqubits() ? this->qc1 : this->qc2;
Expand Down Expand Up @@ -252,8 +410,7 @@
}

// strip away qubits that are not acted upon
this->qc1.stripIdleQubits();
this->qc2.stripIdleQubits();
stripIdleQubits();

// given that one circuit has more qubits than the other, the difference is
// assumed to arise from ancillary qubits. adjust both circuits accordingly
Expand Down
26 changes: 20 additions & 6 deletions src/checker/zx/ZXChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@
#include "zx/Simplify.hpp"
#include "zx/ZXDiagram.hpp"

#include <cassert>
#include <chrono>
#include <cstddef>
#include <optional>
#include <unordered_map>
#include <vector>

namespace ec {
ZXEquivalenceChecker::ZXEquivalenceChecker(const qc::QuantumComputation& circ1,
Expand Down Expand Up @@ -57,6 +61,11 @@ EquivalenceCriterion ZXEquivalenceChecker::run() {

const auto nQubits = miter.getNQubits();
for (std::size_t i = 0U; i < nQubits; ++i) {
if (qc1->logicalQubitIsGarbage(static_cast<qc::Qubit>(i)) &&
qc2->logicalQubitIsGarbage(static_cast<qc::Qubit>(i))) {
continue;
}

const auto& in = miter.getInput(i);
const auto& edge = miter.incidentEdge(in, 0U);

Expand Down Expand Up @@ -121,9 +130,13 @@ qc::Permutation concat(const qc::Permutation& p1,
}

qc::Permutation complete(const qc::Permutation& p, const std::size_t n) {
if (p.size() == n) {
return p;
}

qc::Permutation pComp = p;

std::unordered_map<std::size_t, bool> mappedTo;
std::vector<bool> mappedTo(n, false);
std::unordered_map<std::size_t, bool> mappedFrom;
for (const auto [k, v] : p) {
mappedFrom[k] = true;
Expand All @@ -132,15 +145,16 @@ qc::Permutation complete(const qc::Permutation& p, const std::size_t n) {

// Map qubits greedily
for (std::size_t i = 0; i < n; ++i) {
if (mappedFrom[i]) {
// If qubit is already mapped, skip
if (mappedTo[i]) {
continue;
}

for (std::size_t j = 0; j < n; ++j) {
if (!mappedTo[j]) {
pComp[static_cast<qc::Qubit>(i)] = static_cast<qc::Qubit>(j);
mappedTo[j] = true;
mappedFrom[i] = true;
if (mappedFrom.find(j) == mappedFrom.end()) {
pComp[static_cast<qc::Qubit>(j)] = static_cast<qc::Qubit>(i);
mappedTo[i] = true;
mappedFrom[j] = true;
break;
}
}
Expand Down
Loading
Loading