Skip to content

Commit

Permalink
✨ Support for Qiskit's final_layout (#592)
Browse files Browse the repository at this point in the history
## Description

This PR adds support for Qiskit's `final_layout` attribute when
importing circuits from Qiskit. It interprets the `output_permutation`
of an MQT `QuantumComputation` object based on Qiskit's
`final_index_layout()`, which maps the original input circuit's qubit
indices to the final physical qubit indices after routing. This mapping
is the result of applying `routing_permutation()` to the
`initial_layout`. Additionally, it fixes the interpretation of the
output_permutation in `initializeIOMapping()` to align with Qiskit's
final_layout.

Fixes #439


## 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>
Co-authored-by: burgholzer <burgholzer@me.com>
  • Loading branch information
TeWas and burgholzer committed Apr 20, 2024
1 parent 4d6f788 commit cb555c9
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 10 deletions.
3 changes: 1 addition & 2 deletions src/QuantumComputation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,7 @@ void QuantumComputation::initializeIOMapping() {
// output permutation was already set before -> permute existing
// values
const auto current = outputPermutation.at(qubitidx);
if (static_cast<std::size_t>(qubitidx) != bitidx &&
static_cast<std::size_t>(current) != bitidx) {
if (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
15 changes: 7 additions & 8 deletions src/mqt/core/plugins/qiskit.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,9 +359,7 @@ def _add_two_target_operation(


def _import_layouts(qc: QuantumComputation, circ: QuantumCircuit) -> None:
# qiskit-terra 0.24.0 added a (public) `layout` attribute
layout = circ.layout if hasattr(circ, "layout") else circ._layout # noqa: SLF001

layout = circ.layout
initial_layout = layout.initial_layout

# The following creates a map of virtual qubits in the layout to an integer index.
Expand All @@ -386,14 +384,15 @@ def _import_layouts(qc: QuantumComputation, circ: QuantumCircuit) -> None:
for device_qubit, circuit_qubit in initial_layout.get_physical_bits().items():
idx = qubit_to_idx[circuit_qubit]
qc.initial_layout[device_qubit] = idx
qc.output_permutation[device_qubit] = idx

if not hasattr(layout, "final_layout"):
if layout.final_layout is None:
qc.output_permutation = qc.initial_layout
return

final_layout = layout.final_layout
if final_layout is None:
return
# final_index_layout creates a list of final positions for input circuit qubits
final_index_layout = layout.final_index_layout(filter_ancillas=False)
for idx, value in enumerate(final_index_layout):
qc.output_permutation[value] = idx


def _import_definition(
Expand Down
53 changes: 53 additions & 0 deletions test/python/test_qiskit.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from qiskit import QuantumCircuit, transpile
from qiskit.circuit import AncillaRegister, ClassicalRegister, Parameter, QuantumRegister
from qiskit.circuit.library import U2Gate, XXMinusYYGate, XXPlusYYGate
from qiskit.providers.fake_provider import Fake5QV1

from mqt.core.operations import CompoundOperation, SymbolicOperation
from mqt.core.plugins.qiskit import qiskit_to_mqt
Expand Down Expand Up @@ -321,3 +322,55 @@ def test_symbolic_global_phase() -> None:
mqt_qc = qiskit_to_mqt(qc)

assert mqt_qc.global_phase == 0


def test_final_layout_without_permutation() -> None:
"""Test that the output permutation remains the same as the initial layout when routing is not performed."""
qc = QuantumCircuit(3)
qc.h(0)
qc.cx(0, 1)
qc.cx(0, 2)
initial_layout = [1, 2, 0]
seed = 123
qc_transpiled = transpile(qc, initial_layout=initial_layout, seed_transpiler=seed)
mqt_qc = qiskit_to_mqt(qc_transpiled)
assert mqt_qc.initial_layout == {0: 2, 1: 0, 2: 1}
assert mqt_qc.output_permutation == mqt_qc.initial_layout


def test_final_layout_with_permutation() -> None:
"""Test that the output permutation gets updated correctly when routing is performed."""
qc = QuantumCircuit(3)
qc.h(0)
qc.cx(1, 0)
qc.cx(1, 2)
qc.measure_all()
initial_layout = [1, 0, 3]
seed = 123
backend = Fake5QV1()
qc_transpiled = transpile(qc, backend, initial_layout=initial_layout, seed_transpiler=seed)
final_index_layout = qc_transpiled.layout.final_index_layout()
mqt_qc = qiskit_to_mqt(qc_transpiled)
# Check initialize_io_mapping doesn't change the final_layout
assert mqt_qc.output_permutation == dict(enumerate(final_index_layout))


def test_final_layout_with_permutation_ancilla_in_front_and_back() -> None:
"""Test that permutation update is correct with multiple registers and ancilla qubits."""
e = QuantumRegister(2, "e")
f_anc = AncillaRegister(1, "f")
b_anc = AncillaRegister(2, "b")
qc = QuantumCircuit(f_anc, e, b_anc)
qc.h(0)
qc.cx(1, 0)
qc.cx(1, 2)
qc.measure_all()
initial_layout = [1, 0, 3, 2, 4]
seed = 123
backend = Fake5QV1()
qc_transpiled = transpile(qc, backend, initial_layout=initial_layout, seed_transpiler=seed)
routing_permutation = qc_transpiled.layout.routing_permutation()
mqt_qc = qiskit_to_mqt(qc_transpiled)

# Check that output_permutation matches the result of applying the routing permutation to input_layout
assert mqt_qc.output_permutation == {idx: routing_permutation[key] for idx, key in enumerate(initial_layout)}
28 changes: 28 additions & 0 deletions test/unittests/test_qfr_functionality.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1396,6 +1396,34 @@ TEST_F(QFRFunctionality, AvoidStrippingIdleQubitWhenInOutputPermutation) {
EXPECT_EQ(qc.outputPermutation[1], 0U);
}

TEST_F(QFRFunctionality, UpdateOutputPermutation) {
// Update output permutation if swap gate was applied even if physical qubit
// index matches logical qubit index
QuantumComputation qc(5U, 3U);
qc.h(0);

// Swap qubits 2 and 3
qc.swap(2, 3);

qc.cx(1, 0);
qc.cx(1, 3);
qc.initialLayout[1] = 0;
qc.initialLayout[0] = 1;
qc.initialLayout[3] = 2;
qc.initialLayout[2] = 3;
qc.initialLayout[4] = 4;
qc.measure(1, 0);
qc.measure(0, 1);
qc.measure(2, 2);
qc.initializeIOMapping();

// Check that output permutation is equal to initialLayout for the measured
// qubits, except for the swap between qubits 2 and 3
EXPECT_EQ(qc.outputPermutation[1], qc.initialLayout[1]);
EXPECT_EQ(qc.outputPermutation[0], qc.initialLayout[0]);
EXPECT_EQ(qc.outputPermutation[2], qc.initialLayout[3]);
}

TEST_F(QFRFunctionality, RzAndPhaseDifference) {
QuantumComputation qc(2);
const std::string qasm = "// i 0 1\n"
Expand Down

0 comments on commit cb555c9

Please sign in to comment.