iqm-client 32.0.0__tar.gz → 32.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {iqm_client-32.0.0 → iqm_client-32.1.1}/CHANGELOG.rst +16 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/PKG-INFO +1 -1
- {iqm_client-32.0.0 → iqm_client-32.1.1}/docs/user_guide_qiskit.rst +16 -17
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/qiskit_iqm/iqm_naive_move_pass.py +1 -1
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/qiskit_iqm/iqm_target.py +8 -2
- iqm_client-32.1.1/src/iqm/qiskit_iqm/iqm_transpilation.py +384 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/qiskit_iqm/qiskit_to_iqm.py +110 -38
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/qiskit_iqm/transpiler_plugins.py +11 -8
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm_client.egg-info/PKG-INFO +1 -1
- iqm_client-32.1.1/version.txt +1 -0
- iqm_client-32.0.0/src/iqm/qiskit_iqm/iqm_transpilation.py +0 -191
- iqm_client-32.0.0/version.txt +0 -1
- {iqm_client-32.0.0 → iqm_client-32.1.1}/AUTHORS.rst +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/CHANGELOG_cirq-iqm.rst +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/CHANGELOG_cortex-cli.rst +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/CHANGELOG_qiskit-iqm.rst +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/INTEGRATION_GUIDE.rst +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/LICENSE.txt +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/MANIFEST.in +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/README.rst +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/docbuild +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/docs/API.rst +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/docs/_static/images/favicon.ico +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/docs/_static/images/logo.png +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/docs/_templates/autosummary-class-template.rst +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/docs/_templates/autosummary-module-template.rst +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/docs/authors.rst +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/docs/changelog.rst +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/docs/conf.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/docs/index.rst +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/docs/integration_guide.rst +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/docs/license.rst +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/docs/readme.rst +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/docs/user_guide_cirq.rst +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/pyproject.toml +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/pytest.ini +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/requirements/base.in +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/requirements/base.in.internal +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/requirements/base.txt +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/requirements/cirq.in +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/requirements/cirq.txt +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/requirements/cli.in +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/requirements/cli.txt +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/requirements/qiskit.in +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/requirements/qiskit.txt +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/setup.cfg +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/setup.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/cirq_iqm/__init__.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/cirq_iqm/devices/__init__.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/cirq_iqm/devices/adonis.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/cirq_iqm/devices/aphrodite.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/cirq_iqm/devices/apollo.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/cirq_iqm/devices/iqm_device.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/cirq_iqm/devices/iqm_device_metadata.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/cirq_iqm/examples/demo_adonis.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/cirq_iqm/examples/demo_apollo.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/cirq_iqm/examples/demo_common.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/cirq_iqm/examples/demo_iqm_execution.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/cirq_iqm/examples/usage.ipynb +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/cirq_iqm/extended_qasm_parser.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/cirq_iqm/iqm_gates.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/cirq_iqm/iqm_sampler.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/cirq_iqm/optimizers.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/cirq_iqm/py.typed +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/cirq_iqm/serialize.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/cirq_iqm/transpiler.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/iqm_client/__init__.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/iqm_client/api.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/iqm_client/authentication.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/iqm_client/errors.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/iqm_client/iqm_client.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/iqm_client/models.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/iqm_client/py.typed +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/iqm_client/transpile.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/iqm_client/util.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/iqm_client/validation.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/qiskit_iqm/__init__.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/qiskit_iqm/examples/__init__.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/qiskit_iqm/examples/bell_measure.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/qiskit_iqm/examples/transpile_example.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/qiskit_iqm/fake_backends/__init__.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/qiskit_iqm/fake_backends/fake_adonis.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/qiskit_iqm/fake_backends/fake_aphrodite.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/qiskit_iqm/fake_backends/fake_apollo.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/qiskit_iqm/fake_backends/fake_deneb.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/qiskit_iqm/fake_backends/fake_garnet.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/qiskit_iqm/fake_backends/iqm_fake_backend.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/qiskit_iqm/iqm_backend.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/qiskit_iqm/iqm_circuit.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/qiskit_iqm/iqm_circuit_validation.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/qiskit_iqm/iqm_job.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/qiskit_iqm/iqm_move_layout.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/qiskit_iqm/iqm_provider.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/qiskit_iqm/move_gate.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm/qiskit_iqm/py.typed +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm_client.egg-info/SOURCES.txt +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm_client.egg-info/dependency_links.txt +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm_client.egg-info/entry_points.txt +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm_client.egg-info/requires.txt +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/src/iqm_client.egg-info/top_level.txt +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/test +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/tests/__init__.py +0 -0
- {iqm_client-32.0.0 → iqm_client-32.1.1}/tests/conftest.py +0 -0
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
Changelog
|
|
3
3
|
=========
|
|
4
4
|
|
|
5
|
+
Version 32.1.1 (2025-10-27)
|
|
6
|
+
===========================
|
|
7
|
+
|
|
8
|
+
- Bump version for 4.3.1 release. No functional changes.
|
|
9
|
+
|
|
10
|
+
Version 32.1.0 (2025-10-13)
|
|
11
|
+
===========================
|
|
12
|
+
|
|
13
|
+
Features
|
|
14
|
+
--------
|
|
15
|
+
|
|
16
|
+
- Add support for the ``with circuit.if_test()`` construction for classical control in Qiskit :issue:`SW-1724`.
|
|
17
|
+
- Deprecate the ``c_if`` method in favor of the new ``if_test`` method for classical control in Qiskit :issue:`SW-1724`.
|
|
18
|
+
- Bugfix: Fixed the transpiler bug causing classical conditionals to be ignored in certain cases :issue:`SW-1856`.
|
|
19
|
+
- Using one of our transpiler plugins that do not apply MoveGate routing, no longer raises an error when the target backend is not set in the transpiler. e.g. ``transpile(circuit, scheduling_method="only_rz_optimization", basis_gates=['r','cz', 'if_else'])``.
|
|
20
|
+
|
|
5
21
|
Version 32.0.0 (2025-10-09)
|
|
6
22
|
===========================
|
|
7
23
|
|
|
@@ -279,11 +279,11 @@ support currently has several limitations:
|
|
|
279
279
|
apply the gate if the bit is 1, and apply an identity gate if the bit is 0.
|
|
280
280
|
* The availability of the controlled gates depends on the instrumentation of the quantum computer.
|
|
281
281
|
|
|
282
|
-
The classical control can be applied on a circuit instruction using :meth:`~qiskit.circuit.
|
|
282
|
+
The classical control can be applied on a circuit instruction using :meth:`~qiskit.circuit.QuantumCircuit.if_test`:
|
|
283
283
|
|
|
284
284
|
.. code-block:: python
|
|
285
285
|
|
|
286
|
-
from qiskit import QuantumCircuit
|
|
286
|
+
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
|
|
287
287
|
|
|
288
288
|
qr = QuantumRegister(2, 'q')
|
|
289
289
|
cr = ClassicalRegister(1, 'c')
|
|
@@ -291,26 +291,27 @@ The classical control can be applied on a circuit instruction using :meth:`~qisk
|
|
|
291
291
|
|
|
292
292
|
circuit.h(0)
|
|
293
293
|
circuit.measure(0, cr[0])
|
|
294
|
-
circuit.
|
|
294
|
+
with circuit.if_test((cr[0], 1)):
|
|
295
|
+
circuit.x(1) # apply X gate on qubit 1 if cr[0] is 1
|
|
295
296
|
circuit.measure_all()
|
|
296
297
|
|
|
297
298
|
print(circuit.draw(output='text'))
|
|
298
299
|
|
|
299
300
|
::
|
|
300
301
|
|
|
301
|
-
┌───┐┌─┐
|
|
302
|
-
q_0: ┤ H ├┤M
|
|
303
|
-
└───┘└╥┘
|
|
304
|
-
q_1:
|
|
305
|
-
║
|
|
306
|
-
║
|
|
307
|
-
c: 1/══════╩═╡ 0x1
|
|
308
|
-
0
|
|
309
|
-
meas: 2
|
|
310
|
-
|
|
302
|
+
┌───┐┌─┐ ░ ┌─┐
|
|
303
|
+
q_0: ┤ H ├┤M├───────────────────────────░─┤M├───
|
|
304
|
+
└───┘└╥┘ ┌────── ┌───┐ ───────┐ ░ └╥┘┌─┐
|
|
305
|
+
q_1: ──────╫───┤ If-0 ─┤ X ├ End-0 ├──░──╫─┤M├
|
|
306
|
+
║ └──╥─── └───┘ ───────┘ ░ ║ └╥┘
|
|
307
|
+
║ ┌────╨────┐ ║ ║
|
|
308
|
+
c: 1/══════╩═╡ c_0=0x1 ╞═══════════════════╬══╬═
|
|
309
|
+
0 └─────────┘ ║ ║
|
|
310
|
+
meas: 2/══════════════════════════════════════╩══╩═
|
|
311
|
+
0 1
|
|
311
312
|
|
|
312
313
|
|
|
313
|
-
The first measurement operation stores its result in the
|
|
314
|
+
The first measurement operation stores its result in the classical register ``c``. If the
|
|
314
315
|
result is 1, the ``X`` gate will be applied. If it is zero, an identity gate of corresponding
|
|
315
316
|
duration is applied instead.
|
|
316
317
|
|
|
@@ -319,9 +320,7 @@ between the '00 0' and '11 1' bins of the histogram (even though the state itsel
|
|
|
319
320
|
|
|
320
321
|
.. note::
|
|
321
322
|
|
|
322
|
-
|
|
323
|
-
in a 1-bit classical register, ``c`` in the above example.
|
|
324
|
-
|
|
323
|
+
``if_test`` blocks cannot be nested.
|
|
325
324
|
|
|
326
325
|
Resetting qubits
|
|
327
326
|
~~~~~~~~~~~~~~~~
|
|
@@ -98,7 +98,7 @@ class IQMNaiveResonatorMoving(TransformationPass):
|
|
|
98
98
|
# Convert the circuit to the IQMClientCircuit format and run the transpiler.
|
|
99
99
|
iqm_circuit = IQMClientCircuit(
|
|
100
100
|
name="Transpiling Circuit",
|
|
101
|
-
instructions=tuple(serialize_instructions(circuit, self.idx_to_component)),
|
|
101
|
+
instructions=tuple(serialize_instructions(circuit, self.idx_to_component, overwrite_layout=layout)),
|
|
102
102
|
metadata=None,
|
|
103
103
|
)
|
|
104
104
|
try:
|
|
@@ -26,7 +26,7 @@ from iqm.iqm_client import (
|
|
|
26
26
|
ObservationFinder,
|
|
27
27
|
)
|
|
28
28
|
from iqm.qiskit_iqm.move_gate import MoveGate
|
|
29
|
-
from qiskit.circuit import Delay, Gate, Parameter, Reset
|
|
29
|
+
from qiskit.circuit import Delay, Gate, IfElseOp, Parameter, Reset
|
|
30
30
|
from qiskit.circuit.library import CZGate, IGate, Measure, RGate
|
|
31
31
|
from qiskit.providers import QubitProperties
|
|
32
32
|
from qiskit.transpiler import InstructionProperties, Target
|
|
@@ -44,6 +44,7 @@ _QISKIT_IQM_GATE_MAP: dict[str, Gate] = {
|
|
|
44
44
|
"cz": CZGate(),
|
|
45
45
|
"move": MoveGate(),
|
|
46
46
|
"id": IGate(),
|
|
47
|
+
"if_else": IfElseOp,
|
|
47
48
|
}
|
|
48
49
|
"""Maps IQM native operation names to corresponding Qiskit gate objects."""
|
|
49
50
|
|
|
@@ -126,8 +127,13 @@ class IQMTarget(Target):
|
|
|
126
127
|
if "prx" in op_loci:
|
|
127
128
|
add_gate("prx")
|
|
128
129
|
|
|
129
|
-
# HACK reset gate shares cc_prx loci for now, until reset is also in the DQA/metrics
|
|
130
130
|
if "cc_prx" in op_loci:
|
|
131
|
+
# IfElseOp is a global 'gate' so it's slightly different from the others.
|
|
132
|
+
self.add_instruction(
|
|
133
|
+
instruction=_QISKIT_IQM_GATE_MAP["if_else"],
|
|
134
|
+
name="if_else",
|
|
135
|
+
)
|
|
136
|
+
# HACK reset gate shares cc_prx loci for now, until reset is also in the DQA/metrics
|
|
131
137
|
self.add_instruction(
|
|
132
138
|
_QISKIT_IQM_GATE_MAP["reset"],
|
|
133
139
|
{self.locus_to_idx(locus): None for locus in op_loci["cc_prx"]},
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
# Copyright 2023 Qiskit on IQM developers
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
"""Transpilation tool to optimize the decomposition of single-qubit gates tailored to IQM hardware."""
|
|
15
|
+
|
|
16
|
+
import math
|
|
17
|
+
import warnings
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
20
|
+
from packaging.version import Version
|
|
21
|
+
from qiskit import QuantumCircuit
|
|
22
|
+
from qiskit import __version__ as qiskit_version
|
|
23
|
+
from qiskit.circuit.controlflow import IfElseOp
|
|
24
|
+
from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary
|
|
25
|
+
from qiskit.circuit.library import RGate, RZGate, UnitaryGate
|
|
26
|
+
from qiskit.converters import circuit_to_dag, dag_to_circuit
|
|
27
|
+
from qiskit.dagcircuit import DAGCircuit, DAGOpNode
|
|
28
|
+
from qiskit.transpiler.basepasses import TransformationPass
|
|
29
|
+
from qiskit.transpiler.passes import (
|
|
30
|
+
BasisTranslator,
|
|
31
|
+
Optimize1qGatesDecomposition,
|
|
32
|
+
RemoveBarriers,
|
|
33
|
+
)
|
|
34
|
+
from qiskit.transpiler.passmanager import PassManager
|
|
35
|
+
|
|
36
|
+
TOLERANCE = 1e-10 # The tolerance for equivalence checking against zero.
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class IQMOptimizeSingleQubitGates(TransformationPass):
|
|
40
|
+
r"""Optimize the decomposition of single-qubit gates for the IQM gate set.
|
|
41
|
+
|
|
42
|
+
This optimization pass expects the circuit to be correctly layouted and translated to the IQM architecture
|
|
43
|
+
and raises an error otherwise.
|
|
44
|
+
The optimization logic follows the steps:
|
|
45
|
+
|
|
46
|
+
1. Convert single-qubit gates to :math:`U` gates and combine all neighboring :math:`U` gates.
|
|
47
|
+
2. Convert :math:`U` gates according to
|
|
48
|
+
:math:`U(\theta , \phi , \lambda) = ~ RZ(\phi + \lambda) R(\theta, \pi / 2 - \lambda)`.
|
|
49
|
+
3. Commute `RZ` gates to the end of the circuit using the fact that `RZ` and `CZ` gates commute, and
|
|
50
|
+
:math:`R(\theta , \phi) RZ(\lambda) = RZ(\lambda) R(\theta, \phi - \lambda)`.
|
|
51
|
+
4. Drop `RZ` gates immediately before measurements, and otherwise replace them according to
|
|
52
|
+
:math:`RZ(\lambda) = R(\pi, \lambda / 2) R(- \pi, 0)`.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
drop_final_rz: Drop terminal RZ gates even if there are no measurements following them (since they do not affect
|
|
56
|
+
the measurement results). Note that this will change the unitary propagator of the circuit.
|
|
57
|
+
It is recommended always to set this to true as the final RZ gates do no change the measurement outcomes of
|
|
58
|
+
the circuit.
|
|
59
|
+
ignore_barriers (bool): Removes the barriers from the circuit before optimization (default = False).
|
|
60
|
+
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def __init__(self, drop_final_rz: bool = True, ignore_barriers: bool = False):
|
|
64
|
+
super().__init__()
|
|
65
|
+
self._basis = ["r", "cz", "move", "if_else"]
|
|
66
|
+
self._intermediate_basis = ["u", "cz", "move", "if_else"]
|
|
67
|
+
self._drop_final_rz = drop_final_rz
|
|
68
|
+
self._ignore_barriers = ignore_barriers
|
|
69
|
+
|
|
70
|
+
def run(self, dag: DAGCircuit, decompose_rz_to_r: bool = True) -> DAGCircuit:
|
|
71
|
+
"""Runs the single-qubit gate optimization pass.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
dag: The input DAG circuit to optimize.
|
|
75
|
+
decompose_rz_to_r: Whether to decompose RZ gates into R gates, or add the to the DAG as
|
|
76
|
+
RZ gates. This is used in recursive calls to communicate the accumulated RZ angles in ``rz_angles``.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
The optimized DAG circuit.
|
|
80
|
+
|
|
81
|
+
"""
|
|
82
|
+
if decompose_rz_to_r:
|
|
83
|
+
self._validate_ops(dag)
|
|
84
|
+
# accumulated RZ angles for each qubit, from the beginning of the circuit to the current gate
|
|
85
|
+
rz_angles: list[float] = [0] * dag.num_qubits()
|
|
86
|
+
|
|
87
|
+
# Handle old conditional gates
|
|
88
|
+
if Version(qiskit_version) < Version("2.0"):
|
|
89
|
+
# This needs to be done before the BasisTranslation as that pass does not retain the condition.
|
|
90
|
+
dag = self._handle_c_if_blocks(dag)
|
|
91
|
+
|
|
92
|
+
if self._ignore_barriers:
|
|
93
|
+
dag = RemoveBarriers().run(dag)
|
|
94
|
+
# convert all gates in the circuit to U and CZ gates
|
|
95
|
+
dag = BasisTranslator(SessionEquivalenceLibrary, self._intermediate_basis).run(dag)
|
|
96
|
+
# combine all sequential U gates into one
|
|
97
|
+
dag = Optimize1qGatesDecomposition(self._intermediate_basis).run(dag)
|
|
98
|
+
for node in dag.topological_op_nodes():
|
|
99
|
+
if node.name == "u":
|
|
100
|
+
dag, rz_angles = self._handle_u_gates(dag, node, rz_angles)
|
|
101
|
+
elif node.name in {"measure", "reset"}:
|
|
102
|
+
# measure and reset destroy phase information. The local phases before and after such
|
|
103
|
+
# an operation are in principle independent, and the local computational frame phases
|
|
104
|
+
# are arbitrary so we could set rz_angles to any values here, but zeroing the
|
|
105
|
+
# angles results in fewest changes to the circuit.
|
|
106
|
+
for qubit in node.qargs:
|
|
107
|
+
rz_angles[dag.find_bit(qubit).index] = 0
|
|
108
|
+
elif node.name == "barrier":
|
|
109
|
+
# TODO barriers are meant to restrict circuit optimization, so strictly speaking
|
|
110
|
+
# we should output any accumulated ``rz_angles`` here as explicit z rotations (like
|
|
111
|
+
# the final rz:s). However, ``rz_angles`` simply represents a choice of phases for the
|
|
112
|
+
# local computational frames for the rest of the circuit (the initial part has already
|
|
113
|
+
# been transformed). This choice of local phases is in principle arbitrary, so maybe it
|
|
114
|
+
# makes no sense to convert it into active z rotations if we hit a barrier?
|
|
115
|
+
pass
|
|
116
|
+
elif node.name == "move":
|
|
117
|
+
# acts like iSWAP with RZ, moving it to the other component
|
|
118
|
+
qb, res = (
|
|
119
|
+
dag.find_bit(node.qargs[0]).index,
|
|
120
|
+
dag.find_bit(node.qargs[1]).index,
|
|
121
|
+
)
|
|
122
|
+
rz_angles[res], rz_angles[qb] = rz_angles[qb], rz_angles[res]
|
|
123
|
+
elif node.name in {"cz", "delay"}:
|
|
124
|
+
pass # commutes with RZ gates
|
|
125
|
+
elif node.name == "if_else":
|
|
126
|
+
dag, rz_angles = self._handle_if_else_block(dag, node, rz_angles)
|
|
127
|
+
else:
|
|
128
|
+
raise ValueError(
|
|
129
|
+
f"Unexpected operation '{node.name}' in circuit given to IQMOptimizeSingleQubitGates pass"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if not decompose_rz_to_r:
|
|
133
|
+
for qubit_index, rz_angle in enumerate(rz_angles):
|
|
134
|
+
dag.apply_operation_back(RZGate(rz_angle), qargs=(dag.qubits[qubit_index],))
|
|
135
|
+
elif not self._drop_final_rz:
|
|
136
|
+
dag, rz_angles = self._apply_final_r_gates(dag, rz_angles)
|
|
137
|
+
|
|
138
|
+
return dag
|
|
139
|
+
|
|
140
|
+
def _apply_final_r_gates(self, dag: DAGCircuit, rz_angles: list[float]) -> tuple[DAGCircuit, list[float]]:
|
|
141
|
+
"""Helper function that adds the final PRX/R gates to the circuit according to the accumulated angles.
|
|
142
|
+
|
|
143
|
+
Returns the updated dag and a list of zero angles since the final RZ rotations are already applied.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
dag: The input DAG circuit we are optimizing.
|
|
147
|
+
rz_angles: The accumulated RZ angles for each qubit.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
The updated DAG circuit and a list of zero angles.
|
|
151
|
+
|
|
152
|
+
"""
|
|
153
|
+
for qubit_index, rz_angle in enumerate(rz_angles):
|
|
154
|
+
if not math.isclose(rz_angle, 0, abs_tol=TOLERANCE):
|
|
155
|
+
qubit = dag.qubits[qubit_index]
|
|
156
|
+
dag.apply_operation_back(RGate(-np.pi, 0), qargs=(qubit,))
|
|
157
|
+
dag.apply_operation_back(RGate(np.pi, rz_angle / 2), qargs=(qubit,))
|
|
158
|
+
# Return resetted angles
|
|
159
|
+
return dag, [0.0] * dag.num_qubits()
|
|
160
|
+
|
|
161
|
+
def _handle_u_gates(
|
|
162
|
+
self, dag: DAGCircuit, node: DAGOpNode, rz_angles: list[float]
|
|
163
|
+
) -> tuple[DAGCircuit, list[float]]:
|
|
164
|
+
"""Helper function that converts U gates to PRXs and RZ gates,
|
|
165
|
+
so that the RZ gates can be commuted to the end of the circuit.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
dag: The input DAG circuit we are optimizing.
|
|
169
|
+
node: The DAG node containing the U gate to convert.
|
|
170
|
+
rz_angles: The accumulated RZ angles for each qubit.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
The updated DAG circuit and the updated list of accumulated RZ angles.
|
|
174
|
+
|
|
175
|
+
"""
|
|
176
|
+
qubit_index = dag.find_bit(node.qargs[0]).index
|
|
177
|
+
if isinstance(node.op.params[0], float) and math.isclose(node.op.params[0], 0, abs_tol=TOLERANCE):
|
|
178
|
+
dag.remove_op_node(node)
|
|
179
|
+
else:
|
|
180
|
+
dag.substitute_node(
|
|
181
|
+
node,
|
|
182
|
+
RGate(
|
|
183
|
+
node.op.params[0],
|
|
184
|
+
np.pi / 2 - node.op.params[2] - rz_angles[qubit_index],
|
|
185
|
+
),
|
|
186
|
+
)
|
|
187
|
+
phase = node.op.params[1] + node.op.params[2]
|
|
188
|
+
dag.global_phase += phase / 2
|
|
189
|
+
rz_angles[qubit_index] += phase
|
|
190
|
+
return dag, rz_angles
|
|
191
|
+
|
|
192
|
+
def _handle_if_else_block(
|
|
193
|
+
self, dag: DAGCircuit, node: DAGOpNode, rz_angles: list[float]
|
|
194
|
+
) -> tuple[DAGCircuit, list[float]]:
|
|
195
|
+
"""Call the optimization recursively on both branches of the if_else node.
|
|
196
|
+
|
|
197
|
+
The accumulated RZ angles are added to both branches before optimizing them.
|
|
198
|
+
The accumulated RZ angles after the optimization are taken from the else branch
|
|
199
|
+
and the adjoint is applied to the if branch to correct for the overrotation.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
dag: The input DAG circuit we are optimizing.
|
|
203
|
+
node: The DAG node containing the if_else block to optimize.
|
|
204
|
+
rz_angles: The accumulated RZ angles for each qubit.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
The updated DAG circuit and the updated list of accumulated RZ angles.
|
|
208
|
+
|
|
209
|
+
"""
|
|
210
|
+
# Add the Rz angles to each circuit block of the if_else node
|
|
211
|
+
# and run this pass recursively
|
|
212
|
+
sub_dags = []
|
|
213
|
+
for circuit_block in node.op.params:
|
|
214
|
+
new_circuit = QuantumCircuit(list(node.qargs + node.cargs))
|
|
215
|
+
# Prepend Rz angle to circuit block
|
|
216
|
+
for qubit in node.qargs:
|
|
217
|
+
new_circuit.append(RGate(-np.pi, 0), [qubit])
|
|
218
|
+
new_circuit.append(RGate(np.pi, rz_angles[dag.find_bit(qubit).index] / 2), [qubit])
|
|
219
|
+
if circuit_block is not None:
|
|
220
|
+
new_circuit.compose(circuit_block, node.qargs, node.cargs, inplace=True)
|
|
221
|
+
# Run optimization pass on the block
|
|
222
|
+
block_dag = circuit_to_dag(new_circuit)
|
|
223
|
+
block_dag = self.run(block_dag, decompose_rz_to_r=False)
|
|
224
|
+
sub_dags.append(block_dag)
|
|
225
|
+
# Pick up the final rotation
|
|
226
|
+
for qubit in node.qargs:
|
|
227
|
+
# Find the last node on the qubit
|
|
228
|
+
final_rzs = [list(block_dag.nodes_on_wire(qubit, only_ops=True))[-1] for block_dag in sub_dags]
|
|
229
|
+
# Assertions because this cannot go wrong by user error
|
|
230
|
+
assert len(final_rzs) == 2, "IfElseOp should have exactly two circuit blocks"
|
|
231
|
+
assert final_rzs[0].name == "rz" and final_rzs[1].name == "rz", (
|
|
232
|
+
"The last operation on each qubit in an IfElseOp should be an RZ gate, "
|
|
233
|
+
+ f"found {final_rzs[0].name} and {final_rzs[1].name} instead"
|
|
234
|
+
)
|
|
235
|
+
# Extract the angles
|
|
236
|
+
rz1, rz2 = final_rzs[0].op.params[0], final_rzs[1].op.params[0]
|
|
237
|
+
# We take the else_block rotation as the one to continue pushing through the circuit
|
|
238
|
+
# because we don't support else_blocks in the circuit at the moment.
|
|
239
|
+
# Update the rz_angle on this qubit with the one found
|
|
240
|
+
rz_angles[dag.find_bit(qubit).index] = rz2
|
|
241
|
+
# Remove the final rz from the dag in both circuit blocks
|
|
242
|
+
for block_dag, final_node in zip(sub_dags, final_rzs):
|
|
243
|
+
block_dag.remove_op_node(final_node)
|
|
244
|
+
# Fix the overrotation of the if_block when the final Rz does not match
|
|
245
|
+
if not math.isclose(rz1, rz2):
|
|
246
|
+
rz_angle = rz1 - rz2
|
|
247
|
+
sub_dags[0].apply_operation_back(RGate(-np.pi, 0), qargs=(qubit,))
|
|
248
|
+
sub_dags[0].apply_operation_back(RGate(np.pi, rz_angle / 2), qargs=(qubit,))
|
|
249
|
+
# Replace the params in the if_else node with the optimized circuits
|
|
250
|
+
new_params = []
|
|
251
|
+
for idx, sub_dag in enumerate(sub_dags):
|
|
252
|
+
# Optimize the PRXs on the block_dag, but now keep the final Rzs
|
|
253
|
+
block_dag = IQMOptimizeSingleQubitGates(drop_final_rz=False, ignore_barriers=self._ignore_barriers).run(
|
|
254
|
+
sub_dag
|
|
255
|
+
)
|
|
256
|
+
# Ensure the qubits act on the same qubits as before
|
|
257
|
+
if node.op.params[idx] is not None and block_dag.qubits != node.op.params[idx].qubits:
|
|
258
|
+
# Sometimes the circuit_block.qubits != node.qargs,
|
|
259
|
+
# so we need to make sure that they act on the same qubits as before
|
|
260
|
+
new_circuit = QuantumCircuit(list(node.op.params[idx].qubits + node.op.params[idx].clbits))
|
|
261
|
+
new_circuit.compose(
|
|
262
|
+
dag_to_circuit(block_dag),
|
|
263
|
+
node.op.params[idx].qubits,
|
|
264
|
+
node.op.params[idx].clbits,
|
|
265
|
+
inplace=True,
|
|
266
|
+
)
|
|
267
|
+
else:
|
|
268
|
+
new_circuit = dag_to_circuit(block_dag)
|
|
269
|
+
new_params.append(new_circuit)
|
|
270
|
+
dag.substitute_node(
|
|
271
|
+
node,
|
|
272
|
+
IfElseOp(
|
|
273
|
+
node.op.condition,
|
|
274
|
+
new_params[0],
|
|
275
|
+
false_body=new_params[1] if new_params[1].size() > 0 else None,
|
|
276
|
+
label=node.op.label,
|
|
277
|
+
),
|
|
278
|
+
)
|
|
279
|
+
return dag, rz_angles
|
|
280
|
+
|
|
281
|
+
def _handle_c_if_blocks(self, dag: DAGCircuit) -> DAGCircuit:
|
|
282
|
+
"""Helper function that replaces all classically controlled RGates with an if_else operator.
|
|
283
|
+
|
|
284
|
+
This is needed because the BasisTranslator pass does not retain the condition on the nodes.
|
|
285
|
+
This is only needed for Qiskit versions < 2.0.0.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
dag: The input DAG circuit we are optimizing.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
The updated DAG circuit with if_else blocks instead of R gates with a condition.
|
|
292
|
+
|
|
293
|
+
"""
|
|
294
|
+
for node in dag.topological_op_nodes():
|
|
295
|
+
if hasattr(node, "condition") and node.condition and node.name != "if_else":
|
|
296
|
+
# Manually parse the node to a circuit because helper functions don't exist
|
|
297
|
+
# NOTE if_block needs to have the same size as node or else it cannot be replaced later.
|
|
298
|
+
if_block = QuantumCircuit(list(node.qargs))
|
|
299
|
+
# NOTE Need to reconstruct the node.op manually because rust panics when using node.op directly
|
|
300
|
+
if node.op.name != "r":
|
|
301
|
+
raise ValueError(
|
|
302
|
+
f"Unexpected operation '{node.name}' in circuit given to IQMOptimizeSingleQubitGates pass"
|
|
303
|
+
)
|
|
304
|
+
if_block.append(RGate(node.op.params[0], node.op.params[1], label=node.op.label), node.qargs)
|
|
305
|
+
new_op = IfElseOp(
|
|
306
|
+
node.condition,
|
|
307
|
+
if_block,
|
|
308
|
+
)
|
|
309
|
+
dag.substitute_node(
|
|
310
|
+
node,
|
|
311
|
+
new_op,
|
|
312
|
+
)
|
|
313
|
+
return dag
|
|
314
|
+
|
|
315
|
+
def _validate_ops(self, dag: DAGCircuit): # noqa: ANN202
|
|
316
|
+
"""Helper function that validates that the operations in the circuit are compatible
|
|
317
|
+
with the IQMOptimizeSingleQubitGates pass.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
dag: The input DAG circuit to validate before optimization.
|
|
321
|
+
|
|
322
|
+
Raises:
|
|
323
|
+
ValueError: If an invalid operation is found in the circuit.
|
|
324
|
+
|
|
325
|
+
"""
|
|
326
|
+
valid_ops = self._basis + ["measure", "reset", "delay", "barrier"]
|
|
327
|
+
for node in dag.op_nodes():
|
|
328
|
+
if node.name not in valid_ops:
|
|
329
|
+
raise ValueError(
|
|
330
|
+
f"Invalid operation '{node.name}' found in IQMOptimize1QbDecomposition pass, "
|
|
331
|
+
+ f"expected operations {valid_ops}"
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def optimize_single_qubit_gates(
|
|
336
|
+
circuit: QuantumCircuit, drop_final_rz: bool = True, ignore_barriers: bool = False
|
|
337
|
+
) -> QuantumCircuit:
|
|
338
|
+
"""Optimize number of single-qubit gates in a transpiled circuit exploiting the IQM specific gate set.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
circuit: quantum circuit to optimize
|
|
342
|
+
drop_final_rz: Drop terminal RZ gates even if there are no measurements following them (since they do not affect
|
|
343
|
+
the measurement results). Note that this will change the unitary propagator of the circuit.
|
|
344
|
+
It is recommended always to set this to true as the final RZ gates do no change the measurement outcomes of
|
|
345
|
+
the circuit.
|
|
346
|
+
ignore_barriers (bool): Removes barriers from the circuit if they exist (default = False) before optimization.
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
optimized circuit
|
|
350
|
+
|
|
351
|
+
"""
|
|
352
|
+
warnings.warn(
|
|
353
|
+
DeprecationWarning(
|
|
354
|
+
"This function is deprecated and will be removed in a later version of `iqm.qiskit_iqm`. "
|
|
355
|
+
+ "Single qubit gate optimization is now automatically applied when running `qiskit.transpile()` on any "
|
|
356
|
+
+ "IQM device. If you want to have more fine grained control over the optimization, please use the "
|
|
357
|
+
+ "`iqm.qiskit_iqm.transpile_to_IQM` function."
|
|
358
|
+
)
|
|
359
|
+
)
|
|
360
|
+
# Code not updated to use transpile_to_IQM due to circular imports
|
|
361
|
+
new_circuit = PassManager(IQMOptimizeSingleQubitGates(drop_final_rz, ignore_barriers)).run(circuit)
|
|
362
|
+
new_circuit._layout = circuit.layout
|
|
363
|
+
return new_circuit
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
class IQMReplaceGateWithUnitaryPass(TransformationPass):
|
|
367
|
+
"""Transpiler pass that replaces all gates with given name in a circuit with a UnitaryGate.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
gate: The name of the gate to replace.
|
|
371
|
+
unitary: The unitary matrix to replace the gate with.
|
|
372
|
+
|
|
373
|
+
"""
|
|
374
|
+
|
|
375
|
+
def __init__(self, gate: str, unitary: list[list[float]]):
|
|
376
|
+
super().__init__()
|
|
377
|
+
self.gate = gate
|
|
378
|
+
self.unitary = unitary
|
|
379
|
+
|
|
380
|
+
def run(self, dag): # noqa: ANN001, ANN201
|
|
381
|
+
for node in dag.op_nodes():
|
|
382
|
+
if node.name == self.gate:
|
|
383
|
+
dag.substitute_node(node, UnitaryGate(self.unitary))
|
|
384
|
+
return dag
|