Skip to content

Commit 108dd52

Browse files
authored
✨ Portable Layout Information (#76)
* ✨ return `QuantumCircuit` if Qiskit is available (including Layout information) * 🙈 add virtual environments to .gitignore file * 🔧🐍 make `qiskit-terra` a project dependency * 🚨 fix LGTM warning * ♻️ replace soon to be deprecated `qiskit.test.mock` imports * 📝 update documentation * ⬆️ QFR 📦 with a 🐛 fix for Qiskit `Layout` import * 🐛 fix output permutation bug in heuristic mapper * ⬆️ QFR 📦 with a 🐛 fix for the `cancelCNOTs` optimization * ✅ add tests verifying the correctness of the circuits produced by both mappers
1 parent e6475a1 commit 108dd52

File tree

12 files changed

+224
-71
lines changed

12 files changed

+224
-71
lines changed

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,11 @@ var/
2121
.installed.cfg
2222
*.egg
2323
.pytest_cache/
24+
25+
.env
26+
.venv
27+
env/
28+
venv/
29+
ENV/
30+
env.bak/
31+
venv.bak/

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,13 @@ MQT QMAP is developed as a C++ library with an easy to use Python interface.
6666
- Once installed, start using it in Python:
6767
```python
6868
from mqt import qmap
69-
results = qmap.compile(circ, arch)
69+
circ_mapped, results = qmap.compile(circ, arch)
7070
```
7171

7272
where `circ` is either a Qiskit `QuantumCircuit` object or the path to an input file (in any of the formats listed above)
7373
and `arch` is either
7474

75-
- a Qiskit `Backend` instance such as those defined under `qiskit.test.mock` **(recommended)**,
75+
- a Qiskit `Backend` instance such as those defined under `qiskit.providers.fake_provider` **(recommended)**,
7676
- one of the pre-defined architectures (see below), or
7777
- the path to a file containing the number of qubits and a line-by-line enumeration of the qubit connections.
7878

extern/qfr

Submodule qfr updated from 92bbd82 to 2c0c3d6

jkq/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import warnings
2-
from mqt import *
2+
from mqt import qmap
33

44
warnings.simplefilter('always', DeprecationWarning)
55
warnings.warn('Usage via `import jkq` is deprecated in favor of the new prefix. Please use `import mqt` instead.', DeprecationWarning)

mqt/qmap/compile.py

Lines changed: 60 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,42 @@
44
#
55
import pickle
66
from pathlib import Path
7-
from typing import Union, Optional, Set
7+
from typing import Union, Optional, Set, List, Tuple
8+
9+
from qiskit import QuantumCircuit, QuantumRegister
10+
from qiskit.providers import Backend
11+
from qiskit.providers.models import BackendProperties
12+
from qiskit.transpiler.target import Target
13+
from qiskit.transpiler import Layout
14+
815
from mqt.qmap.pyqmap import map, Method, InitialLayout, Layering, Arch, Encoding, CommanderGrouping, SwapReduction, Configuration, MappingResults, Architecture
916

10-
try:
11-
from qiskit.providers import Backend
12-
from qiskit.providers.models import BackendProperties
13-
from qiskit.transpiler.target import Target
1417

15-
PossibleArchitectureTypes = Union[str, Arch, Architecture, Backend]
16-
PossibleCalibrationTypes = Union[str, BackendProperties, Target]
17-
except ModuleNotFoundError:
18-
PossibleArchitectureTypes = Union[str, Arch, Architecture]
19-
PossibleCalibrationTypes = Union[str]
18+
def extract_initial_layout_from_qasm(qasm: str, qregs: List[QuantumRegister]) -> Layout:
19+
"""
20+
Extracts the initial layout resulting from compiling a circuit from a QASM file.
21+
:param qasm: QASM file
22+
:type qasm: str
23+
:param qregs: The quantum registers to apply the layout to.
24+
:type qregs: List[QuantumRegister]
25+
:return: layout to be used in Qiskit
26+
"""
27+
for line in qasm.split("\n"):
28+
if line.startswith("// i "):
29+
# strip away initial part of line
30+
line = line[5:]
31+
# split line into tokens
32+
tokens = line.split(" ")
33+
# convert tokens to integers
34+
tokens = [int(token) for token in tokens]
35+
# create an empty layout
36+
layout = Layout().from_intlist(tokens, *qregs)
37+
return layout
2038

2139

22-
def compile(circ, arch: Optional[PossibleArchitectureTypes],
23-
calibration: Optional[PossibleCalibrationTypes] = None,
40+
def compile(circ: Union[QuantumCircuit, str],
41+
arch: Optional[Union[str, Arch, Architecture, Backend]],
42+
calibration: Optional[Union[str, BackendProperties, Target]] = None,
2443
method: Union[str, Method] = "heuristic",
2544
initial_layout: Union[str, InitialLayout] = "dynamic",
2645
layering: Union[str, Layering] = "individual_gates",
@@ -38,14 +57,15 @@ def compile(circ, arch: Optional[PossibleArchitectureTypes],
3857
pre_mapping_optimizations: bool = True,
3958
post_mapping_optimizations: bool = True,
4059
verbose: bool = False
41-
) -> MappingResults:
60+
) -> Tuple[QuantumCircuit, MappingResults]:
4261
"""Interface to the MQT QMAP tool for mapping quantum circuits
4362
44-
:param circ: Path to circuit file, path to Qiskit QuantumCircuit pickle, or Qiskit QuantumCircuit object
63+
:param circ: Qiskit QuantumCircuit object, path to circuit file, or path to Qiskit QuantumCircuit pickle
64+
:type circ: Union[QuantumCircuit, str]
4565
:param arch: Architecture to map to. Either a path to a file with architecture information, one of the available architectures (Arch), qmap.Architecture, or `qiskit.providers.backend` (if Qiskit is installed)
46-
:type arch: Optional[PossibleArchitectureTypes]
66+
:type arch: Optional[Union[str, Arch, Architecture, Backend]]
4767
:param calibration: Path to file containing calibration information, `qiskit.providers.models.BackendProperties` object (if Qiskit is installed), or `qiskit.transpiler.target.Target` object (if Qiskit is installed)
48-
:type calibration: Optional[PossibleCalibrationTypes]
68+
:type calibration: Optional[Union[str, BackendProperties, Target]]
4969
:param method: Mapping technique to use (*heuristic* | exact)
5070
:type method: Union[str, Method]
5171
:param initial_layout: Strategy to use for determining initial layout in heuristic mapper (identity | static | *dynamic*)
@@ -77,12 +97,13 @@ def compile(circ, arch: Optional[PossibleArchitectureTypes],
7797
:type post_mapping_optimizations: bool
7898
:param verbose: Print more detailed information during the mapping process
7999
:type verbose: bool
80-
:return: Object containing all the results
81-
:rtype: MappingResults
100+
:return: Mapped circuit (as Qiskit `QuantumCircuit`) and results
101+
:rtype: Tuple[QuantumCircuit, MappingResults]
82102
"""
83103

84104
if subgraph is None:
85105
subgraph = set()
106+
86107
if type(circ) == str and Path(circ).suffix == '.pickle':
87108
circ = pickle.load(open(circ, "rb"))
88109

@@ -100,34 +121,26 @@ def compile(circ, arch: Optional[PossibleArchitectureTypes],
100121
architecture.load_coupling_map(arch)
101122
elif isinstance(arch, Architecture):
102123
architecture = arch
124+
elif isinstance(arch, Backend):
125+
from mqt.qmap.qiskit.backend import import_backend
126+
127+
architecture = import_backend(arch)
103128
else:
104-
try:
105-
from qiskit.providers.backend import Backend
106-
from mqt.qmap.qiskit.backend import import_backend
107-
if isinstance(arch, Backend):
108-
architecture = import_backend(arch)
109-
else:
110-
raise ValueError("No compatible type for architecture:", type(arch))
111-
except ModuleNotFoundError:
112-
raise ValueError("No compatible type for architecture:", type(arch))
129+
raise ValueError("No compatible type for architecture:", type(arch))
113130

114131
if calibration is not None:
115132
if type(calibration) == str:
116133
architecture.load_properties(calibration)
134+
elif isinstance(calibration, BackendProperties):
135+
from mqt.qmap.qiskit.backend import import_backend_properties
136+
137+
architecture.load_properties(import_backend_properties(calibration))
138+
elif isinstance(calibration, Target):
139+
from mqt.qmap.qiskit.backend import import_target
140+
141+
architecture.load_properties(import_target(calibration))
117142
else:
118-
try:
119-
from qiskit.providers.models import BackendProperties
120-
from qiskit.transpiler.target import Target
121-
from mqt.qmap.qiskit.backend import import_backend_properties, import_target
122-
123-
if isinstance(calibration, BackendProperties):
124-
architecture.load_properties(import_backend_properties(calibration))
125-
elif isinstance(calibration, Target):
126-
architecture.load_properties(import_target(calibration))
127-
else:
128-
raise ValueError("No compatible type for calibration:", type(calibration))
129-
except ModuleNotFoundError:
130-
raise ValueError("No compatible type for calibration:", type(calibration))
143+
raise ValueError("No compatible type for calibration:", type(calibration))
131144

132145
config = Configuration()
133146
config.method = Method(method)
@@ -148,4 +161,10 @@ def compile(circ, arch: Optional[PossibleArchitectureTypes],
148161
config.post_mapping_optimizations = post_mapping_optimizations
149162
config.verbose = verbose
150163

151-
return map(circ, architecture, config)
164+
results = map(circ, architecture, config)
165+
166+
circ = QuantumCircuit.from_qasm_str(results.mapped_circuit)
167+
layout = extract_initial_layout_from_qasm(results.mapped_circuit, circ.qregs)
168+
circ._layout = layout
169+
170+
return circ, results

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ build = "cp3*"
1515
archs = "auto64"
1616
skip = "*-musllinux*"
1717
test-skip = "*-macosx_arm64 *-musllinux* *aarch64"
18-
test-extras = ["tests"]
18+
test-extras = ["test"]
1919
test-command = "python -m pytest {project}/test/python"
2020
environment = { DEPLOY = "ON" }
2121
build-frontend = "build"

setup.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,10 @@ def build_extension(self, ext):
103103
cmdclass={"build_ext": CMakeBuild},
104104
zip_safe=False,
105105
packages=find_namespace_packages(include=['mqt.*']),
106+
install_requires=["qiskit-terra~=0.20.2"],
106107
extras_require={
107-
"tests": ["pytest~=7.1.1", "qiskit-terra>=0.19.2,<0.21.0"],
108+
"test": ["pytest~=7.1.1", "mqt.qcec~=2.0.0rc4"],
109+
"dev": ["mqt.qmap[test]"] # requires Pip 21.2 or newer
108110
},
109111
classifiers=[
110112
'Development Status :: 5 - Production/Stable',

src/heuristic/HeuristicMapper.cpp

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,18 @@ void HeuristicMapper::map(const Configuration& ms) {
111111
}
112112
}
113113

114+
// infer output permutation from qubit locations
115+
qcMapped.outputPermutation.clear();
116+
std::size_t count = 0U;
117+
for (std::size_t i = 0U; i < architecture.getNqubits(); ++i) {
118+
if (qubits[i] != -1) {
119+
qcMapped.outputPermutation[static_cast<dd::Qubit>(i)] = static_cast<dd::Qubit>(qubits[i]);
120+
} else {
121+
qcMapped.setLogicalQubitGarbage(qc.getNqubits() + count);
122+
++count;
123+
}
124+
}
125+
114126
// fix single qubit gates
115127
if (!gatesToAdjust.empty()) {
116128
gateidx--; // index of last operation
@@ -153,18 +165,6 @@ void HeuristicMapper::map(const Configuration& ms) {
153165
}
154166
}
155167

156-
// infer output permutation from qubit locations
157-
qcMapped.outputPermutation.clear();
158-
std::size_t count = 0U;
159-
for (std::size_t i = 0U; i < architecture.getNqubits(); ++i) {
160-
if (qubits[i] != -1) {
161-
qcMapped.outputPermutation[static_cast<dd::Qubit>(i)] = static_cast<dd::Qubit>(qubits[i]);
162-
} else {
163-
qcMapped.setLogicalQubitGarbage(qc.getNqubits() + count);
164-
++count;
165-
}
166-
}
167-
168168
postMappingOptimizations(config);
169169
countGates(qcMapped, results.output);
170170
finalizeMappedCircuit();

test/python/test.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from qiskit import QuantumCircuit
2-
from qiskit.test.mock.backends import FakeLondon
2+
from qiskit.providers.fake_provider import FakeLondon
33

44
from mqt import qmap
55

@@ -12,8 +12,5 @@
1212
print(qc.draw(fold=-1))
1313

1414
# compile the circuit
15-
results = qmap.compile(qc, arch=FakeLondon())
16-
17-
# get the mapped circuit
18-
qc_mapped = QuantumCircuit.from_qasm_str(results.mapped_circuit)
15+
qc_mapped, results = qmap.compile(qc, arch=FakeLondon())
1916
print(qc_mapped.draw(fold=-1))

test/python/test_exact_mapper.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import pytest
2+
from qiskit import QuantumCircuit
3+
from qiskit.providers.fake_provider import FakeLondon
4+
5+
from mqt import qmap, qcec
6+
7+
8+
def test_exact_no_swaps_trivial_layout():
9+
"""Verify that the exact mapper works on a simple circuit that requires no swaps on a trivial initial layout."""
10+
qc = QuantumCircuit(3)
11+
qc.h(0)
12+
qc.cx(0, 1)
13+
qc.cx(1, 2)
14+
qc.measure_all()
15+
16+
qc_mapped, results = qmap.compile(qc, arch=FakeLondon(), method="exact")
17+
assert results.timeout is False
18+
assert results.mapped_circuit != ""
19+
assert results.output.swaps == 0
20+
21+
result = qcec.verify(qc, qc_mapped)
22+
assert result.considered_equivalent() is True
23+
24+
25+
def test_exact_no_swaps_non_trivial_layout():
26+
"""Verify that the exact mapper works on a simple circuit that requires a non-trivial layout to achieve no swaps."""
27+
qc = QuantumCircuit(4)
28+
qc.h(0)
29+
qc.cx(0, 1)
30+
qc.cx(0, 2)
31+
qc.cx(0, 3)
32+
qc.measure_all()
33+
34+
qc_mapped, results = qmap.compile(qc, arch=FakeLondon(), method="exact")
35+
36+
assert results.timeout is False
37+
assert results.mapped_circuit != ""
38+
assert results.output.swaps == 0
39+
40+
result = qcec.verify(qc, qc_mapped)
41+
assert result.considered_equivalent() is True
42+
43+
44+
def test_exact_non_trivial_swaps():
45+
"""Verify that the exact mapper works on a simple circuit that requires at least a single SWAP."""
46+
qc = QuantumCircuit(3)
47+
qc.h(0)
48+
qc.cx(0, 1)
49+
qc.cx(1, 2)
50+
qc.cx(2, 0)
51+
qc.measure_all()
52+
53+
qc_mapped, results = qmap.compile(qc, arch=FakeLondon(), method="exact")
54+
55+
assert results.timeout is False
56+
assert results.mapped_circuit != ""
57+
assert results.output.swaps == 1
58+
59+
result = qcec.verify(qc, qc_mapped)
60+
assert result.considered_equivalent() is True

0 commit comments

Comments
 (0)