qiskit 2.0.3__cp39-abi3-macosx_11_0_arm64.whl → 2.1.0__cp39-abi3-macosx_11_0_arm64.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/__init__.py +19 -1
- qiskit/_accelerate.abi3.so +0 -0
- qiskit/circuit/__init__.py +104 -20
- qiskit/circuit/_add_control.py +57 -31
- qiskit/circuit/_classical_resource_map.py +4 -0
- qiskit/circuit/annotation.py +504 -0
- qiskit/circuit/classical/expr/__init__.py +1 -1
- qiskit/circuit/classical/expr/expr.py +104 -446
- qiskit/circuit/classical/expr/visitors.py +6 -0
- qiskit/circuit/classical/types/types.py +7 -130
- qiskit/circuit/controlflow/box.py +32 -7
- qiskit/circuit/delay.py +11 -9
- qiskit/circuit/library/arithmetic/adders/adder.py +4 -4
- qiskit/circuit/library/arithmetic/multipliers/multiplier.py +2 -2
- qiskit/circuit/library/arithmetic/piecewise_chebyshev.py +8 -4
- qiskit/circuit/library/arithmetic/piecewise_linear_pauli_rotations.py +23 -15
- qiskit/circuit/library/arithmetic/piecewise_polynomial_pauli_rotations.py +22 -14
- qiskit/circuit/library/arithmetic/quadratic_form.py +6 -0
- qiskit/circuit/library/arithmetic/weighted_adder.py +43 -24
- qiskit/circuit/library/basis_change/qft.py +2 -2
- qiskit/circuit/library/blueprintcircuit.py +6 -0
- qiskit/circuit/library/boolean_logic/inner_product.py +2 -2
- qiskit/circuit/library/boolean_logic/quantum_and.py +2 -2
- qiskit/circuit/library/boolean_logic/quantum_or.py +3 -3
- qiskit/circuit/library/boolean_logic/quantum_xor.py +2 -2
- qiskit/circuit/library/data_preparation/_z_feature_map.py +2 -2
- qiskit/circuit/library/data_preparation/_zz_feature_map.py +2 -2
- qiskit/circuit/library/data_preparation/pauli_feature_map.py +2 -2
- qiskit/circuit/library/fourier_checking.py +2 -2
- qiskit/circuit/library/generalized_gates/diagonal.py +5 -1
- qiskit/circuit/library/generalized_gates/gms.py +5 -1
- qiskit/circuit/library/generalized_gates/linear_function.py +2 -2
- qiskit/circuit/library/generalized_gates/permutation.py +5 -1
- qiskit/circuit/library/generalized_gates/uc.py +1 -1
- qiskit/circuit/library/generalized_gates/unitary.py +21 -2
- qiskit/circuit/library/graph_state.py +2 -2
- qiskit/circuit/library/grover_operator.py +2 -2
- qiskit/circuit/library/hidden_linear_function.py +2 -2
- qiskit/circuit/library/iqp.py +2 -2
- qiskit/circuit/library/n_local/efficient_su2.py +2 -2
- qiskit/circuit/library/n_local/evolved_operator_ansatz.py +1 -1
- qiskit/circuit/library/n_local/excitation_preserving.py +7 -9
- qiskit/circuit/library/n_local/n_local.py +4 -3
- qiskit/circuit/library/n_local/pauli_two_design.py +2 -2
- qiskit/circuit/library/n_local/real_amplitudes.py +2 -2
- qiskit/circuit/library/n_local/two_local.py +2 -2
- qiskit/circuit/library/overlap.py +2 -2
- qiskit/circuit/library/pauli_evolution.py +3 -2
- qiskit/circuit/library/phase_estimation.py +2 -2
- qiskit/circuit/library/standard_gates/dcx.py +11 -12
- qiskit/circuit/library/standard_gates/ecr.py +21 -24
- qiskit/circuit/library/standard_gates/equivalence_library.py +232 -96
- qiskit/circuit/library/standard_gates/global_phase.py +5 -6
- qiskit/circuit/library/standard_gates/h.py +22 -45
- qiskit/circuit/library/standard_gates/i.py +1 -1
- qiskit/circuit/library/standard_gates/iswap.py +13 -31
- qiskit/circuit/library/standard_gates/p.py +19 -26
- qiskit/circuit/library/standard_gates/r.py +11 -17
- qiskit/circuit/library/standard_gates/rx.py +21 -45
- qiskit/circuit/library/standard_gates/rxx.py +7 -22
- qiskit/circuit/library/standard_gates/ry.py +21 -39
- qiskit/circuit/library/standard_gates/ryy.py +13 -28
- qiskit/circuit/library/standard_gates/rz.py +18 -35
- qiskit/circuit/library/standard_gates/rzx.py +7 -22
- qiskit/circuit/library/standard_gates/rzz.py +7 -19
- qiskit/circuit/library/standard_gates/s.py +44 -39
- qiskit/circuit/library/standard_gates/swap.py +25 -38
- qiskit/circuit/library/standard_gates/sx.py +34 -41
- qiskit/circuit/library/standard_gates/t.py +18 -27
- qiskit/circuit/library/standard_gates/u.py +8 -24
- qiskit/circuit/library/standard_gates/u1.py +28 -52
- qiskit/circuit/library/standard_gates/u2.py +9 -9
- qiskit/circuit/library/standard_gates/u3.py +24 -40
- qiskit/circuit/library/standard_gates/x.py +190 -336
- qiskit/circuit/library/standard_gates/xx_minus_yy.py +12 -50
- qiskit/circuit/library/standard_gates/xx_plus_yy.py +13 -52
- qiskit/circuit/library/standard_gates/y.py +19 -23
- qiskit/circuit/library/standard_gates/z.py +31 -38
- qiskit/circuit/parameter.py +14 -5
- qiskit/circuit/parameterexpression.py +109 -75
- qiskit/circuit/quantumcircuit.py +172 -99
- qiskit/circuit/quantumcircuitdata.py +1 -0
- qiskit/circuit/random/__init__.py +37 -2
- qiskit/circuit/random/utils.py +445 -56
- qiskit/circuit/tools/pi_check.py +5 -13
- qiskit/compiler/transpiler.py +1 -1
- qiskit/converters/circuit_to_instruction.py +2 -2
- qiskit/dagcircuit/dagnode.py +8 -3
- qiskit/primitives/__init__.py +2 -2
- qiskit/primitives/base/base_estimator.py +2 -2
- qiskit/primitives/containers/data_bin.py +0 -3
- qiskit/primitives/containers/observables_array.py +192 -108
- qiskit/primitives/primitive_job.py +29 -10
- qiskit/providers/fake_provider/generic_backend_v2.py +2 -0
- qiskit/qasm3/__init__.py +106 -12
- qiskit/qasm3/ast.py +15 -1
- qiskit/qasm3/exporter.py +59 -36
- qiskit/qasm3/printer.py +12 -0
- qiskit/qpy/__init__.py +182 -6
- qiskit/qpy/binary_io/circuits.py +256 -24
- qiskit/qpy/binary_io/parse_sympy_repr.py +5 -0
- qiskit/qpy/binary_io/schedules.py +12 -32
- qiskit/qpy/binary_io/value.py +36 -18
- qiskit/qpy/common.py +11 -3
- qiskit/qpy/formats.py +17 -1
- qiskit/qpy/interface.py +52 -12
- qiskit/qpy/type_keys.py +7 -1
- qiskit/quantum_info/__init__.py +10 -0
- qiskit/quantum_info/operators/__init__.py +1 -0
- qiskit/quantum_info/operators/symplectic/__init__.py +1 -0
- qiskit/quantum_info/operators/symplectic/clifford_circuits.py +26 -0
- qiskit/quantum_info/operators/symplectic/pauli.py +2 -2
- qiskit/result/sampled_expval.py +3 -1
- qiskit/synthesis/__init__.py +10 -0
- qiskit/synthesis/arithmetic/__init__.py +1 -1
- qiskit/synthesis/arithmetic/adders/__init__.py +1 -0
- qiskit/synthesis/arithmetic/adders/draper_qft_adder.py +6 -2
- qiskit/synthesis/arithmetic/adders/rv_ripple_carry_adder.py +156 -0
- qiskit/synthesis/discrete_basis/generate_basis_approximations.py +14 -126
- qiskit/synthesis/discrete_basis/solovay_kitaev.py +161 -121
- qiskit/synthesis/evolution/lie_trotter.py +10 -7
- qiskit/synthesis/evolution/product_formula.py +10 -7
- qiskit/synthesis/evolution/qdrift.py +10 -7
- qiskit/synthesis/evolution/suzuki_trotter.py +10 -7
- qiskit/synthesis/multi_controlled/__init__.py +4 -0
- qiskit/synthesis/multi_controlled/mcx_synthesis.py +402 -178
- qiskit/synthesis/multi_controlled/multi_control_rotation_gates.py +14 -15
- qiskit/synthesis/qft/qft_decompose_lnn.py +7 -25
- qiskit/synthesis/unitary/qsd.py +80 -9
- qiskit/transpiler/__init__.py +10 -3
- qiskit/transpiler/instruction_durations.py +2 -20
- qiskit/transpiler/passes/__init__.py +5 -2
- qiskit/transpiler/passes/layout/dense_layout.py +26 -6
- qiskit/transpiler/passes/layout/disjoint_utils.py +1 -166
- qiskit/transpiler/passes/layout/sabre_layout.py +22 -3
- qiskit/transpiler/passes/layout/sabre_pre_layout.py +1 -1
- qiskit/transpiler/passes/layout/vf2_layout.py +49 -13
- qiskit/transpiler/passes/layout/vf2_utils.py +10 -0
- qiskit/transpiler/passes/optimization/__init__.py +1 -1
- qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py +2 -1
- qiskit/transpiler/passes/optimization/optimize_clifford_t.py +68 -0
- qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +3 -9
- qiskit/transpiler/passes/routing/sabre_swap.py +4 -2
- qiskit/transpiler/passes/routing/star_prerouting.py +106 -81
- qiskit/transpiler/passes/scheduling/__init__.py +1 -1
- qiskit/transpiler/passes/scheduling/alignments/check_durations.py +1 -1
- qiskit/transpiler/passes/scheduling/padding/__init__.py +1 -0
- qiskit/transpiler/passes/scheduling/padding/context_aware_dynamical_decoupling.py +876 -0
- qiskit/transpiler/passes/synthesis/__init__.py +1 -0
- qiskit/transpiler/passes/synthesis/clifford_unitary_synth_plugin.py +123 -0
- qiskit/transpiler/passes/synthesis/hls_plugins.py +494 -93
- qiskit/transpiler/passes/synthesis/plugin.py +4 -0
- qiskit/transpiler/passes/synthesis/solovay_kitaev_synthesis.py +27 -22
- qiskit/transpiler/passmanager_config.py +3 -0
- qiskit/transpiler/preset_passmanagers/builtin_plugins.py +149 -28
- qiskit/transpiler/preset_passmanagers/common.py +101 -0
- qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py +6 -0
- qiskit/transpiler/preset_passmanagers/level3.py +2 -2
- qiskit/transpiler/target.py +15 -2
- qiskit/utils/optionals.py +6 -5
- qiskit/visualization/circuit/_utils.py +5 -3
- qiskit/visualization/circuit/latex.py +9 -2
- qiskit/visualization/circuit/matplotlib.py +26 -4
- qiskit/visualization/circuit/qcstyle.py +9 -157
- qiskit/visualization/dag/__init__.py +13 -0
- qiskit/visualization/dag/dagstyle.py +103 -0
- qiskit/visualization/dag/styles/__init__.py +13 -0
- qiskit/visualization/dag/styles/color.json +10 -0
- qiskit/visualization/dag/styles/plain.json +5 -0
- qiskit/visualization/dag_visualization.py +169 -98
- qiskit/visualization/style.py +223 -0
- {qiskit-2.0.3.dist-info → qiskit-2.1.0.dist-info}/METADATA +7 -6
- {qiskit-2.0.3.dist-info → qiskit-2.1.0.dist-info}/RECORD +178 -169
- {qiskit-2.0.3.dist-info → qiskit-2.1.0.dist-info}/entry_points.txt +6 -0
- qiskit/synthesis/discrete_basis/commutator_decompose.py +0 -265
- qiskit/synthesis/discrete_basis/gate_sequence.py +0 -421
- {qiskit-2.0.3.dist-info → qiskit-2.1.0.dist-info}/WHEEL +0 -0
- {qiskit-2.0.3.dist-info → qiskit-2.1.0.dist-info}/licenses/LICENSE.txt +0 -0
- {qiskit-2.0.3.dist-info → qiskit-2.1.0.dist-info}/top_level.txt +0 -0
@@ -54,8 +54,8 @@ class BaseEstimatorV2(ABC):
|
|
54
54
|
whose final state we define as :math:`\psi(\theta)`.
|
55
55
|
|
56
56
|
* One or more observables (specified as any :class:`~.ObservablesArrayLike`, including
|
57
|
-
:class:`~.Pauli`, :class:`~.SparsePauliOp`, ``str``) that specify which
|
58
|
-
values to estimate, denoted :math:`H_j`.
|
57
|
+
:class:`~.quantum_info.Pauli`, :class:`~.SparsePauliOp`, ``str``) that specify which
|
58
|
+
expectation values to estimate, denoted :math:`H_j`.
|
59
59
|
|
60
60
|
* A collection parameter value sets to bind the circuit against, :math:`\theta_k`
|
61
61
|
|
@@ -103,9 +103,6 @@ class DataBin(ShapedMixin):
|
|
103
103
|
def __len__(self):
|
104
104
|
return len(self._data)
|
105
105
|
|
106
|
-
def __setattr__(self, *_):
|
107
|
-
raise NotImplementedError
|
108
|
-
|
109
106
|
def __repr__(self):
|
110
107
|
vals = [f"{name}={_value_repr(val)}" for name, val in self.items()]
|
111
108
|
if self.ndim:
|
@@ -16,28 +16,33 @@ ND-Array container class for Estimator observables.
|
|
16
16
|
"""
|
17
17
|
from __future__ import annotations
|
18
18
|
|
19
|
-
import
|
20
|
-
from collections import defaultdict
|
19
|
+
from copy import deepcopy
|
21
20
|
from collections.abc import Iterable, Mapping as _Mapping
|
22
|
-
from
|
23
|
-
from typing import Union, Mapping, overload
|
24
|
-
from numbers import Complex
|
21
|
+
from typing import Union, Mapping, overload, TYPE_CHECKING
|
25
22
|
|
26
23
|
import numpy as np
|
27
24
|
from numpy.typing import ArrayLike
|
28
25
|
|
29
|
-
from qiskit.quantum_info import Pauli, PauliList, SparsePauliOp
|
26
|
+
from qiskit.quantum_info import Pauli, PauliList, SparsePauliOp, SparseObservable
|
30
27
|
|
31
28
|
from .object_array import object_array
|
32
29
|
from .shape import ShapedMixin, shape_tuple
|
33
30
|
|
31
|
+
|
32
|
+
if TYPE_CHECKING:
|
33
|
+
from qiskit.transpiler.layout import TranspileLayout
|
34
|
+
|
35
|
+
|
34
36
|
# Public API classes
|
35
37
|
__all__ = ["ObservableLike", "ObservablesArrayLike"]
|
36
38
|
|
39
|
+
IndexType = Union[int, slice, None] # pylint: disable=used-before-assignment
|
40
|
+
|
37
41
|
ObservableLike = Union[
|
38
42
|
str,
|
39
43
|
Pauli,
|
40
44
|
SparsePauliOp,
|
45
|
+
SparseObservable,
|
41
46
|
Mapping[Union[str, Pauli], float],
|
42
47
|
]
|
43
48
|
"""Types that can be natively used to construct a Hermitian Estimator observable."""
|
@@ -51,12 +56,11 @@ class ObservablesArray(ShapedMixin):
|
|
51
56
|
"""An ND-array of Hermitian observables for an :class:`.Estimator` primitive."""
|
52
57
|
|
53
58
|
__slots__ = ("_array", "_shape")
|
54
|
-
ALLOWED_BASIS: str = "IXYZ01+-lr"
|
55
|
-
"""The allowed characters in basis strings."""
|
56
59
|
|
57
60
|
def __init__(
|
58
61
|
self,
|
59
62
|
observables: ObservablesArrayLike,
|
63
|
+
num_qubits: int | None = None,
|
60
64
|
copy: bool = True,
|
61
65
|
validate: bool = True,
|
62
66
|
):
|
@@ -66,35 +70,69 @@ class ObservablesArray(ShapedMixin):
|
|
66
70
|
observables: An array-like of basis observable compatible objects.
|
67
71
|
copy: Specify the ``copy`` kwarg of the :func:`.object_array` function
|
68
72
|
when initializing observables.
|
73
|
+
num_qubits: The number of qubits of the observables. If not specified, the number of
|
74
|
+
qubits will be inferred from the observables. If specified, then the specified
|
75
|
+
number of qubits must match the number of qubits in the observables.
|
69
76
|
validate: If true, coerce entries into the internal format and validate them. If false,
|
70
77
|
the input should already be an array-like.
|
71
78
|
|
72
79
|
Raises:
|
73
|
-
ValueError: If ``validate=True`` and the input observables is not valid.
|
80
|
+
ValueError: If ``validate=True`` and the input observables array is not valid.
|
74
81
|
"""
|
75
82
|
super().__init__()
|
76
83
|
if isinstance(observables, ObservablesArray):
|
77
84
|
observables = observables._array
|
78
85
|
self._array = object_array(observables, copy=copy, list_types=(PauliList,))
|
79
86
|
self._shape = self._array.shape
|
87
|
+
self._num_qubits = num_qubits
|
88
|
+
|
80
89
|
if validate:
|
81
|
-
num_qubits = None
|
82
90
|
for ndi, obs in np.ndenumerate(self._array):
|
83
91
|
basis_obs = self.coerce_observable(obs)
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
elif basis_num_qubits != num_qubits:
|
92
|
+
if self._num_qubits is None:
|
93
|
+
self._num_qubits = basis_obs.num_qubits
|
94
|
+
elif self._num_qubits != basis_obs.num_qubits:
|
88
95
|
raise ValueError(
|
89
96
|
"The number of qubits must be the same for all observables in the "
|
90
97
|
"observables array."
|
91
98
|
)
|
92
99
|
self._array[ndi] = basis_obs
|
100
|
+
elif self._num_qubits is None and self._array.size > 0:
|
101
|
+
self._num_qubits = self._array.reshape(-1)[0].num_qubits
|
102
|
+
|
103
|
+
# can happen for empty arrays
|
104
|
+
if self._num_qubits is None:
|
105
|
+
self._num_qubits = 0
|
106
|
+
|
107
|
+
@staticmethod
|
108
|
+
def _obs_to_dict(obs: SparseObservable) -> Mapping[str, float]:
|
109
|
+
"""Convert a sparse observable to a mapping from Pauli strings to coefficients."""
|
110
|
+
result = {}
|
111
|
+
for sparse_pauli_str, pauli_qubits, coeff in obs.to_sparse_list():
|
112
|
+
|
113
|
+
if len(sparse_pauli_str) == 0:
|
114
|
+
full_pauli_str = "I" * obs.num_qubits
|
115
|
+
else:
|
116
|
+
sorted_lists = sorted(zip(pauli_qubits, sparse_pauli_str))
|
117
|
+
string_fragments = []
|
118
|
+
prev_qubit = -1
|
119
|
+
for qubit, pauli in sorted_lists:
|
120
|
+
string_fragments.append("I" * (qubit - prev_qubit - 1) + pauli)
|
121
|
+
prev_qubit = qubit
|
122
|
+
|
123
|
+
string_fragments.append("I" * (obs.num_qubits - max(pauli_qubits) - 1))
|
124
|
+
full_pauli_str = "".join(string_fragments)[::-1]
|
125
|
+
|
126
|
+
# We know that the dictionary doesn't contain yet full_pauli_str as a key
|
127
|
+
# because the observable is guaranteed to be simplified
|
128
|
+
result[full_pauli_str] = np.real(coeff)
|
129
|
+
|
130
|
+
return result
|
93
131
|
|
94
132
|
def __repr__(self):
|
95
133
|
prefix = f"{type(self).__name__}("
|
96
134
|
suffix = f", shape={self.shape})"
|
97
|
-
array = np.array2string(self.
|
135
|
+
array = np.array2string(self.__array__(), prefix=prefix, suffix=suffix, threshold=50)
|
98
136
|
return prefix + array + suffix
|
99
137
|
|
100
138
|
def tolist(self) -> list | ObservableLike:
|
@@ -116,24 +154,68 @@ class ObservablesArray(ShapedMixin):
|
|
116
154
|
>>> print(type(oa.tolist()))
|
117
155
|
<class 'dict'>
|
118
156
|
"""
|
119
|
-
return self.
|
157
|
+
return self.__array__().tolist()
|
120
158
|
|
121
|
-
def __array__(self, dtype=None, copy=None):
|
122
|
-
"""Convert to
|
159
|
+
def __array__(self, dtype=None, copy=None) -> np.ndarray: # pylint: disable=unused-argument
|
160
|
+
"""Convert to a Numpy.ndarray with elements of type dict."""
|
123
161
|
if dtype is None or dtype == object:
|
124
|
-
|
162
|
+
tmp_result = self.__getitem__(tuple(slice(None) for _ in self._array.shape))
|
163
|
+
if len(self._array.shape) == 0:
|
164
|
+
result = np.ndarray(shape=self._array.shape, dtype=dict)
|
165
|
+
result[()] = tmp_result
|
166
|
+
else:
|
167
|
+
result = np.ndarray(tmp_result.shape, dtype=dict)
|
168
|
+
for ndi, obs in np.ndenumerate(tmp_result._array):
|
169
|
+
result[ndi] = self._obs_to_dict(obs)
|
170
|
+
return result
|
125
171
|
raise ValueError("Type must be 'None' or 'object'")
|
126
172
|
|
173
|
+
def sparse_observables_array(self, copy: bool = False) -> np.ndarray:
|
174
|
+
"""Convert to a :class:`numpy.ndarray` with elements of type :class:`~.SparseObservable`.
|
175
|
+
|
176
|
+
Args:
|
177
|
+
copy: Whether to make a new array instance with new sparse observables as elements.
|
178
|
+
|
179
|
+
Returns:
|
180
|
+
A :class:`numpy.ndarray` with elements of type :class:`~.SparseObservable`.
|
181
|
+
"""
|
182
|
+
obs = self.copy() if copy else self
|
183
|
+
return obs._array
|
184
|
+
|
127
185
|
@overload
|
128
186
|
def __getitem__(self, args: int | tuple[int, ...]) -> Mapping[str, float]: ...
|
129
187
|
|
130
188
|
@overload
|
131
|
-
def __getitem__(self, args:
|
189
|
+
def __getitem__(self, args: IndexType | tuple[IndexType, ...]) -> ObservablesArray: ...
|
132
190
|
|
133
191
|
def __getitem__(self, args):
|
192
|
+
item = self._array[args]
|
193
|
+
if not isinstance(item, np.ndarray):
|
194
|
+
return self._obs_to_dict(item)
|
195
|
+
|
196
|
+
return ObservablesArray(item, copy=False, validate=False)
|
197
|
+
|
198
|
+
@overload
|
199
|
+
def slice(self, args: int | tuple[int, ...]) -> SparseObservable: ...
|
200
|
+
|
201
|
+
@overload
|
202
|
+
def slice(self, args: IndexType | tuple[IndexType, ...]) -> ObservablesArray: ...
|
203
|
+
|
204
|
+
def slice(self, args):
|
205
|
+
"""Take a slice of the observables in this array.
|
206
|
+
|
207
|
+
.. note::
|
208
|
+
This method does not copy observables; modifying the returned observables will affect this
|
209
|
+
instance.
|
210
|
+
|
211
|
+
Returns:
|
212
|
+
A single :class:`~.SparseObservable` if an integer is given for every array axis, otherwise,
|
213
|
+
a new :class:`~.ObservablesArray`.
|
214
|
+
"""
|
134
215
|
item = self._array[args]
|
135
216
|
if not isinstance(item, np.ndarray):
|
136
217
|
return item
|
218
|
+
|
137
219
|
return ObservablesArray(item, copy=False, validate=False)
|
138
220
|
|
139
221
|
def reshape(self, *shape: int | Iterable[int]) -> ObservablesArray:
|
@@ -161,8 +243,13 @@ class ObservablesArray(ShapedMixin):
|
|
161
243
|
"""
|
162
244
|
return self.reshape(self.size)
|
163
245
|
|
246
|
+
@property
|
247
|
+
def num_qubits(self) -> int:
|
248
|
+
"""The number of qubits each observable acts on."""
|
249
|
+
return self._num_qubits
|
250
|
+
|
164
251
|
@classmethod
|
165
|
-
def coerce_observable(cls, observable: ObservableLike) ->
|
252
|
+
def coerce_observable(cls, observable: ObservableLike) -> SparseObservable:
|
166
253
|
"""Format an observable-like object into the internal format.
|
167
254
|
|
168
255
|
Args:
|
@@ -177,61 +264,39 @@ class ObservablesArray(ShapedMixin):
|
|
177
264
|
"""
|
178
265
|
# Pauli-type conversions
|
179
266
|
if isinstance(observable, SparsePauliOp):
|
180
|
-
observable =
|
181
|
-
|
267
|
+
observable = SparseObservable.from_sparse_pauli_op(observable)
|
268
|
+
elif isinstance(observable, Pauli):
|
269
|
+
observable = SparseObservable.from_pauli(observable)
|
270
|
+
elif isinstance(observable, str):
|
271
|
+
observable = SparseObservable.from_label(observable)
|
272
|
+
elif isinstance(observable, _Mapping):
|
273
|
+
term_list = []
|
274
|
+
for basis, coeff in observable.items():
|
275
|
+
if isinstance(basis, str):
|
276
|
+
term_list.append((basis, coeff))
|
277
|
+
elif isinstance(basis, Pauli):
|
278
|
+
unphased_basis, phase = basis[:].to_label(), basis.phase
|
279
|
+
term_list.append((unphased_basis, complex(0, 1) ** phase * coeff))
|
280
|
+
else:
|
281
|
+
raise TypeError(f"Invalid observable basis type: {type(basis)}")
|
282
|
+
observable = SparseObservable.from_list(term_list)
|
283
|
+
|
284
|
+
if isinstance(observable, SparseObservable):
|
285
|
+
# Check that the operator has real coeffs
|
182
286
|
coeffs = np.real_if_close(observable.coeffs)
|
183
287
|
if np.iscomplexobj(coeffs):
|
184
288
|
raise ValueError(
|
185
289
|
"Non-Hermitian input observable: the input SparsePauliOp has non-zero"
|
186
290
|
" imaginary part in its coefficients."
|
187
291
|
)
|
188
|
-
paulis = observable.paulis.to_labels()
|
189
|
-
# Call simplify to combine duplicate keys before converting to a mapping
|
190
|
-
return dict(zip(paulis, coeffs))
|
191
|
-
|
192
|
-
if isinstance(observable, Pauli):
|
193
|
-
label, phase = observable[:].to_label(), observable.phase
|
194
|
-
if phase % 2:
|
195
|
-
raise ValueError(
|
196
|
-
"Non-Hermitian input observable: the input Pauli has an imaginary phase."
|
197
|
-
)
|
198
|
-
return {label: 1} if phase == 0 else {label: -1}
|
199
|
-
|
200
|
-
# String conversion
|
201
|
-
if isinstance(observable, str):
|
202
|
-
cls._validate_basis(observable)
|
203
|
-
return {observable: 1}
|
204
292
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
if phase % 2:
|
213
|
-
raise ValueError(
|
214
|
-
"Non-Hermitian input observable: the input Pauli has an imaginary phase."
|
215
|
-
)
|
216
|
-
if phase == 2:
|
217
|
-
coeff = -coeff
|
218
|
-
# Truncate complex numbers to real
|
219
|
-
if isinstance(coeff, Complex):
|
220
|
-
if abs(coeff.imag) > 1e-7:
|
221
|
-
raise TypeError(
|
222
|
-
f"Non-Hermitian input observable: {basis} term has a complex value"
|
223
|
-
" coefficient."
|
224
|
-
)
|
225
|
-
coeff = coeff.real
|
226
|
-
|
227
|
-
# Validate basis
|
228
|
-
cls._validate_basis(basis)
|
229
|
-
if len(basis) != num_qubits:
|
230
|
-
raise ValueError(
|
231
|
-
"Number of qubits must be the same for all observable basis elements."
|
232
|
-
)
|
233
|
-
unique[basis] += coeff
|
234
|
-
return dict(unique)
|
293
|
+
return SparseObservable.from_raw_parts(
|
294
|
+
observable.num_qubits,
|
295
|
+
coeffs,
|
296
|
+
observable.bit_terms,
|
297
|
+
observable.indices,
|
298
|
+
observable.boundaries,
|
299
|
+
).simplify(tol=0)
|
235
300
|
|
236
301
|
raise TypeError(f"Invalid observable type: {type(observable)}")
|
237
302
|
|
@@ -249,48 +314,67 @@ class ObservablesArray(ShapedMixin):
|
|
249
314
|
return observables
|
250
315
|
return cls(observables)
|
251
316
|
|
252
|
-
def
|
253
|
-
"""
|
254
|
-
num_qubits = None
|
255
|
-
for obs in self._array.reshape(-1):
|
256
|
-
basis_num_qubits = len(next(iter(obs)))
|
257
|
-
if num_qubits is None:
|
258
|
-
num_qubits = basis_num_qubits
|
259
|
-
elif basis_num_qubits != num_qubits:
|
260
|
-
raise ValueError(
|
261
|
-
"The number of qubits must be the same for all observables in the "
|
262
|
-
"observables array."
|
263
|
-
)
|
317
|
+
def equivalent(self, other: ObservablesArray, tol: float = 1e-08) -> bool:
|
318
|
+
"""Compute whether the observable arrays are equal within a given tolerance.
|
264
319
|
|
265
|
-
|
266
|
-
|
267
|
-
|
320
|
+
Args:
|
321
|
+
other: Another observables array to compare with.
|
322
|
+
tol: The tolerance to provide to :attr:`~.SparseObservable.simplify` during checking.
|
323
|
+
|
324
|
+
Returns:
|
325
|
+
Whether the two observables arrays have the same shape and number of qubits,
|
326
|
+
and if so, whether they are equal within tolerance.
|
327
|
+
"""
|
328
|
+
if self.num_qubits != other.num_qubits or self.shape != other.shape:
|
329
|
+
return False
|
330
|
+
|
331
|
+
zero_obs = SparseObservable.zero(self.num_qubits)
|
332
|
+
for obs1, obs2 in zip(self._array.ravel(), other._array.ravel()):
|
333
|
+
if (obs1 - obs2).simplify(tol) != zero_obs:
|
334
|
+
return False
|
335
|
+
|
336
|
+
return True
|
337
|
+
|
338
|
+
def copy(self):
|
339
|
+
"""Return a deep copy of the array."""
|
340
|
+
return deepcopy(self)
|
341
|
+
|
342
|
+
def apply_layout(
|
343
|
+
self, layout: TranspileLayout | list[int] | None, num_qubits: int | None = None
|
344
|
+
) -> ObservablesArray:
|
345
|
+
"""Apply a transpiler layout to this :class:`~.ObservablesArray`.
|
268
346
|
|
269
347
|
Args:
|
270
|
-
|
348
|
+
layout: Either a :class:`~.TranspileLayout`, a list of integers or None.
|
349
|
+
If both layout and ``num_qubits`` are none, a deep copy of the array is
|
350
|
+
returned.
|
351
|
+
num_qubits: The number of qubits to expand the array to. If not
|
352
|
+
provided then if ``layout`` is a :class:`~.TranspileLayout` the
|
353
|
+
number of the transpiler output circuit qubits will be used by
|
354
|
+
default. If ``layout`` is a list of integers the permutation
|
355
|
+
specified will be applied without any expansion. If layout is
|
356
|
+
None, the array will be expanded to the given number of qubits.
|
357
|
+
|
358
|
+
Returns:
|
359
|
+
A new :class:`.ObservablesArray` with the provided layout applied.
|
271
360
|
|
272
361
|
Raises:
|
273
|
-
|
362
|
+
QiskitError: ...
|
274
363
|
"""
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
@lru_cache(1)
|
294
|
-
def _regex_invalid(allowed_chars: str) -> re.Pattern:
|
295
|
-
"""Return pattern for selecting invalid strings"""
|
296
|
-
return re.compile(f"[^{re.escape(allowed_chars)}]")
|
364
|
+
if layout is None and num_qubits is None:
|
365
|
+
return self.copy()
|
366
|
+
|
367
|
+
new_arr = np.ndarray(self.shape, dtype=SparseObservable)
|
368
|
+
for ndi, obs in np.ndenumerate(self._array):
|
369
|
+
new_arr[ndi] = obs.apply_layout(layout, num_qubits)
|
370
|
+
|
371
|
+
return ObservablesArray(new_arr, validate=False)
|
372
|
+
|
373
|
+
def validate(self):
|
374
|
+
"""Validate the consistency in observables array."""
|
375
|
+
for obs in self._array.reshape(-1):
|
376
|
+
if obs.num_qubits != self.num_qubits:
|
377
|
+
raise ValueError(
|
378
|
+
"An observable was detected, whose number of qubits"
|
379
|
+
" does not match the array's number of qubits"
|
380
|
+
)
|
@@ -35,6 +35,8 @@ class PrimitiveJob(BasePrimitiveJob[ResultT, JobStatus]):
|
|
35
35
|
super().__init__(str(uuid.uuid4()))
|
36
36
|
self._future = None
|
37
37
|
self._function = function
|
38
|
+
self._result = None
|
39
|
+
self._status = None
|
38
40
|
self._args = args
|
39
41
|
self._kwargs = kwargs
|
40
42
|
|
@@ -46,19 +48,36 @@ class PrimitiveJob(BasePrimitiveJob[ResultT, JobStatus]):
|
|
46
48
|
self._future = executor.submit(self._function, *self._args, **self._kwargs)
|
47
49
|
executor.shutdown(wait=False)
|
48
50
|
|
51
|
+
def __getstate__(self):
|
52
|
+
_ = self.result()
|
53
|
+
_ = self.status()
|
54
|
+
state = self.__dict__.copy()
|
55
|
+
state["_future"] = None
|
56
|
+
return state
|
57
|
+
|
58
|
+
def __setstate__(self, state):
|
59
|
+
self.__dict__.update(state)
|
60
|
+
self._future = None
|
61
|
+
|
49
62
|
def result(self) -> ResultT:
|
50
|
-
self.
|
51
|
-
|
63
|
+
if self._result is None:
|
64
|
+
self._check_submitted()
|
65
|
+
self._result = self._future.result()
|
66
|
+
return self._result
|
52
67
|
|
53
68
|
def status(self) -> JobStatus:
|
54
|
-
self.
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
69
|
+
if self._status is None:
|
70
|
+
self._check_submitted()
|
71
|
+
if self._future.running():
|
72
|
+
# we should not store status running because it is not completed
|
73
|
+
return JobStatus.RUNNING
|
74
|
+
elif self._future.cancelled():
|
75
|
+
self._status = JobStatus.CANCELLED
|
76
|
+
elif self._future.done() and self._future.exception() is None:
|
77
|
+
self._status = JobStatus.DONE
|
78
|
+
else:
|
79
|
+
self._status = JobStatus.ERROR
|
80
|
+
return self._status
|
62
81
|
|
63
82
|
def _check_submitted(self):
|
64
83
|
if self._future is None:
|
@@ -19,6 +19,7 @@ import numpy as np
|
|
19
19
|
|
20
20
|
from qiskit.circuit import QuantumCircuit, Instruction
|
21
21
|
from qiskit.circuit.controlflow import (
|
22
|
+
BoxOp,
|
22
23
|
IfElseOp,
|
23
24
|
WhileLoopOp,
|
24
25
|
ForLoopOp,
|
@@ -265,6 +266,7 @@ class GenericBackendV2(BackendV2):
|
|
265
266
|
self._target.add_instruction(SwitchCaseOp, name="switch_case")
|
266
267
|
self._target.add_instruction(BreakLoopOp, name="break")
|
267
268
|
self._target.add_instruction(ContinueLoopOp, name="continue")
|
269
|
+
self._target.add_instruction(BoxOp, name="box")
|
268
270
|
|
269
271
|
def _add_noisy_instruction_to_target(
|
270
272
|
self,
|