Skip to content

Commit

Permalink
✨ Elide permutations optimization (#586)
Browse files Browse the repository at this point in the history
## Description

This PR introduces a new optimization that allows to elide permutations
from circuits.
Essentially, it starts with the initial layout, applies it to every gate
of the circuit.
Upon encountering a `SWAP` gate, the tracked permutation is updated and
the gate eliminated.
Finally, the output permutation of the circuit is updated.

The PR also includes some fixes for newer clang-tidy warnings as well as
a small printing fix for quantum circuits.

## Checklist:

<!---
This checklist serves as a reminder of a couple of things that ensure
your pull request will be merged swiftly.
-->

- [x] The pull request only contains commits that are related to it.
- [x] I have added appropriate tests and documentation.
- [x] I have made sure that all CI jobs on GitHub pass.
- [x] The pull request introduces no new warnings and follows the
project's style guidelines.

---------

Signed-off-by: burgholzer <[email protected]>
  • Loading branch information
burgholzer authored Apr 15, 2024
1 parent 1c225b2 commit 5649e0c
Show file tree
Hide file tree
Showing 12 changed files with 291 additions and 28 deletions.
10 changes: 10 additions & 0 deletions include/mqt-core/CircuitOptimizer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,15 @@ class CircuitOptimizer {
* @param maxBlockSize the maximum size of a block
*/
static void collectBlocks(QuantumComputation& qc, std::size_t maxBlockSize);

/**
* @brief Elide permutations by propagating them through the circuit.
* @details The circuit is traversed and any SWAP gate is eliminated by
* propagating the permutation through the circuit. The final layout of the
* circuit is updated accordingly. This pass works well together with the
* `swapReconstruction` pass.
* @param qc the quantum circuit
*/
static void elidePermutations(QuantumComputation& qc);
};
} // namespace qc
2 changes: 2 additions & 0 deletions include/mqt-core/operations/CompoundOperation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ class CompoundOperation final : public Operation {

void invert() override;

void apply(const Permutation& permutation) override;

/**
* @brief Merge another compound operation into this one.
* @details This transfers ownership of the operations from the other compound
Expand Down
6 changes: 3 additions & 3 deletions include/mqt-core/operations/Control.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ inline bool operator!=(const Control& lhs, const Control& rhs) {
struct CompareControl {
using is_transparent [[maybe_unused]] = void;

inline bool operator()(const Control& lhs, const Control& rhs) const {
bool operator()(const Control& lhs, const Control& rhs) const {
return lhs < rhs;
}

inline bool operator()(Qubit lhs, const Control& rhs) const {
bool operator()(Qubit lhs, const Control& rhs) const {
return lhs < rhs.qubit;
}

inline bool operator()(const Control& lhs, Qubit rhs) const {
bool operator()(const Control& lhs, Qubit rhs) const {
return lhs.qubit < rhs;
}
};
Expand Down
2 changes: 2 additions & 0 deletions include/mqt-core/operations/NonUnitaryOperation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ class NonUnitaryOperation final : public Operation {
void invert() override {
throw QFRException("Inverting a non-unitary operation is not supported.");
}

void apply(const Permutation& permutation) override;
};
} // namespace qc

Expand Down
36 changes: 14 additions & 22 deletions include/mqt-core/operations/Operation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@
namespace qc {
class Operation {
protected:
Controls controls{};
Targets targets{};
std::vector<fp> parameter{};
Controls controls;
Targets targets;
std::vector<fp> parameter;

OpType type = None;
std::string name{};
std::string name;

static bool isWholeQubitRegister(const RegisterNames& reg, std::size_t start,
std::size_t end) {
Expand Down Expand Up @@ -115,33 +115,25 @@ class Operation {

virtual void setParameter(const std::vector<fp>& p) { parameter = p; }

[[nodiscard]] inline virtual bool isUnitary() const { return true; }
virtual void apply(const Permutation& permutation);

[[nodiscard]] inline virtual bool isStandardOperation() const {
return false;
}
[[nodiscard]] virtual bool isUnitary() const { return true; }

[[nodiscard]] inline virtual bool isCompoundOperation() const {
return false;
}
[[nodiscard]] virtual bool isStandardOperation() const { return false; }

[[nodiscard]] inline virtual bool isNonUnitaryOperation() const {
return false;
}
[[nodiscard]] virtual bool isCompoundOperation() const { return false; }

[[nodiscard]] inline virtual bool isClassicControlledOperation() const {
return false;
}
[[nodiscard]] virtual bool isNonUnitaryOperation() const { return false; }

[[nodiscard]] inline virtual bool isSymbolicOperation() const {
[[nodiscard]] virtual bool isClassicControlledOperation() const {
return false;
}

[[nodiscard]] inline virtual bool isControlled() const {
return !controls.empty();
}
[[nodiscard]] virtual bool isSymbolicOperation() const { return false; }

[[nodiscard]] virtual bool isControlled() const { return !controls.empty(); }

[[nodiscard]] inline virtual bool actsOn(const Qubit i) const {
[[nodiscard]] virtual bool actsOn(const Qubit i) const {
for (const auto& t : targets) {
if (t == i) {
return true;
Expand Down
61 changes: 61 additions & 0 deletions src/CircuitOptimizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1752,4 +1752,65 @@ void CircuitOptimizer::collectBlocks(qc::QuantumComputation& qc,
removeIdentities(qc);
}

void elidePermutations(std::vector<std::unique_ptr<Operation>>& ops,
Permutation& permutation) {
for (auto it = ops.begin(); it != ops.end();) {
auto& op = *it;
if (auto* compOp = dynamic_cast<CompoundOperation*>(op.get())) {
elidePermutations(compOp->getOps(), permutation);
if (compOp->empty()) {
it = ops.erase(it);
continue;
}
if (compOp->isConvertibleToSingleOperation()) {
*it = compOp->collapseToSingleOperation();
} else {
// also update the tracked controls in the compound operation
compOp->getControls() = permutation.apply(compOp->getControls());
}
++it;
continue;
}

if (op->getType() == SWAP && !op->isControlled()) {
const auto& targets = op->getTargets();
assert(targets.size() == 2U);
assert(permutation.find(targets[0]) != permutation.end());
assert(permutation.find(targets[1]) != permutation.end());
auto& target0 = permutation[targets[0]];
auto& target1 = permutation[targets[1]];
std::swap(target0, target1);
it = ops.erase(it);
continue;
}

op->apply(permutation);
++it;
}
}

void CircuitOptimizer::elidePermutations(QuantumComputation& qc) {
if (qc.empty()) {
return;
}

auto permutation = qc.initialLayout;
::qc::elidePermutations(qc.ops, permutation);

// adjust the initial layout
Permutation initialLayout{};
for (auto& [physical, logical] : qc.initialLayout) {
initialLayout[logical] = logical;
}
qc.initialLayout = initialLayout;

// adjust the output permutation
Permutation outputPermutation{};
for (auto& [physical, logical] : qc.outputPermutation) {
assert(permutation.find(physical) != permutation.end());
outputPermutation[permutation[physical]] = logical;
}
qc.outputPermutation = outputPermutation;
}

} // namespace qc
3 changes: 2 additions & 1 deletion src/QuantumComputation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,8 @@ std::ostream& QuantumComputation::print(std::ostream& os) const {
size_t i = 0U;
for (const auto& op : ops) {
os << std::setw(width) << ++i << ":";
op->print(os, {}, static_cast<std::size_t>(width) + 1U, nqubits);
op->print(os, initialLayout, static_cast<std::size_t>(width) + 1U,
getNqubits());
os << "\n";
}

Expand Down
7 changes: 7 additions & 0 deletions src/operations/CompoundOperation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,13 @@ void CompoundOperation::invert() {
std::reverse(ops.begin(), ops.end());
}

void CompoundOperation::apply(const Permutation& permutation) {
Operation::apply(permutation);
for (auto& op : ops) {
op->apply(permutation);
}
}

void CompoundOperation::merge(CompoundOperation& op) {
ops.reserve(ops.size() + op.size());
ops.insert(ops.end(), std::make_move_iterator(op.begin()),
Expand Down
4 changes: 4 additions & 0 deletions src/operations/NonUnitaryOperation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,4 +192,8 @@ void NonUnitaryOperation::addDepthContribution(
depths[target] += 1;
}
}

void NonUnitaryOperation::apply(const Permutation& permutation) {
getTargets() = permutation.apply(getTargets());
}
} // namespace qc
6 changes: 6 additions & 0 deletions src/operations/Operation.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "operations/Operation.hpp"

#include <algorithm>
#include <cassert>

namespace qc {

Expand Down Expand Up @@ -175,4 +176,9 @@ void Operation::addDepthContribution(std::vector<std::size_t>& depths) const {
}
}

void Operation::apply(const Permutation& permutation) {
getTargets() = permutation.apply(getTargets());
getControls() = permutation.apply(getControls());
}

} // namespace qc
3 changes: 1 addition & 2 deletions src/parsers/QCParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,7 @@ int qc::QuantumComputation::readQCHeader(std::istream& is,
constants.at(constidx - inputs.size()) == "1") {
// add X operation in case of initial value 1
if (constants.at(constidx - inputs.size()) == "1") {
emplace_back<StandardOperation>(nqubits + nancillae,
static_cast<Qubit>(constidx), X);
x(static_cast<Qubit>(constidx));
}
varMap.insert({var, static_cast<Qubit>(constidx++)});
} else {
Expand Down
Loading

0 comments on commit 5649e0c

Please sign in to comment.