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 17 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
1 change: 0 additions & 1 deletion docs/source/library/EquivalenceCheckingManager.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ In addition, the :class:`~.Configuration` of the manager can be altered after it
.. automethod:: EquivalenceCheckingManager.fuse_single_qubit_gates
.. automethod:: EquivalenceCheckingManager.reconstruct_swaps
.. automethod:: EquivalenceCheckingManager.reorder_operations
.. automethod:: EquivalenceCheckingManager.fix_output_permutation_mismatch

* :class:`Application Options <Configuration.Application>`
These options describe the :class:`Application Scheme <ApplicationScheme>` that is used for the individual equivalence checkers (based on decision diagrams). The scheme can either be set collectively for all checkers at once or individually.
Expand Down
1 change: 0 additions & 1 deletion include/Configuration.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ class Configuration {

// configuration options for pre-check optimizations
struct Optimizations {
bool fixOutputPermutationMismatch = false;
bool fuseSingleQubitGates = true;
bool reconstructSWAPs = true;
bool removeDiagonalGatesBeforeMeasure = false;
Expand Down
19 changes: 12 additions & 7 deletions include/EquivalenceCheckingManager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ class EquivalenceCheckingManager {
std::size_t numQubits1{};
std::size_t numQubits2{};

std::size_t numMeasuredQubits1{};
std::size_t numMeasuredQubits2{};

std::size_t numAncillae1{};
std::size_t numAncillae2{};

std::size_t numGates1{};
std::size_t numGates2{};

Expand Down Expand Up @@ -93,7 +99,7 @@ class EquivalenceCheckingManager {

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

Expand Down Expand Up @@ -139,7 +145,6 @@ class EquivalenceCheckingManager {

// Optimization: Optimizations are applied during initialization. Already
// configured and applied optimizations cannot be reverted.
void runFixOutputPermutationMismatch();
void fuseSingleQubitGates();
void reconstructSWAPs();
void reorderOperations();
Expand Down Expand Up @@ -255,17 +260,17 @@ 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.
void stripIdleQubits();

/// 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
/// it adds corresponding ancillaries in the smaller circuit
void setupAncillariesAndGarbage();

/// In some cases both circuits calculate the same function, but on different
/// qubits. This function tries to correct such mismatches. Note that this is
/// still highly experimental!
void fixOutputPermutationMismatch();

/// Run all configured optimization passes
void runOptimizationPasses();

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ filterwarnings = [
"error",
'ignore:.*encountered in det.*:RuntimeWarning:numpy.linalg:',
'ignore:.*datetime\.datetime\.utcfromtimestamp.*:DeprecationWarning:',
'ignore:.*Qiskit with Python 3.8.*:DeprecationWarning:',
]

[tool.coverage]
Expand Down
2 changes: 0 additions & 2 deletions src/Configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,6 @@ nlohmann::json Configuration::json() const {
exe["timeout"] = execution.timeout;
}
auto& opt = config["optimizations"];
opt["fix_output_permutation_mismatch"] =
optimizations.fixOutputPermutationMismatch;
opt["fuse_consecutive_single_qubit_gates"] =
optimizations.fuseSingleQubitGates;
opt["reconstruct_swaps"] = optimizations.reconstructSWAPs;
Expand Down
222 changes: 163 additions & 59 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,155 @@
#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() {
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 =
(!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 = it->first;

// 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 =
(!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 =
(!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;
}
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 @@ -70,45 +222,6 @@ void EquivalenceCheckingManager::setupAncillariesAndGarbage() {
}
}

void EquivalenceCheckingManager::fixOutputPermutationMismatch() {
// Try to fix potential mismatches in output permutations
auto& smallerCircuit = qc1.getNqubits() > qc2.getNqubits() ? qc2 : qc1;
auto& largerCircuit = qc1.getNqubits() > qc2.getNqubits() ? qc1 : qc2;

auto& smallerGarbage = smallerCircuit.garbage;

const auto& largerOutput = largerCircuit.outputPermutation;
auto& largerGarbage = largerCircuit.garbage;

for (const auto& [lPhysical, lLogical] : largerOutput) {
const qc::Qubit outputQubitInLargerCircuit = lLogical;
qc::Qubit nout = 1;
for (qc::Qubit i = 0; i < outputQubitInLargerCircuit; ++i) {
if (!largerGarbage[i]) {
++nout;
}
}

bool existsInSmaller = false;
const auto nqubits = smallerCircuit.getNqubits();
for (std::size_t i = 0U; i < nqubits; ++i) {
if (!smallerGarbage[i]) {
--nout;
}
if (nout == 0) {
existsInSmaller = true;
break;
}
}
// algorithm has logical qubit that does not exist in the smaller circuit
if (!existsInSmaller) {
continue;
}

std::cerr << "Uncorrected mismatch in output qubits!\n";
}
}

void EquivalenceCheckingManager::runOptimizationPasses() {
if (qc1.empty() && qc2.empty()) {
return;
Expand Down Expand Up @@ -181,12 +294,16 @@ void EquivalenceCheckingManager::runOptimizationPasses() {
void EquivalenceCheckingManager::run() {
done = false;

results.name1 = qc1.getName();
results.name2 = qc2.getName();
results.numQubits1 = qc1.getNqubits();
results.numQubits2 = qc2.getNqubits();
results.numGates1 = qc1.getNops();
results.numGates2 = qc2.getNops();
results.name1 = qc1.getName();
results.name2 = qc2.getName();
results.numQubits1 = qc1.getNqubits();
results.numQubits2 = qc2.getNqubits();
results.numMeasuredQubits1 = qc1.getNmeasuredQubits();
results.numMeasuredQubits2 = qc2.getNmeasuredQubits();
results.numAncillae1 = qc1.getNancillae();
results.numAncillae2 = qc2.getNancillae();
results.numGates1 = qc1.getNops();
results.numGates2 = qc2.getNops();

results.configuration = configuration;

Expand Down Expand Up @@ -252,8 +369,7 @@ EquivalenceCheckingManager::EquivalenceCheckingManager(
}

// 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 All @@ -265,11 +381,6 @@ EquivalenceCheckingManager::EquivalenceCheckingManager(
"inputs! Proceed with caution!\n";
}

// try to fix a potential mismatch in the output permutations of both circuits
if (configuration.optimizations.fixOutputPermutationMismatch) {
fixOutputPermutationMismatch();
}

// check whether the alternating checker is configured and can handle the
// circuits
if (configuration.execution.runAlternatingChecker &&
Expand Down Expand Up @@ -784,13 +895,6 @@ void EquivalenceCheckingManager::checkSymbolic() {
}
}

void EquivalenceCheckingManager::runFixOutputPermutationMismatch() {
if (!configuration.optimizations.fixOutputPermutationMismatch) {
fixOutputPermutationMismatch();
configuration.optimizations.fixOutputPermutationMismatch = true;
}
}

void EquivalenceCheckingManager::fuseSingleQubitGates() {
if (!configuration.optimizations.fuseSingleQubitGates) {
qc::CircuitOptimizer::singleQubitGateFusion(qc1);
Expand Down
Loading
Loading