Skip to content

Commit

Permalink
♻️🐛 Refactor qc::Permutation and fix corner case in Qiskit layout i…
Browse files Browse the repository at this point in the history
…mport (#858)

## Description

This PR refactors the `qc::Permutation` class to not subclass from
`std::map`. This has caused more harm than good, especially in the
Python bindings, where pybind automatically binds some of the `std::map`
members and then won't accept them being called on a `Permutation`
object.
This creates quite a bit of boilerplate code for exposing the `map`
interface in the permutation class, but has no other major influence
(except that the Python bindings now work properly).

Additionally, this fixes an error related to the layout import from
Qiskit related to Qiskit/qiskit#12749. It
turns out that calling the public API of the `TranspileLayout` may throw
errors if the `final_layout` is `None`.

Last, but not least, this PR adds a warnings ignore filter for a
nonsense GCC warning, which should make the build log warning free
again.

## Checklist:

- [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
  • Loading branch information
burgholzer authored Mar 10, 2025
2 parents a927ced + 1fecc96 commit 5e94260
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 36 deletions.
165 changes: 164 additions & 1 deletion include/mqt-core/ir/Permutation.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,179 @@

#include <cstddef>
#include <functional>
#include <initializer_list>
#include <map>
#include <utility>

namespace qc {
class Permutation : public std::map<Qubit, Qubit> {
class Permutation {
std::map<Qubit, Qubit> permutation;

public:
[[nodiscard]] auto apply(const Controls& controls) const -> Controls;
[[nodiscard]] auto apply(const Targets& targets) const -> Targets;
[[nodiscard]] auto apply(Qubit qubit) const -> Qubit;
[[nodiscard]] auto maxKey() const -> Qubit;
[[nodiscard]] auto maxValue() const -> Qubit;

/// Constructors
Permutation() = default;
template <class InputIt>
Permutation(InputIt first, InputIt last) : permutation(first, last) {}
Permutation(const std::initializer_list<std::pair<const Qubit, Qubit>> init)
: permutation(init) {}

/// Returns an iterator to the beginning
[[nodiscard]] auto begin() noexcept -> decltype(permutation)::iterator {
return permutation.begin();
}
[[nodiscard]] auto begin() const noexcept
-> decltype(permutation)::const_iterator {
return permutation.begin();
}
[[nodiscard]] auto cbegin() const noexcept -> auto {
return permutation.cbegin();
}

/// Returns an iterator to the end
[[nodiscard]] auto end() noexcept -> decltype(permutation)::iterator {
return permutation.end();
}
[[nodiscard]] auto end() const noexcept
-> decltype(permutation)::const_iterator {
return permutation.end();
}
[[nodiscard]] auto cend() const noexcept -> auto {
return permutation.cend();
}

/// Returns a reverse iterator to the beginning
[[nodiscard]] auto rbegin() noexcept
-> decltype(permutation)::reverse_iterator {
return permutation.rbegin();
}
[[nodiscard]] auto rbegin() const noexcept
-> decltype(permutation)::const_reverse_iterator {
return permutation.rbegin();
}
[[nodiscard]] auto crbegin() const noexcept -> auto {
return permutation.crbegin();
}

/// Returns a reverse iterator to the end
[[nodiscard]] auto rend() noexcept
-> decltype(permutation)::reverse_iterator {
return permutation.rend();
}
[[nodiscard]] auto rend() const noexcept
-> decltype(permutation)::const_reverse_iterator {
return permutation.rend();
}
[[nodiscard]] auto crend() const noexcept -> auto {
return permutation.crend();
}

/// Checks whether the permutation is empty
[[nodiscard]] auto empty() const -> bool { return permutation.empty(); }

/// Returns the number of elements
[[nodiscard]] auto size() const -> std::size_t { return permutation.size(); }

/// Clears the permutation
void clear() { permutation.clear(); }

/// Finds element with specific key
[[nodiscard]] auto find(const Qubit qubit)
-> decltype(permutation.find(qubit)) {
return permutation.find(qubit);
}
[[nodiscard]] auto find(const Qubit qubit) const
-> decltype(permutation.find(qubit)) {
return permutation.find(qubit);
}

/// Returns the number of elements with specific key
[[nodiscard]] auto count(const Qubit qubit) const -> std::size_t {
return permutation.count(qubit);
}

/// Access specified element with bounds checking
[[nodiscard]] auto at(const Qubit qubit) const -> Qubit {
return permutation.at(qubit);
}

/// Access specified element with bounds checking
[[nodiscard]] auto at(const Qubit qubit) -> Qubit& {
return permutation.at(qubit);
}

/// Access or insert specified element
[[nodiscard]] auto operator[](const Qubit qubit) -> Qubit& {
return permutation[qubit];
}

/// Inserts elements or nodes
auto insert(const std::pair<const Qubit, Qubit>& value) -> auto {
return permutation.insert(value);
}
template <class InputIt> auto insert(InputIt first, InputIt last) -> void {
permutation.insert(first, last);
}
auto insert(const std::initializer_list<std::pair<const Qubit, Qubit>> init)
-> void {
permutation.insert(init);
}

/// Constructs element in-place
template <class... Args> auto emplace(Args&&... args) -> auto {
return permutation.emplace(std::forward<Args>(args)...);
}

/// Inserts in-place if the key does not exist, does nothing otherwise
template <class... Args>
// NOLINTNEXTLINE(readability-identifier-naming)
auto try_emplace(const Qubit key, Args&&... args) -> auto {
return permutation.try_emplace(key, std::forward<Args>(args)...);
}

/// Erases elements
auto erase(const Qubit qubit) -> std::size_t {
return permutation.erase(qubit);
}
auto erase(const decltype(permutation)::const_iterator pos)
-> decltype(permutation)::iterator {
return permutation.erase(pos);
}
auto erase(const decltype(permutation)::const_iterator first,
const decltype(permutation)::const_iterator last)
-> decltype(permutation)::iterator {
return permutation.erase(first, last);
}

/// Swaps the contents
void swap(Permutation& other) noexcept {
permutation.swap(other.permutation);
}

/// Lexicographically compares the values in the map
[[nodiscard]] auto operator<(const Permutation& other) const -> bool {
return permutation < other.permutation;
}
[[nodiscard]] auto operator<=(const Permutation& other) const -> bool {
return permutation <= other.permutation;
}
[[nodiscard]] auto operator>(const Permutation& other) const -> bool {
return permutation > other.permutation;
}
[[nodiscard]] auto operator>=(const Permutation& other) const -> bool {
return permutation >= other.permutation;
}
[[nodiscard]] auto operator==(const Permutation& other) const -> bool {
return permutation == other.permutation;
}
[[nodiscard]] auto operator!=(const Permutation& other) const -> bool {
return permutation != other.permutation;
}
};
} // namespace qc

Expand Down
41 changes: 13 additions & 28 deletions src/ir/QuantumComputation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -247,14 +247,6 @@ std::size_t QuantumComputation::getDepth() const {
}

void QuantumComputation::initializeIOMapping() {
// if no initial layout was found during parsing the identity mapping is
// assumed
if (initialLayout.empty()) {
for (Qubit i = 0; i < nqubits; ++i) {
initialLayout.emplace(i, i);
}
}

// try gathering (additional) output permutation information from
// measurements, e.g., a measurement
// `measure q[i] -> c[j];`
Expand Down Expand Up @@ -286,8 +278,8 @@ void QuantumComputation::initializeIOMapping() {
if (outputPermutationFound) {
// output permutation was already set before -> permute existing
// values
const auto current = outputPermutation.at(qubitidx);
if (static_cast<std::size_t>(current) != bitidx) {
if (const auto current = outputPermutation.at(qubitidx);
static_cast<std::size_t>(current) != bitidx) {
for (auto& p : outputPermutation) {
if (static_cast<std::size_t>(p.second) == bitidx) {
p.second = current;
Expand Down Expand Up @@ -319,16 +311,8 @@ void QuantumComputation::initializeIOMapping() {
}
}

const bool buildOutputPermutation = outputPermutation.empty();
garbage.assign(nqubits + nancillae, false);
for (const auto& [physicalIn, logicalIn] : initialLayout) {
const bool isIdle = isIdleQubit(physicalIn);

// if no output permutation was found, build it from the initial layout
if (buildOutputPermutation && !isIdle) {
outputPermutation.insert({physicalIn, logicalIn});
}

// if the qubit is not an output, mark it as garbage
const bool isOutput = std::any_of(
outputPermutation.begin(), outputPermutation.end(),
Expand All @@ -338,7 +322,8 @@ void QuantumComputation::initializeIOMapping() {
}

// if the qubit is an ancillary and idle, mark it as garbage
if (logicalQubitIsAncillary(logicalIn) && isIdle) {
if (const bool isIdle = isIdleQubit(physicalIn);
logicalQubitIsAncillary(logicalIn) && isIdle) {
setLogicalQubitGarbage(logicalIn);
}
}
Expand Down Expand Up @@ -367,8 +352,8 @@ QuantumComputation::addQubitRegister(std::size_t nq,
regName);
for (std::size_t i = 0; i < nq; ++i) {
auto j = static_cast<Qubit>(nqubits + i);
initialLayout.insert({j, j});
outputPermutation.insert({j, j});
initialLayout.emplace(j, j);
outputPermutation.emplace(j, j);
}
nqubits += nq;
ancillary.resize(nqubits + nancillae);
Expand Down Expand Up @@ -414,8 +399,8 @@ QuantumComputation::addAncillaryRegister(std::size_t nq,
garbage.resize(totalqubits + nq);
for (std::size_t i = 0; i < nq; ++i) {
auto j = static_cast<Qubit>(totalqubits + i);
initialLayout.insert({j, j});
outputPermutation.insert({j, j});
initialLayout.emplace(j, j);
outputPermutation.emplace(j, j);
ancillary[j] = true;
}
nancillae += nq;
Expand Down Expand Up @@ -495,12 +480,12 @@ void QuantumComputation::addAncillaryQubit(
ancillary[logicalQubitIndex] = true;

// adjust initial layout
initialLayout.insert(
{physicalQubitIndex, static_cast<Qubit>(logicalQubitIndex)});
initialLayout.emplace(physicalQubitIndex,
static_cast<Qubit>(logicalQubitIndex));

// adjust output permutation
if (outputQubitIndex.has_value()) {
outputPermutation.insert({physicalQubitIndex, *outputQubitIndex});
outputPermutation.emplace(physicalQubitIndex, *outputQubitIndex);
} else {
// if a qubit is not relevant for the output, it is considered garbage
garbage[logicalQubitIndex] = true;
Expand Down Expand Up @@ -530,10 +515,10 @@ void QuantumComputation::addQubit(const Qubit logicalQubitIndex,
// increase qubit count
nqubits++;
// adjust initial layout
initialLayout.insert({physicalQubitIndex, logicalQubitIndex});
initialLayout.emplace(physicalQubitIndex, logicalQubitIndex);
if (outputQubitIndex.has_value()) {
// adjust output permutation
outputPermutation.insert({physicalQubitIndex, *outputQubitIndex});
outputPermutation.emplace(physicalQubitIndex, *outputQubitIndex);
}

// update ancillary and garbage tracking
Expand Down
2 changes: 1 addition & 1 deletion src/mqt/core/ir/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class Permutation(MutableMapping[int, int]):
"""

def __init__(self, permutation: dict[int, int]) -> None:
def __init__(self, permutation: dict[int, int] | None = None) -> None:
"""Initialize the permutation."""

def __getitem__(self, idx: int) -> int:
Expand Down
14 changes: 9 additions & 5 deletions src/mqt/core/plugins/qiskit.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,11 +383,6 @@ def _import_layouts(qc: QuantumComputation, circ: QuantumCircuit) -> None:
for virtual, physical in enumerate(initial_index_layout):
qc.initial_layout[physical] = virtual

qc.output_permutation.clear()
final_index_layout = circ.layout.final_index_layout()
for virtual, physical in enumerate(final_index_layout):
qc.output_permutation[physical] = virtual

# Properly mark ancillary qubits
for register in circ.layout.initial_layout.get_registers():
if register.name != "ancilla" and not isinstance(register, AncillaRegister):
Expand All @@ -397,6 +392,15 @@ def _import_layouts(qc: QuantumComputation, circ: QuantumCircuit) -> None:
virtual_qubit = qc.initial_layout[physical_qubit]
qc.set_circuit_qubit_ancillary(virtual_qubit)

if circ.layout.final_layout is None:
qc.output_permutation = qc.initial_layout
return

qc.output_permutation.clear()
final_index_layout = circ.layout.final_index_layout()
for virtual, physical in enumerate(final_index_layout):
qc.output_permutation[physical] = virtual

# Properly mark garbage qubits
# Any qubit in the initial layout that is not in the final layout is garbage
for virtual_qubit in range(len(final_index_layout), len(initial_index_layout)):
Expand Down
1 change: 1 addition & 0 deletions src/python/ir/register_permutation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ namespace mqt {

void registerPermutation(py::module& m) {
py::class_<qc::Permutation>(m, "Permutation")
.def(py::init<>())
.def(py::init([](const py::dict& p) {
qc::Permutation perm;
for (const auto& [key, value] : p) {
Expand Down
2 changes: 1 addition & 1 deletion src/qasm3/Parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -903,7 +903,7 @@ qc::Permutation Parser::parsePermutation(std::string s) {
qc::Qubit logicalQubit = 0;
for (std::smatch m; std::regex_search(s, m, QUBIT_REGEX); s = m.suffix()) {
auto physicalQubit = static_cast<qc::Qubit>(std::stoul(m.str()));
permutation.insert({physicalQubit, logicalQubit});
permutation.emplace(physicalQubit, logicalQubit);
++logicalQubit;
}
return permutation;
Expand Down
3 changes: 3 additions & 0 deletions src/qasm3/passes/TypeCheckPass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -331,10 +331,13 @@ InferredType TypeCheckPass::visitIdentifierExpression(
return type->second;
}

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsuggest-attribute=noreturn"
InferredType TypeCheckPass::visitIdentifierList(
std::shared_ptr<IdentifierList> /*identifierList*/) {
throw TypeCheckError("TypeCheckPass::visitIdentifierList not implemented");
}
#pragma GCC diagnostic pop

InferredType TypeCheckPass::visitIndexedIdentifier(
const std::shared_ptr<IndexedIdentifier> indexedIdentifier) {
Expand Down

0 comments on commit 5e94260

Please sign in to comment.