qiskit 1.1.0rc1__cp38-abi3-win_amd64.whl → 1.1.2__cp38-abi3-win_amd64.whl
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.
- qiskit/VERSION.txt +1 -1
- qiskit/_accelerate.pyd +0 -0
- qiskit/assembler/__init__.py +5 -10
- qiskit/circuit/__init__.py +21 -153
- qiskit/circuit/_classical_resource_map.py +3 -0
- qiskit/circuit/classical/expr/__init__.py +1 -1
- qiskit/circuit/classical/types/__init__.py +5 -4
- qiskit/circuit/classicalfunction/__init__.py +9 -0
- qiskit/circuit/library/__init__.py +3 -19
- qiskit/circuit/library/data_preparation/pauli_feature_map.py +1 -1
- qiskit/circuit/library/n_local/two_local.py +1 -1
- qiskit/circuit/library/standard_gates/x.py +2 -0
- qiskit/circuit/parameterexpression.py +3 -0
- qiskit/circuit/parametervector.py +22 -16
- qiskit/circuit/quantumcircuit.py +1100 -200
- qiskit/converters/__init__.py +17 -2
- qiskit/dagcircuit/dagcircuit.py +8 -1
- qiskit/passmanager/passmanager.py +11 -11
- qiskit/primitives/__init__.py +15 -9
- qiskit/primitives/containers/__init__.py +1 -0
- qiskit/primitives/containers/bit_array.py +6 -2
- qiskit/primitives/containers/shape.py +3 -3
- qiskit/providers/__init__.py +49 -17
- qiskit/providers/backend.py +0 -6
- qiskit/providers/basic_provider/__init__.py +2 -23
- qiskit/providers/fake_provider/__init__.py +1 -1
- qiskit/providers/fake_provider/generic_backend_v2.py +5 -0
- qiskit/providers/models/__init__.py +2 -2
- qiskit/pulse/builder.py +2 -2
- qiskit/pulse/schedule.py +3 -3
- qiskit/qasm2/parse.py +8 -0
- qiskit/qasm3/exporter.py +2 -2
- qiskit/qobj/converters/pulse_instruction.py +6 -6
- qiskit/qpy/__init__.py +60 -62
- qiskit/qpy/binary_io/value.py +1 -1
- qiskit/quantum_info/operators/symplectic/pauli.py +18 -13
- qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py +8 -4
- qiskit/result/__init__.py +6 -0
- qiskit/scheduler/__init__.py +10 -1
- qiskit/scheduler/methods/__init__.py +1 -8
- qiskit/synthesis/__init__.py +1 -6
- qiskit/synthesis/discrete_basis/generate_basis_approximations.py +1 -1
- qiskit/synthesis/discrete_basis/solovay_kitaev.py +22 -12
- qiskit/transpiler/__init__.py +5 -5
- qiskit/transpiler/layout.py +3 -3
- qiskit/transpiler/passes/__init__.py +4 -2
- qiskit/transpiler/passes/basis/basis_translator.py +2 -2
- qiskit/transpiler/passes/layout/vf2_layout.py +10 -4
- qiskit/transpiler/passes/layout/vf2_utils.py +2 -2
- qiskit/transpiler/passes/optimization/collect_cliffords.py +6 -15
- qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py +8 -1
- qiskit/transpiler/passes/routing/commuting_2q_gate_routing/pauli_2q_evolution_commutation.py +5 -1
- qiskit/transpiler/passes/routing/star_prerouting.py +5 -5
- qiskit/transpiler/passes/synthesis/unitary_synthesis.py +3 -0
- qiskit/transpiler/preset_passmanagers/__init__.py +29 -3
- qiskit/transpiler/target.py +1 -1
- qiskit/utils/__init__.py +3 -2
- qiskit/utils/parallel.py +24 -15
- qiskit/visualization/bloch.py +44 -1
- qiskit/visualization/dag_visualization.py +10 -3
- qiskit/visualization/gate_map.py +9 -1
- qiskit/visualization/pass_manager_visualization.py +9 -9
- qiskit/visualization/pulse_v2/device_info.py +58 -31
- {qiskit-1.1.0rc1.dist-info → qiskit-1.1.2.dist-info}/METADATA +18 -18
- {qiskit-1.1.0rc1.dist-info → qiskit-1.1.2.dist-info}/RECORD +69 -69
- {qiskit-1.1.0rc1.dist-info → qiskit-1.1.2.dist-info}/WHEEL +1 -1
- {qiskit-1.1.0rc1.dist-info → qiskit-1.1.2.dist-info}/LICENSE.txt +0 -0
- {qiskit-1.1.0rc1.dist-info → qiskit-1.1.2.dist-info}/entry_points.txt +0 -0
- {qiskit-1.1.0rc1.dist-info → qiskit-1.1.2.dist-info}/top_level.txt +0 -0
@@ -16,8 +16,6 @@ from __future__ import annotations
|
|
16
16
|
|
17
17
|
import numpy as np
|
18
18
|
|
19
|
-
from qiskit.circuit.gate import Gate
|
20
|
-
|
21
19
|
from .gate_sequence import GateSequence
|
22
20
|
from .commutator_decompose import commutator_decompose
|
23
21
|
from .generate_basis_approximations import generate_basic_approximations, _1q_gates, _1q_inverses
|
@@ -53,14 +51,19 @@ class SolovayKitaevDecomposition:
|
|
53
51
|
|
54
52
|
self.basic_approximations = self.load_basic_approximations(basic_approximations)
|
55
53
|
|
56
|
-
|
54
|
+
@staticmethod
|
55
|
+
def load_basic_approximations(data: list | str | dict) -> list[GateSequence]:
|
57
56
|
"""Load basic approximations.
|
58
57
|
|
59
58
|
Args:
|
60
59
|
data: If a string, specifies the path to the file from where to load the data.
|
61
|
-
If a dictionary, directly specifies the decompositions as ``{gates: matrix}
|
62
|
-
There ``gates`` are the names of the gates
|
63
|
-
|
60
|
+
If a dictionary, directly specifies the decompositions as ``{gates: matrix}``
|
61
|
+
or ``{gates: (matrix, global_phase)}``. There, ``gates`` are the names of the gates
|
62
|
+
producing the SO(3) matrix ``matrix``, e.g.
|
63
|
+
``{"h t": np.array([[0, 0.7071, -0.7071], [0, -0.7071, -0.7071], [-1, 0, 0]]}``
|
64
|
+
and the ``global_phase`` can be given to account for a global phase difference
|
65
|
+
between the U(2) matrix of the quantum gates and the stored SO(3) matrix.
|
66
|
+
If not given, the ``global_phase`` will be assumed to be 0.
|
64
67
|
|
65
68
|
Returns:
|
66
69
|
A list of basic approximations as type ``GateSequence``.
|
@@ -74,13 +77,20 @@ class SolovayKitaevDecomposition:
|
|
74
77
|
|
75
78
|
# if a file, load the dictionary
|
76
79
|
if isinstance(data, str):
|
77
|
-
data = np.load(data, allow_pickle=True)
|
80
|
+
data = np.load(data, allow_pickle=True).item()
|
78
81
|
|
79
82
|
sequences = []
|
80
|
-
for gatestring,
|
83
|
+
for gatestring, matrix_and_phase in data.items():
|
84
|
+
if isinstance(matrix_and_phase, tuple):
|
85
|
+
matrix, global_phase = matrix_and_phase
|
86
|
+
else:
|
87
|
+
matrix, global_phase = matrix_and_phase, 0
|
88
|
+
|
81
89
|
sequence = GateSequence()
|
82
90
|
sequence.gates = [_1q_gates[element] for element in gatestring.split()]
|
91
|
+
sequence.labels = [gate.name for gate in sequence.gates]
|
83
92
|
sequence.product = np.asarray(matrix)
|
93
|
+
sequence.global_phase = global_phase
|
84
94
|
sequences.append(sequence)
|
85
95
|
|
86
96
|
return sequences
|
@@ -157,14 +167,14 @@ class SolovayKitaevDecomposition:
|
|
157
167
|
w_n1 = self._recurse(w_n, n - 1, check_input=check_input)
|
158
168
|
return v_n1.dot(w_n1).dot(v_n1.adjoint()).dot(w_n1.adjoint()).dot(u_n1)
|
159
169
|
|
160
|
-
def find_basic_approximation(self, sequence: GateSequence) ->
|
161
|
-
"""
|
170
|
+
def find_basic_approximation(self, sequence: GateSequence) -> GateSequence:
|
171
|
+
"""Find ``GateSequence`` in ``self._basic_approximations`` that approximates ``sequence``.
|
162
172
|
|
163
173
|
Args:
|
164
|
-
sequence:
|
174
|
+
sequence: ``GateSequence`` to find the approximation to.
|
165
175
|
|
166
176
|
Returns:
|
167
|
-
|
177
|
+
``GateSequence`` in ``self._basic_approximations`` that approximates ``sequence``.
|
168
178
|
"""
|
169
179
|
# TODO explore using a k-d tree here
|
170
180
|
|
qiskit/transpiler/__init__.py
CHANGED
@@ -650,8 +650,6 @@ manner to the "physical" qubits in an actual quantum device.
|
|
650
650
|
.. image:: /source_images/mapping.png
|
651
651
|
|
652
652
|
|
653
|
-
|
654
|
-
|
655
653
|
By default, qiskit will do this mapping for you. The choice of mapping depends on the
|
656
654
|
properties of the circuit, the particular device you are targeting, and the optimization
|
657
655
|
level that is chosen. The choice of initial layout is extremely important for minimizing the
|
@@ -684,10 +682,12 @@ Next, for the heuristic stage, 2 passes are used by default:
|
|
684
682
|
:class:`~.SabreLayout` is used to select a layout if a perfect layout isn't found for
|
685
683
|
optimization levels 1, 2, and 3.
|
686
684
|
- :class:`~.TrivialLayout`: Always used for the layout at optimization level 0.
|
685
|
+
|
686
|
+
There are other passes than can be used for the heuristic stage, but are not included in the default
|
687
|
+
pipeline, such as:
|
688
|
+
|
687
689
|
- :class:`~.DenseLayout`: Finds the sub-graph of the device with greatest connectivity
|
688
|
-
that has the same number of qubits as the circuit.
|
689
|
-
optimization level 1 if there are control flow operations (such as
|
690
|
-
:class:`~.IfElseOp`) present in the circuit.
|
690
|
+
that has the same number of qubits as the circuit.
|
691
691
|
|
692
692
|
Let's see what layouts are automatically picked at various optimization levels. The circuits
|
693
693
|
returned by :func:`qiskit.compiler.transpile` are annotated with this initial layout information,
|
qiskit/transpiler/layout.py
CHANGED
@@ -454,7 +454,7 @@ class TranspileLayout:
|
|
454
454
|
qubits in the circuit as it fits the circuit to the target backend. For example,
|
455
455
|
let the input circuit be:
|
456
456
|
|
457
|
-
.. plot
|
457
|
+
.. plot::
|
458
458
|
:include-source:
|
459
459
|
|
460
460
|
from qiskit.circuit import QuantumCircuit, QuantumRegister
|
@@ -469,7 +469,7 @@ class TranspileLayout:
|
|
469
469
|
|
470
470
|
Suppose that during the layout stage the transpiler reorders the qubits to be:
|
471
471
|
|
472
|
-
.. plot
|
472
|
+
.. plot::
|
473
473
|
:include-source:
|
474
474
|
|
475
475
|
from qiskit import QuantumCircuit
|
@@ -497,7 +497,7 @@ class TranspileLayout:
|
|
497
497
|
the transpiler needs to insert swap gates, and the output circuit
|
498
498
|
becomes:
|
499
499
|
|
500
|
-
.. plot
|
500
|
+
.. plot::
|
501
501
|
:include-source:
|
502
502
|
|
503
503
|
from qiskit import QuantumCircuit
|
@@ -154,8 +154,10 @@ The synthesis transpiler plugin documentation can be found in the
|
|
154
154
|
HLSConfig
|
155
155
|
SolovayKitaev
|
156
156
|
|
157
|
-
Post Layout
|
158
|
-
|
157
|
+
Post Layout
|
158
|
+
===========
|
159
|
+
|
160
|
+
These are post qubit selection.
|
159
161
|
|
160
162
|
.. autosummary::
|
161
163
|
:toctree: ../stubs/
|
@@ -97,8 +97,8 @@ class BasisTranslator(TransformationPass):
|
|
97
97
|
|
98
98
|
When this error occurs it typically means that either the target basis
|
99
99
|
is not universal or there are additional equivalence rules needed in the
|
100
|
-
:
|
101
|
-
:class
|
100
|
+
:class:`~.EquivalenceLibrary` instance being used by the
|
101
|
+
:class:`~.BasisTranslator` pass. You can refer to
|
102
102
|
:ref:`custom_basis_gates` for details on adding custom equivalence rules.
|
103
103
|
"""
|
104
104
|
|
@@ -104,15 +104,21 @@ class VF2Layout(AnalysisPass):
|
|
104
104
|
limit on the number of trials will be set.
|
105
105
|
target (Target): A target representing the backend device to run ``VF2Layout`` on.
|
106
106
|
If specified it will supersede a set value for ``properties`` and
|
107
|
-
``coupling_map
|
107
|
+
``coupling_map`` if the :class:`.Target` contains connectivity constraints. If the value
|
108
|
+
of ``target`` models an ideal backend without any constraints then the value of
|
109
|
+
``coupling_map``
|
110
|
+
will be used.
|
108
111
|
|
109
112
|
Raises:
|
110
113
|
TypeError: At runtime, if neither ``coupling_map`` or ``target`` are provided.
|
111
114
|
"""
|
112
115
|
super().__init__()
|
113
116
|
self.target = target
|
114
|
-
if
|
115
|
-
|
117
|
+
if (
|
118
|
+
target is not None
|
119
|
+
and (target_coupling_map := self.target.build_coupling_map()) is not None
|
120
|
+
):
|
121
|
+
self.coupling_map = target_coupling_map
|
116
122
|
else:
|
117
123
|
self.coupling_map = coupling_map
|
118
124
|
self.properties = properties
|
@@ -145,7 +151,7 @@ class VF2Layout(AnalysisPass):
|
|
145
151
|
)
|
146
152
|
# Filter qubits without any supported operations. If they don't support any operations
|
147
153
|
# They're not valid for layout selection
|
148
|
-
if self.target is not None:
|
154
|
+
if self.target is not None and self.target.qargs is not None:
|
149
155
|
has_operations = set(itertools.chain.from_iterable(self.target.qargs))
|
150
156
|
to_remove = set(range(len(cm_nodes))).difference(has_operations)
|
151
157
|
if to_remove:
|
@@ -145,7 +145,7 @@ def score_layout(
|
|
145
145
|
def build_average_error_map(target, properties, coupling_map):
|
146
146
|
"""Build an average error map used for scoring layouts pre-basis translation."""
|
147
147
|
num_qubits = 0
|
148
|
-
if target is not None:
|
148
|
+
if target is not None and target.qargs is not None:
|
149
149
|
num_qubits = target.num_qubits
|
150
150
|
avg_map = ErrorMap(len(target.qargs))
|
151
151
|
elif coupling_map is not None:
|
@@ -157,7 +157,7 @@ def build_average_error_map(target, properties, coupling_map):
|
|
157
157
|
# object
|
158
158
|
avg_map = ErrorMap(0)
|
159
159
|
built = False
|
160
|
-
if target is not None:
|
160
|
+
if target is not None and target.qargs is not None:
|
161
161
|
for qargs in target.qargs:
|
162
162
|
if qargs is None:
|
163
163
|
continue
|
@@ -22,6 +22,7 @@ from qiskit.transpiler.passes.optimization.collect_and_collapse import (
|
|
22
22
|
)
|
23
23
|
|
24
24
|
from qiskit.quantum_info.operators import Clifford
|
25
|
+
from qiskit.quantum_info.operators.symplectic.clifford_circuits import _BASIS_1Q, _BASIS_2Q
|
25
26
|
|
26
27
|
|
27
28
|
class CollectCliffords(CollectAndCollapse):
|
@@ -69,21 +70,11 @@ class CollectCliffords(CollectAndCollapse):
|
|
69
70
|
)
|
70
71
|
|
71
72
|
|
72
|
-
clifford_gate_names =
|
73
|
-
|
74
|
-
|
75
|
-
"
|
76
|
-
|
77
|
-
"s",
|
78
|
-
"sdg",
|
79
|
-
"cx",
|
80
|
-
"cy",
|
81
|
-
"cz",
|
82
|
-
"swap",
|
83
|
-
"clifford",
|
84
|
-
"linear_function",
|
85
|
-
"pauli",
|
86
|
-
]
|
73
|
+
clifford_gate_names = (
|
74
|
+
list(_BASIS_1Q.keys())
|
75
|
+
+ list(_BASIS_2Q.keys())
|
76
|
+
+ ["clifford", "linear_function", "pauli", "permutation"]
|
77
|
+
)
|
87
78
|
|
88
79
|
|
89
80
|
def _is_clifford_gate(node):
|
@@ -160,8 +160,13 @@ class Commuting2qGateRouter(TransformationPass):
|
|
160
160
|
if len(dag.qubits) != next(iter(dag.qregs.values())).size:
|
161
161
|
raise TranspilerError("Circuit has qubits not contained in the qubit register.")
|
162
162
|
|
163
|
-
|
163
|
+
# Fix output permutation -- copied from ElidePermutations
|
164
|
+
input_qubit_mapping = {qubit: index for index, qubit in enumerate(dag.qubits)}
|
165
|
+
self.property_set["original_layout"] = Layout(input_qubit_mapping)
|
166
|
+
if self.property_set["original_qubit_indices"] is None:
|
167
|
+
self.property_set["original_qubit_indices"] = input_qubit_mapping
|
164
168
|
|
169
|
+
new_dag = dag.copy_empty_like()
|
165
170
|
current_layout = Layout.generate_trivial_layout(*dag.qregs.values())
|
166
171
|
|
167
172
|
# Used to keep track of nodes that do not decompose using swap strategies.
|
@@ -183,6 +188,8 @@ class Commuting2qGateRouter(TransformationPass):
|
|
183
188
|
|
184
189
|
self._compose_non_swap_nodes(accumulator, current_layout, new_dag)
|
185
190
|
|
191
|
+
self.property_set["virtual_permutation_layout"] = current_layout
|
192
|
+
|
186
193
|
return new_dag
|
187
194
|
|
188
195
|
def _compose_non_swap_nodes(
|
qiskit/transpiler/passes/routing/commuting_2q_gate_routing/pauli_2q_evolution_commutation.py
CHANGED
@@ -51,7 +51,11 @@ class FindCommutingPauliEvolutions(TransformationPass):
|
|
51
51
|
sub_dag = self._decompose_to_2q(dag, node.op)
|
52
52
|
|
53
53
|
block_op = Commuting2qBlock(set(sub_dag.op_nodes()))
|
54
|
-
wire_order = {
|
54
|
+
wire_order = {
|
55
|
+
wire: idx
|
56
|
+
for idx, wire in enumerate(sub_dag.qubits)
|
57
|
+
if wire not in sub_dag.idle_wires()
|
58
|
+
}
|
55
59
|
dag.replace_block_with_op([node], block_op, wire_order)
|
56
60
|
|
57
61
|
return dag
|
@@ -329,13 +329,13 @@ class StarPreRouting(TransformationPass):
|
|
329
329
|
last_2q_gate = None
|
330
330
|
|
331
331
|
int_digits = floor(log10(len(processing_order))) + 1
|
332
|
-
|
332
|
+
processing_order_index_map = {
|
333
|
+
node: f"a{str(index).zfill(int(int_digits))}"
|
334
|
+
for index, node in enumerate(processing_order)
|
335
|
+
}
|
333
336
|
|
334
337
|
def tie_breaker_key(node):
|
335
|
-
|
336
|
-
return "a" + str(processing_order.index(node)).zfill(int(int_digits))
|
337
|
-
else:
|
338
|
-
return node.sort_key
|
338
|
+
return processing_order_index_map.get(node, node.sort_key)
|
339
339
|
|
340
340
|
for node in dag.topological_op_nodes(key=tie_breaker_key):
|
341
341
|
block_id = node_to_block_id.get(node, None)
|
@@ -49,6 +49,7 @@ from qiskit.circuit.library.standard_gates import (
|
|
49
49
|
CZGate,
|
50
50
|
RXXGate,
|
51
51
|
RZXGate,
|
52
|
+
RZZGate,
|
52
53
|
ECRGate,
|
53
54
|
)
|
54
55
|
from qiskit.transpiler.passes.synthesis import plugin
|
@@ -744,6 +745,8 @@ class DefaultUnitarySynthesis(plugin.UnitarySynthesisPlugin):
|
|
744
745
|
op = RXXGate(pi / 2)
|
745
746
|
elif isinstance(op, RZXGate) and isinstance(op.params[0], Parameter):
|
746
747
|
op = RZXGate(pi / 4)
|
748
|
+
elif isinstance(op, RZZGate) and isinstance(op.params[0], Parameter):
|
749
|
+
op = RZZGate(pi / 2)
|
747
750
|
return op
|
748
751
|
|
749
752
|
try:
|
@@ -96,11 +96,37 @@ def generate_preset_pass_manager(
|
|
96
96
|
):
|
97
97
|
"""Generate a preset :class:`~.PassManager`
|
98
98
|
|
99
|
-
This function is used to quickly generate a preset pass manager.
|
100
|
-
|
99
|
+
This function is used to quickly generate a preset pass manager. Preset pass
|
100
|
+
managers are the default pass managers used by the :func:`~.transpile`
|
101
101
|
function. This function provides a convenient and simple method to construct
|
102
|
-
a standalone :class:`~.PassManager` object that mirrors what the transpile
|
102
|
+
a standalone :class:`~.PassManager` object that mirrors what the :func:`~.transpile`
|
103
|
+
function internally builds and uses.
|
103
104
|
|
105
|
+
The target constraints for the pass manager construction can be specified through a :class:`.Target`
|
106
|
+
instance, a :class:`.BackendV1` or :class:`.BackendV2` instance, or via loose constraints
|
107
|
+
(``basis_gates``, ``inst_map``, ``coupling_map``, ``backend_properties``, ``instruction_durations``,
|
108
|
+
``dt`` or ``timing_constraints``).
|
109
|
+
The order of priorities for target constraints works as follows: if a ``target``
|
110
|
+
input is provided, it will take priority over any ``backend`` input or loose constraints.
|
111
|
+
If a ``backend`` is provided together with any loose constraint
|
112
|
+
from the list above, the loose constraint will take priority over the corresponding backend
|
113
|
+
constraint. This behavior is independent of whether the ``backend`` instance is of type
|
114
|
+
:class:`.BackendV1` or :class:`.BackendV2`, as summarized in the table below. The first column
|
115
|
+
in the table summarizes the potential user-provided constraints, and each cell shows whether
|
116
|
+
the priority is assigned to that specific constraint input or another input
|
117
|
+
(`target`/`backend(V1)`/`backend(V2)`).
|
118
|
+
|
119
|
+
============================ ========= ======================== =======================
|
120
|
+
User Provided target backend(V1) backend(V2)
|
121
|
+
============================ ========= ======================== =======================
|
122
|
+
**basis_gates** target basis_gates basis_gates
|
123
|
+
**coupling_map** target coupling_map coupling_map
|
124
|
+
**instruction_durations** target instruction_durations instruction_durations
|
125
|
+
**inst_map** target inst_map inst_map
|
126
|
+
**dt** target dt dt
|
127
|
+
**timing_constraints** target timing_constraints timing_constraints
|
128
|
+
**backend_properties** target backend_properties backend_properties
|
129
|
+
============================ ========= ======================== =======================
|
104
130
|
|
105
131
|
Args:
|
106
132
|
optimization_level (int): The optimization level to generate a
|
qiskit/transpiler/target.py
CHANGED
@@ -890,7 +890,7 @@ class Target(Mapping):
|
|
890
890
|
return False
|
891
891
|
if qargs not in self._gate_map[operation_name]:
|
892
892
|
return False
|
893
|
-
return getattr(self._gate_map[operation_name][qargs], "_calibration") is not None
|
893
|
+
return getattr(self._gate_map[operation_name][qargs], "_calibration", None) is not None
|
894
894
|
|
895
895
|
def get_calibration(
|
896
896
|
self,
|
qiskit/utils/__init__.py
CHANGED
@@ -44,7 +44,7 @@ Multiprocessing
|
|
44
44
|
.. autofunction:: local_hardware_info
|
45
45
|
.. autofunction:: is_main_process
|
46
46
|
|
47
|
-
A helper function for calling a custom function with
|
47
|
+
A helper function for calling a custom function with Python
|
48
48
|
:class:`~concurrent.futures.ProcessPoolExecutor`. Tasks can be executed in parallel using this function.
|
49
49
|
|
50
50
|
.. autofunction:: parallel_map
|
@@ -70,7 +70,7 @@ from .lazy_tester import LazyDependencyManager, LazyImportTester, LazySubprocess
|
|
70
70
|
|
71
71
|
from . import optionals
|
72
72
|
|
73
|
-
from .parallel import parallel_map
|
73
|
+
from .parallel import parallel_map, should_run_in_parallel
|
74
74
|
|
75
75
|
__all__ = [
|
76
76
|
"LazyDependencyManager",
|
@@ -85,4 +85,5 @@ __all__ = [
|
|
85
85
|
"is_main_process",
|
86
86
|
"apply_prefix",
|
87
87
|
"parallel_map",
|
88
|
+
"should_run_in_parallel",
|
88
89
|
]
|
qiskit/utils/parallel.py
CHANGED
@@ -48,6 +48,8 @@ Routines for running Python functions in parallel using process pools
|
|
48
48
|
from the multiprocessing library.
|
49
49
|
"""
|
50
50
|
|
51
|
+
from __future__ import annotations
|
52
|
+
|
51
53
|
import os
|
52
54
|
from concurrent.futures import ProcessPoolExecutor
|
53
55
|
import sys
|
@@ -101,6 +103,21 @@ def _task_wrapper(param):
|
|
101
103
|
return task(value, *task_args, **task_kwargs)
|
102
104
|
|
103
105
|
|
106
|
+
def should_run_in_parallel(num_processes: int | None = None) -> bool:
|
107
|
+
"""Return whether the current parallelisation configuration suggests that we should run things
|
108
|
+
like :func:`parallel_map` in parallel (``True``) or degrade to serial (``False``).
|
109
|
+
|
110
|
+
Args:
|
111
|
+
num_processes: the number of processes requested for use (if given).
|
112
|
+
"""
|
113
|
+
num_processes = CPU_COUNT if num_processes is None else num_processes
|
114
|
+
return (
|
115
|
+
num_processes > 1
|
116
|
+
and os.getenv("QISKIT_IN_PARALLEL", "FALSE") == "FALSE"
|
117
|
+
and CONFIG.get("parallel_enabled", PARALLEL_DEFAULT)
|
118
|
+
)
|
119
|
+
|
120
|
+
|
104
121
|
def parallel_map( # pylint: disable=dangerous-default-value
|
105
122
|
task, values, task_args=(), task_kwargs={}, num_processes=CPU_COUNT
|
106
123
|
):
|
@@ -110,21 +127,20 @@ def parallel_map( # pylint: disable=dangerous-default-value
|
|
110
127
|
|
111
128
|
result = [task(value, *task_args, **task_kwargs) for value in values]
|
112
129
|
|
113
|
-
|
114
|
-
|
130
|
+
This will parallelise the results if the number of ``values`` is greater than one, and the
|
131
|
+
current system configuration permits parallelization.
|
115
132
|
|
116
133
|
Args:
|
117
134
|
task (func): Function that is to be called for each value in ``values``.
|
118
|
-
values (array_like): List or array of values for which the ``task``
|
119
|
-
|
135
|
+
values (array_like): List or array of values for which the ``task`` function is to be
|
136
|
+
evaluated.
|
120
137
|
task_args (list): Optional additional arguments to the ``task`` function.
|
121
138
|
task_kwargs (dict): Optional additional keyword argument to the ``task`` function.
|
122
139
|
num_processes (int): Number of processes to spawn.
|
123
140
|
|
124
141
|
Returns:
|
125
|
-
result: The result list contains the value of
|
126
|
-
|
127
|
-
each value in ``values``.
|
142
|
+
result: The result list contains the value of ``task(value, *task_args, **task_kwargs)`` for
|
143
|
+
each value in ``values``.
|
128
144
|
|
129
145
|
Raises:
|
130
146
|
QiskitError: If user interrupts via keyboard.
|
@@ -147,12 +163,7 @@ def parallel_map( # pylint: disable=dangerous-default-value
|
|
147
163
|
if len(values) == 1:
|
148
164
|
return [task(values[0], *task_args, **task_kwargs)]
|
149
165
|
|
150
|
-
|
151
|
-
if (
|
152
|
-
num_processes > 1
|
153
|
-
and os.getenv("QISKIT_IN_PARALLEL") == "FALSE"
|
154
|
-
and CONFIG.get("parallel_enabled", PARALLEL_DEFAULT)
|
155
|
-
):
|
166
|
+
if should_run_in_parallel(num_processes):
|
156
167
|
os.environ["QISKIT_IN_PARALLEL"] = "TRUE"
|
157
168
|
try:
|
158
169
|
results = []
|
@@ -173,8 +184,6 @@ def parallel_map( # pylint: disable=dangerous-default-value
|
|
173
184
|
os.environ["QISKIT_IN_PARALLEL"] = "FALSE"
|
174
185
|
return results
|
175
186
|
|
176
|
-
# Cannot do parallel on Windows , if another parallel_map is running in parallel,
|
177
|
-
# or len(values) == 1.
|
178
187
|
results = []
|
179
188
|
for _, value in enumerate(values):
|
180
189
|
result = task(value, *task_args, **task_kwargs)
|
qiskit/visualization/bloch.py
CHANGED
@@ -50,6 +50,7 @@ __all__ = ["Bloch"]
|
|
50
50
|
|
51
51
|
import math
|
52
52
|
import os
|
53
|
+
import re
|
53
54
|
import numpy as np
|
54
55
|
import matplotlib
|
55
56
|
import matplotlib.pyplot as plt
|
@@ -60,6 +61,47 @@ from mpl_toolkits.mplot3d.art3d import Patch3D
|
|
60
61
|
from .utils import matplotlib_close_if_inline
|
61
62
|
|
62
63
|
|
64
|
+
# This version pattern is taken from the pypa packaging project:
|
65
|
+
# https://github.com/pypa/packaging/blob/21.3/packaging/version.py#L223-L254
|
66
|
+
# which is dual licensed Apache 2.0 and BSD see the source for the original
|
67
|
+
# authors and other details
|
68
|
+
VERSION_PATTERN = (
|
69
|
+
"^"
|
70
|
+
+ r"""
|
71
|
+
v?
|
72
|
+
(?:
|
73
|
+
(?:(?P<epoch>[0-9]+)!)? # epoch
|
74
|
+
(?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
|
75
|
+
(?P<pre> # pre-release
|
76
|
+
[-_\.]?
|
77
|
+
(?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
|
78
|
+
[-_\.]?
|
79
|
+
(?P<pre_n>[0-9]+)?
|
80
|
+
)?
|
81
|
+
(?P<post> # post release
|
82
|
+
(?:-(?P<post_n1>[0-9]+))
|
83
|
+
|
|
84
|
+
(?:
|
85
|
+
[-_\.]?
|
86
|
+
(?P<post_l>post|rev|r)
|
87
|
+
[-_\.]?
|
88
|
+
(?P<post_n2>[0-9]+)?
|
89
|
+
)
|
90
|
+
)?
|
91
|
+
(?P<dev> # dev release
|
92
|
+
[-_\.]?
|
93
|
+
(?P<dev_l>dev)
|
94
|
+
[-_\.]?
|
95
|
+
(?P<dev_n>[0-9]+)?
|
96
|
+
)?
|
97
|
+
)
|
98
|
+
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
|
99
|
+
"""
|
100
|
+
+ "$"
|
101
|
+
)
|
102
|
+
VERSION_PATTERN_REGEX = re.compile(VERSION_PATTERN, re.VERBOSE | re.IGNORECASE)
|
103
|
+
|
104
|
+
|
63
105
|
class Arrow3D(Patch3D, FancyArrowPatch):
|
64
106
|
"""Makes a fancy arrow"""
|
65
107
|
|
@@ -419,7 +461,8 @@ class Bloch:
|
|
419
461
|
self.fig = plt.figure(figsize=self.figsize)
|
420
462
|
|
421
463
|
if not self._ext_axes:
|
422
|
-
|
464
|
+
version_match = VERSION_PATTERN_REGEX.search(matplotlib.__version__)
|
465
|
+
if tuple(int(x) for x in version_match.group("release").split(".")) >= (3, 4, 0):
|
423
466
|
self.axes = Axes3D(
|
424
467
|
self.fig, azim=self.view[0], elev=self.view[1], auto_add_to_figure=False
|
425
468
|
)
|
@@ -174,10 +174,13 @@ def dag_drawer(dag, scale=0.7, filename=None, style="color"):
|
|
174
174
|
label = register_bit_labels.get(
|
175
175
|
node.wire, f"q_{dag.find_bit(node.wire).index}"
|
176
176
|
)
|
177
|
-
|
177
|
+
elif isinstance(node.wire, Clbit):
|
178
178
|
label = register_bit_labels.get(
|
179
179
|
node.wire, f"c_{dag.find_bit(node.wire).index}"
|
180
180
|
)
|
181
|
+
else:
|
182
|
+
label = str(node.wire.name)
|
183
|
+
|
181
184
|
n["label"] = label
|
182
185
|
n["color"] = "black"
|
183
186
|
n["style"] = "filled"
|
@@ -187,10 +190,12 @@ def dag_drawer(dag, scale=0.7, filename=None, style="color"):
|
|
187
190
|
label = register_bit_labels.get(
|
188
191
|
node.wire, f"q[{dag.find_bit(node.wire).index}]"
|
189
192
|
)
|
190
|
-
|
193
|
+
elif isinstance(node.wire, Clbit):
|
191
194
|
label = register_bit_labels.get(
|
192
195
|
node.wire, f"c[{dag.find_bit(node.wire).index}]"
|
193
196
|
)
|
197
|
+
else:
|
198
|
+
label = str(node.wire.name)
|
194
199
|
n["label"] = label
|
195
200
|
n["color"] = "black"
|
196
201
|
n["style"] = "filled"
|
@@ -203,8 +208,10 @@ def dag_drawer(dag, scale=0.7, filename=None, style="color"):
|
|
203
208
|
e = {}
|
204
209
|
if isinstance(edge, Qubit):
|
205
210
|
label = register_bit_labels.get(edge, f"q_{dag.find_bit(edge).index}")
|
206
|
-
|
211
|
+
elif isinstance(edge, Clbit):
|
207
212
|
label = register_bit_labels.get(edge, f"c_{dag.find_bit(edge).index}")
|
213
|
+
else:
|
214
|
+
label = str(edge.name)
|
208
215
|
e["label"] = label
|
209
216
|
return e
|
210
217
|
|
qiskit/visualization/gate_map.py
CHANGED
@@ -1039,7 +1039,9 @@ def plot_coupling_map(
|
|
1039
1039
|
graph = CouplingMap(coupling_map).graph
|
1040
1040
|
|
1041
1041
|
if not plot_directed:
|
1042
|
+
line_color_map = dict(zip(graph.edge_list(), line_color))
|
1042
1043
|
graph = graph.to_undirected(multigraph=False)
|
1044
|
+
line_color = [line_color_map[edge] for edge in graph.edge_list()]
|
1043
1045
|
|
1044
1046
|
for node in graph.node_indices():
|
1045
1047
|
graph[node] = node
|
@@ -1122,7 +1124,13 @@ def plot_circuit_layout(circuit, backend, view="virtual", qubit_coordinates=None
|
|
1122
1124
|
Args:
|
1123
1125
|
circuit (QuantumCircuit): Input quantum circuit.
|
1124
1126
|
backend (Backend): Target backend.
|
1125
|
-
view (str):
|
1127
|
+
view (str): How to label qubits in the layout. Options:
|
1128
|
+
|
1129
|
+
- ``"virtual"``: Label each qubit with the index of the virtual qubit that
|
1130
|
+
mapped to it.
|
1131
|
+
- ``"physical"``: Label each qubit with the index of the physical qubit that it
|
1132
|
+
corresponds to on the device.
|
1133
|
+
|
1126
1134
|
qubit_coordinates (Sequence): An optional sequence input (list or array being the
|
1127
1135
|
most common) of 2d coordinates for each qubit. The length of the
|
1128
1136
|
sequence must match the number of qubits on the backend. The sequence
|