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.
Files changed (180) hide show
  1. qiskit/VERSION.txt +1 -1
  2. qiskit/__init__.py +19 -1
  3. qiskit/_accelerate.abi3.so +0 -0
  4. qiskit/circuit/__init__.py +104 -20
  5. qiskit/circuit/_add_control.py +57 -31
  6. qiskit/circuit/_classical_resource_map.py +4 -0
  7. qiskit/circuit/annotation.py +504 -0
  8. qiskit/circuit/classical/expr/__init__.py +1 -1
  9. qiskit/circuit/classical/expr/expr.py +104 -446
  10. qiskit/circuit/classical/expr/visitors.py +6 -0
  11. qiskit/circuit/classical/types/types.py +7 -130
  12. qiskit/circuit/controlflow/box.py +32 -7
  13. qiskit/circuit/delay.py +11 -9
  14. qiskit/circuit/library/arithmetic/adders/adder.py +4 -4
  15. qiskit/circuit/library/arithmetic/multipliers/multiplier.py +2 -2
  16. qiskit/circuit/library/arithmetic/piecewise_chebyshev.py +8 -4
  17. qiskit/circuit/library/arithmetic/piecewise_linear_pauli_rotations.py +23 -15
  18. qiskit/circuit/library/arithmetic/piecewise_polynomial_pauli_rotations.py +22 -14
  19. qiskit/circuit/library/arithmetic/quadratic_form.py +6 -0
  20. qiskit/circuit/library/arithmetic/weighted_adder.py +43 -24
  21. qiskit/circuit/library/basis_change/qft.py +2 -2
  22. qiskit/circuit/library/blueprintcircuit.py +6 -0
  23. qiskit/circuit/library/boolean_logic/inner_product.py +2 -2
  24. qiskit/circuit/library/boolean_logic/quantum_and.py +2 -2
  25. qiskit/circuit/library/boolean_logic/quantum_or.py +3 -3
  26. qiskit/circuit/library/boolean_logic/quantum_xor.py +2 -2
  27. qiskit/circuit/library/data_preparation/_z_feature_map.py +2 -2
  28. qiskit/circuit/library/data_preparation/_zz_feature_map.py +2 -2
  29. qiskit/circuit/library/data_preparation/pauli_feature_map.py +2 -2
  30. qiskit/circuit/library/fourier_checking.py +2 -2
  31. qiskit/circuit/library/generalized_gates/diagonal.py +5 -1
  32. qiskit/circuit/library/generalized_gates/gms.py +5 -1
  33. qiskit/circuit/library/generalized_gates/linear_function.py +2 -2
  34. qiskit/circuit/library/generalized_gates/permutation.py +5 -1
  35. qiskit/circuit/library/generalized_gates/uc.py +1 -1
  36. qiskit/circuit/library/generalized_gates/unitary.py +21 -2
  37. qiskit/circuit/library/graph_state.py +2 -2
  38. qiskit/circuit/library/grover_operator.py +2 -2
  39. qiskit/circuit/library/hidden_linear_function.py +2 -2
  40. qiskit/circuit/library/iqp.py +2 -2
  41. qiskit/circuit/library/n_local/efficient_su2.py +2 -2
  42. qiskit/circuit/library/n_local/evolved_operator_ansatz.py +1 -1
  43. qiskit/circuit/library/n_local/excitation_preserving.py +7 -9
  44. qiskit/circuit/library/n_local/n_local.py +4 -3
  45. qiskit/circuit/library/n_local/pauli_two_design.py +2 -2
  46. qiskit/circuit/library/n_local/real_amplitudes.py +2 -2
  47. qiskit/circuit/library/n_local/two_local.py +2 -2
  48. qiskit/circuit/library/overlap.py +2 -2
  49. qiskit/circuit/library/pauli_evolution.py +3 -2
  50. qiskit/circuit/library/phase_estimation.py +2 -2
  51. qiskit/circuit/library/standard_gates/dcx.py +11 -12
  52. qiskit/circuit/library/standard_gates/ecr.py +21 -24
  53. qiskit/circuit/library/standard_gates/equivalence_library.py +232 -96
  54. qiskit/circuit/library/standard_gates/global_phase.py +5 -6
  55. qiskit/circuit/library/standard_gates/h.py +22 -45
  56. qiskit/circuit/library/standard_gates/i.py +1 -1
  57. qiskit/circuit/library/standard_gates/iswap.py +13 -31
  58. qiskit/circuit/library/standard_gates/p.py +19 -26
  59. qiskit/circuit/library/standard_gates/r.py +11 -17
  60. qiskit/circuit/library/standard_gates/rx.py +21 -45
  61. qiskit/circuit/library/standard_gates/rxx.py +7 -22
  62. qiskit/circuit/library/standard_gates/ry.py +21 -39
  63. qiskit/circuit/library/standard_gates/ryy.py +13 -28
  64. qiskit/circuit/library/standard_gates/rz.py +18 -35
  65. qiskit/circuit/library/standard_gates/rzx.py +7 -22
  66. qiskit/circuit/library/standard_gates/rzz.py +7 -19
  67. qiskit/circuit/library/standard_gates/s.py +44 -39
  68. qiskit/circuit/library/standard_gates/swap.py +25 -38
  69. qiskit/circuit/library/standard_gates/sx.py +34 -41
  70. qiskit/circuit/library/standard_gates/t.py +18 -27
  71. qiskit/circuit/library/standard_gates/u.py +8 -24
  72. qiskit/circuit/library/standard_gates/u1.py +28 -52
  73. qiskit/circuit/library/standard_gates/u2.py +9 -9
  74. qiskit/circuit/library/standard_gates/u3.py +24 -40
  75. qiskit/circuit/library/standard_gates/x.py +190 -336
  76. qiskit/circuit/library/standard_gates/xx_minus_yy.py +12 -50
  77. qiskit/circuit/library/standard_gates/xx_plus_yy.py +13 -52
  78. qiskit/circuit/library/standard_gates/y.py +19 -23
  79. qiskit/circuit/library/standard_gates/z.py +31 -38
  80. qiskit/circuit/parameter.py +14 -5
  81. qiskit/circuit/parameterexpression.py +109 -75
  82. qiskit/circuit/quantumcircuit.py +172 -99
  83. qiskit/circuit/quantumcircuitdata.py +1 -0
  84. qiskit/circuit/random/__init__.py +37 -2
  85. qiskit/circuit/random/utils.py +445 -56
  86. qiskit/circuit/tools/pi_check.py +5 -13
  87. qiskit/compiler/transpiler.py +1 -1
  88. qiskit/converters/circuit_to_instruction.py +2 -2
  89. qiskit/dagcircuit/dagnode.py +8 -3
  90. qiskit/primitives/__init__.py +2 -2
  91. qiskit/primitives/base/base_estimator.py +2 -2
  92. qiskit/primitives/containers/data_bin.py +0 -3
  93. qiskit/primitives/containers/observables_array.py +192 -108
  94. qiskit/primitives/primitive_job.py +29 -10
  95. qiskit/providers/fake_provider/generic_backend_v2.py +2 -0
  96. qiskit/qasm3/__init__.py +106 -12
  97. qiskit/qasm3/ast.py +15 -1
  98. qiskit/qasm3/exporter.py +59 -36
  99. qiskit/qasm3/printer.py +12 -0
  100. qiskit/qpy/__init__.py +182 -6
  101. qiskit/qpy/binary_io/circuits.py +256 -24
  102. qiskit/qpy/binary_io/parse_sympy_repr.py +5 -0
  103. qiskit/qpy/binary_io/schedules.py +12 -32
  104. qiskit/qpy/binary_io/value.py +36 -18
  105. qiskit/qpy/common.py +11 -3
  106. qiskit/qpy/formats.py +17 -1
  107. qiskit/qpy/interface.py +52 -12
  108. qiskit/qpy/type_keys.py +7 -1
  109. qiskit/quantum_info/__init__.py +10 -0
  110. qiskit/quantum_info/operators/__init__.py +1 -0
  111. qiskit/quantum_info/operators/symplectic/__init__.py +1 -0
  112. qiskit/quantum_info/operators/symplectic/clifford_circuits.py +26 -0
  113. qiskit/quantum_info/operators/symplectic/pauli.py +2 -2
  114. qiskit/result/sampled_expval.py +3 -1
  115. qiskit/synthesis/__init__.py +10 -0
  116. qiskit/synthesis/arithmetic/__init__.py +1 -1
  117. qiskit/synthesis/arithmetic/adders/__init__.py +1 -0
  118. qiskit/synthesis/arithmetic/adders/draper_qft_adder.py +6 -2
  119. qiskit/synthesis/arithmetic/adders/rv_ripple_carry_adder.py +156 -0
  120. qiskit/synthesis/discrete_basis/generate_basis_approximations.py +14 -126
  121. qiskit/synthesis/discrete_basis/solovay_kitaev.py +161 -121
  122. qiskit/synthesis/evolution/lie_trotter.py +10 -7
  123. qiskit/synthesis/evolution/product_formula.py +10 -7
  124. qiskit/synthesis/evolution/qdrift.py +10 -7
  125. qiskit/synthesis/evolution/suzuki_trotter.py +10 -7
  126. qiskit/synthesis/multi_controlled/__init__.py +4 -0
  127. qiskit/synthesis/multi_controlled/mcx_synthesis.py +402 -178
  128. qiskit/synthesis/multi_controlled/multi_control_rotation_gates.py +14 -15
  129. qiskit/synthesis/qft/qft_decompose_lnn.py +7 -25
  130. qiskit/synthesis/unitary/qsd.py +80 -9
  131. qiskit/transpiler/__init__.py +10 -3
  132. qiskit/transpiler/instruction_durations.py +2 -20
  133. qiskit/transpiler/passes/__init__.py +5 -2
  134. qiskit/transpiler/passes/layout/dense_layout.py +26 -6
  135. qiskit/transpiler/passes/layout/disjoint_utils.py +1 -166
  136. qiskit/transpiler/passes/layout/sabre_layout.py +22 -3
  137. qiskit/transpiler/passes/layout/sabre_pre_layout.py +1 -1
  138. qiskit/transpiler/passes/layout/vf2_layout.py +49 -13
  139. qiskit/transpiler/passes/layout/vf2_utils.py +10 -0
  140. qiskit/transpiler/passes/optimization/__init__.py +1 -1
  141. qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py +2 -1
  142. qiskit/transpiler/passes/optimization/optimize_clifford_t.py +68 -0
  143. qiskit/transpiler/passes/optimization/template_matching/template_substitution.py +3 -9
  144. qiskit/transpiler/passes/routing/sabre_swap.py +4 -2
  145. qiskit/transpiler/passes/routing/star_prerouting.py +106 -81
  146. qiskit/transpiler/passes/scheduling/__init__.py +1 -1
  147. qiskit/transpiler/passes/scheduling/alignments/check_durations.py +1 -1
  148. qiskit/transpiler/passes/scheduling/padding/__init__.py +1 -0
  149. qiskit/transpiler/passes/scheduling/padding/context_aware_dynamical_decoupling.py +876 -0
  150. qiskit/transpiler/passes/synthesis/__init__.py +1 -0
  151. qiskit/transpiler/passes/synthesis/clifford_unitary_synth_plugin.py +123 -0
  152. qiskit/transpiler/passes/synthesis/hls_plugins.py +494 -93
  153. qiskit/transpiler/passes/synthesis/plugin.py +4 -0
  154. qiskit/transpiler/passes/synthesis/solovay_kitaev_synthesis.py +27 -22
  155. qiskit/transpiler/passmanager_config.py +3 -0
  156. qiskit/transpiler/preset_passmanagers/builtin_plugins.py +149 -28
  157. qiskit/transpiler/preset_passmanagers/common.py +101 -0
  158. qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py +6 -0
  159. qiskit/transpiler/preset_passmanagers/level3.py +2 -2
  160. qiskit/transpiler/target.py +15 -2
  161. qiskit/utils/optionals.py +6 -5
  162. qiskit/visualization/circuit/_utils.py +5 -3
  163. qiskit/visualization/circuit/latex.py +9 -2
  164. qiskit/visualization/circuit/matplotlib.py +26 -4
  165. qiskit/visualization/circuit/qcstyle.py +9 -157
  166. qiskit/visualization/dag/__init__.py +13 -0
  167. qiskit/visualization/dag/dagstyle.py +103 -0
  168. qiskit/visualization/dag/styles/__init__.py +13 -0
  169. qiskit/visualization/dag/styles/color.json +10 -0
  170. qiskit/visualization/dag/styles/plain.json +5 -0
  171. qiskit/visualization/dag_visualization.py +169 -98
  172. qiskit/visualization/style.py +223 -0
  173. {qiskit-2.0.3.dist-info → qiskit-2.1.0.dist-info}/METADATA +7 -6
  174. {qiskit-2.0.3.dist-info → qiskit-2.1.0.dist-info}/RECORD +178 -169
  175. {qiskit-2.0.3.dist-info → qiskit-2.1.0.dist-info}/entry_points.txt +6 -0
  176. qiskit/synthesis/discrete_basis/commutator_decompose.py +0 -265
  177. qiskit/synthesis/discrete_basis/gate_sequence.py +0 -421
  178. {qiskit-2.0.3.dist-info → qiskit-2.1.0.dist-info}/WHEEL +0 -0
  179. {qiskit-2.0.3.dist-info → qiskit-2.1.0.dist-info}/licenses/LICENSE.txt +0 -0
  180. {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 expectation
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 re
20
- from collections import defaultdict
19
+ from copy import deepcopy
21
20
  from collections.abc import Iterable, Mapping as _Mapping
22
- from functools import lru_cache
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
- basis_num_qubits = len(next(iter(basis_obs)))
85
- if num_qubits is None:
86
- num_qubits = basis_num_qubits
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._array, prefix=prefix, suffix=suffix, threshold=50)
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._array.tolist()
157
+ return self.__array__().tolist()
120
158
 
121
- def __array__(self, dtype=None, copy=None):
122
- """Convert to an Numpy.ndarray"""
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
- return self._array.copy() if copy else self._array
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: slice) -> ObservablesArray: ...
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) -> Mapping[str, float]:
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 = observable.simplify(atol=0)
181
- # Check that the operator is Hermitian and has real coeffs
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
- # Mapping conversion (with possible Pauli keys)
206
- if isinstance(observable, _Mapping):
207
- num_qubits = len(next(iter(observable)))
208
- unique = defaultdict(float)
209
- for basis, coeff in observable.items():
210
- if isinstance(basis, Pauli):
211
- basis, phase = basis[:].to_label(), basis.phase
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 validate(self):
253
- """Validate the consistency in observables array."""
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
- @classmethod
266
- def _validate_basis(cls, basis: str) -> None:
267
- """Validate a basis string.
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
- basis: a basis string to validate.
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
- ValueError: If basis string contains invalid characters
362
+ QiskitError: ...
274
363
  """
275
- # NOTE: the allowed basis characters can be overridden by modifying the class
276
- # attribute ALLOWED_BASIS
277
- allowed_pattern = _regex_match(cls.ALLOWED_BASIS)
278
- if not allowed_pattern.match(basis):
279
- invalid_pattern = _regex_invalid(cls.ALLOWED_BASIS)
280
- invalid_chars = list(set(invalid_pattern.findall(basis)))
281
- raise ValueError(
282
- f"Observable basis string '{basis}' contains invalid characters {invalid_chars},"
283
- f" allowed characters are {list(cls.ALLOWED_BASIS)}.",
284
- )
285
-
286
-
287
- @lru_cache(1)
288
- def _regex_match(allowed_chars: str) -> re.Pattern:
289
- """Return pattern for matching if a string contains only the allowed characters."""
290
- return re.compile(f"^[{re.escape(allowed_chars)}]*$")
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._check_submitted()
51
- return self._future.result()
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._check_submitted()
55
- if self._future.running():
56
- return JobStatus.RUNNING
57
- elif self._future.cancelled():
58
- return JobStatus.CANCELLED
59
- elif self._future.done() and self._future.exception() is None:
60
- return JobStatus.DONE
61
- return JobStatus.ERROR
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,