Skip to content

Commit 5649e0c

Browse files
authored
✨ Elide permutations optimization (cda-tum#586)
## 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 <burgholzer@me.com>
1 parent 1c225b2 commit 5649e0c

12 files changed

+291
-28
lines changed

include/mqt-core/CircuitOptimizer.hpp

+10
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,15 @@ class CircuitOptimizer {
8585
* @param maxBlockSize the maximum size of a block
8686
*/
8787
static void collectBlocks(QuantumComputation& qc, std::size_t maxBlockSize);
88+
89+
/**
90+
* @brief Elide permutations by propagating them through the circuit.
91+
* @details The circuit is traversed and any SWAP gate is eliminated by
92+
* propagating the permutation through the circuit. The final layout of the
93+
* circuit is updated accordingly. This pass works well together with the
94+
* `swapReconstruction` pass.
95+
* @param qc the quantum circuit
96+
*/
97+
static void elidePermutations(QuantumComputation& qc);
8898
};
8999
} // namespace qc

include/mqt-core/operations/CompoundOperation.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ class CompoundOperation final : public Operation {
6363

6464
void invert() override;
6565

66+
void apply(const Permutation& permutation) override;
67+
6668
/**
6769
* @brief Merge another compound operation into this one.
6870
* @details This transfers ownership of the operations from the other compound

include/mqt-core/operations/Control.hpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,15 @@ inline bool operator!=(const Control& lhs, const Control& rhs) {
4646
struct CompareControl {
4747
using is_transparent [[maybe_unused]] = void;
4848

49-
inline bool operator()(const Control& lhs, const Control& rhs) const {
49+
bool operator()(const Control& lhs, const Control& rhs) const {
5050
return lhs < rhs;
5151
}
5252

53-
inline bool operator()(Qubit lhs, const Control& rhs) const {
53+
bool operator()(Qubit lhs, const Control& rhs) const {
5454
return lhs < rhs.qubit;
5555
}
5656

57-
inline bool operator()(const Control& lhs, Qubit rhs) const {
57+
bool operator()(const Control& lhs, Qubit rhs) const {
5858
return lhs.qubit < rhs;
5959
}
6060
};

include/mqt-core/operations/NonUnitaryOperation.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ class NonUnitaryOperation final : public Operation {
8484
void invert() override {
8585
throw QFRException("Inverting a non-unitary operation is not supported.");
8686
}
87+
88+
void apply(const Permutation& permutation) override;
8789
};
8890
} // namespace qc
8991

include/mqt-core/operations/Operation.hpp

+14-22
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@
1717
namespace qc {
1818
class Operation {
1919
protected:
20-
Controls controls{};
21-
Targets targets{};
22-
std::vector<fp> parameter{};
20+
Controls controls;
21+
Targets targets;
22+
std::vector<fp> parameter;
2323

2424
OpType type = None;
25-
std::string name{};
25+
std::string name;
2626

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

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

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

120-
[[nodiscard]] inline virtual bool isStandardOperation() const {
121-
return false;
122-
}
120+
[[nodiscard]] virtual bool isUnitary() const { return true; }
123121

124-
[[nodiscard]] inline virtual bool isCompoundOperation() const {
125-
return false;
126-
}
122+
[[nodiscard]] virtual bool isStandardOperation() const { return false; }
127123

128-
[[nodiscard]] inline virtual bool isNonUnitaryOperation() const {
129-
return false;
130-
}
124+
[[nodiscard]] virtual bool isCompoundOperation() const { return false; }
131125

132-
[[nodiscard]] inline virtual bool isClassicControlledOperation() const {
133-
return false;
134-
}
126+
[[nodiscard]] virtual bool isNonUnitaryOperation() const { return false; }
135127

136-
[[nodiscard]] inline virtual bool isSymbolicOperation() const {
128+
[[nodiscard]] virtual bool isClassicControlledOperation() const {
137129
return false;
138130
}
139131

140-
[[nodiscard]] inline virtual bool isControlled() const {
141-
return !controls.empty();
142-
}
132+
[[nodiscard]] virtual bool isSymbolicOperation() const { return false; }
133+
134+
[[nodiscard]] virtual bool isControlled() const { return !controls.empty(); }
143135

144-
[[nodiscard]] inline virtual bool actsOn(const Qubit i) const {
136+
[[nodiscard]] virtual bool actsOn(const Qubit i) const {
145137
for (const auto& t : targets) {
146138
if (t == i) {
147139
return true;

src/CircuitOptimizer.cpp

+61
Original file line numberDiff line numberDiff line change
@@ -1752,4 +1752,65 @@ void CircuitOptimizer::collectBlocks(qc::QuantumComputation& qc,
17521752
removeIdentities(qc);
17531753
}
17541754

1755+
void elidePermutations(std::vector<std::unique_ptr<Operation>>& ops,
1756+
Permutation& permutation) {
1757+
for (auto it = ops.begin(); it != ops.end();) {
1758+
auto& op = *it;
1759+
if (auto* compOp = dynamic_cast<CompoundOperation*>(op.get())) {
1760+
elidePermutations(compOp->getOps(), permutation);
1761+
if (compOp->empty()) {
1762+
it = ops.erase(it);
1763+
continue;
1764+
}
1765+
if (compOp->isConvertibleToSingleOperation()) {
1766+
*it = compOp->collapseToSingleOperation();
1767+
} else {
1768+
// also update the tracked controls in the compound operation
1769+
compOp->getControls() = permutation.apply(compOp->getControls());
1770+
}
1771+
++it;
1772+
continue;
1773+
}
1774+
1775+
if (op->getType() == SWAP && !op->isControlled()) {
1776+
const auto& targets = op->getTargets();
1777+
assert(targets.size() == 2U);
1778+
assert(permutation.find(targets[0]) != permutation.end());
1779+
assert(permutation.find(targets[1]) != permutation.end());
1780+
auto& target0 = permutation[targets[0]];
1781+
auto& target1 = permutation[targets[1]];
1782+
std::swap(target0, target1);
1783+
it = ops.erase(it);
1784+
continue;
1785+
}
1786+
1787+
op->apply(permutation);
1788+
++it;
1789+
}
1790+
}
1791+
1792+
void CircuitOptimizer::elidePermutations(QuantumComputation& qc) {
1793+
if (qc.empty()) {
1794+
return;
1795+
}
1796+
1797+
auto permutation = qc.initialLayout;
1798+
::qc::elidePermutations(qc.ops, permutation);
1799+
1800+
// adjust the initial layout
1801+
Permutation initialLayout{};
1802+
for (auto& [physical, logical] : qc.initialLayout) {
1803+
initialLayout[logical] = logical;
1804+
}
1805+
qc.initialLayout = initialLayout;
1806+
1807+
// adjust the output permutation
1808+
Permutation outputPermutation{};
1809+
for (auto& [physical, logical] : qc.outputPermutation) {
1810+
assert(permutation.find(physical) != permutation.end());
1811+
outputPermutation[permutation[physical]] = logical;
1812+
}
1813+
qc.outputPermutation = outputPermutation;
1814+
}
1815+
17551816
} // namespace qc

src/QuantumComputation.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,8 @@ std::ostream& QuantumComputation::print(std::ostream& os) const {
502502
size_t i = 0U;
503503
for (const auto& op : ops) {
504504
os << std::setw(width) << ++i << ":";
505-
op->print(os, {}, static_cast<std::size_t>(width) + 1U, nqubits);
505+
op->print(os, initialLayout, static_cast<std::size_t>(width) + 1U,
506+
getNqubits());
506507
os << "\n";
507508
}
508509

src/operations/CompoundOperation.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,13 @@ void CompoundOperation::invert() {
160160
std::reverse(ops.begin(), ops.end());
161161
}
162162

163+
void CompoundOperation::apply(const Permutation& permutation) {
164+
Operation::apply(permutation);
165+
for (auto& op : ops) {
166+
op->apply(permutation);
167+
}
168+
}
169+
163170
void CompoundOperation::merge(CompoundOperation& op) {
164171
ops.reserve(ops.size() + op.size());
165172
ops.insert(ops.end(), std::make_move_iterator(op.begin()),

src/operations/NonUnitaryOperation.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -192,4 +192,8 @@ void NonUnitaryOperation::addDepthContribution(
192192
depths[target] += 1;
193193
}
194194
}
195+
196+
void NonUnitaryOperation::apply(const Permutation& permutation) {
197+
getTargets() = permutation.apply(getTargets());
198+
}
195199
} // namespace qc

src/operations/Operation.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "operations/Operation.hpp"
22

33
#include <algorithm>
4+
#include <cassert>
45

56
namespace qc {
67

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

179+
void Operation::apply(const Permutation& permutation) {
180+
getTargets() = permutation.apply(getTargets());
181+
getControls() = permutation.apply(getControls());
182+
}
183+
178184
} // namespace qc

src/parsers/QCParser.cpp

+1-2
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,7 @@ int qc::QuantumComputation::readQCHeader(std::istream& is,
136136
constants.at(constidx - inputs.size()) == "1") {
137137
// add X operation in case of initial value 1
138138
if (constants.at(constidx - inputs.size()) == "1") {
139-
emplace_back<StandardOperation>(nqubits + nancillae,
140-
static_cast<Qubit>(constidx), X);
139+
x(static_cast<Qubit>(constidx));
141140
}
142141
varMap.insert({var, static_cast<Qubit>(constidx++)});
143142
} else {

0 commit comments

Comments
 (0)