qiskit-aer 0.17.2__cp314-cp314-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.
Files changed (83) hide show
  1. qiskit_aer/VERSION.txt +1 -0
  2. qiskit_aer/__init__.py +89 -0
  3. qiskit_aer/aererror.py +30 -0
  4. qiskit_aer/aerprovider.py +119 -0
  5. qiskit_aer/backends/__init__.py +20 -0
  6. qiskit_aer/backends/aer_compiler.py +1085 -0
  7. qiskit_aer/backends/aer_simulator.py +1025 -0
  8. qiskit_aer/backends/aerbackend.py +679 -0
  9. qiskit_aer/backends/backend_utils.py +567 -0
  10. qiskit_aer/backends/backendconfiguration.py +395 -0
  11. qiskit_aer/backends/backendproperties.py +590 -0
  12. qiskit_aer/backends/compatibility.py +287 -0
  13. qiskit_aer/backends/controller_wrappers.cp314-win_amd64.pyd +0 -0
  14. qiskit_aer/backends/libopenblas.dll +0 -0
  15. qiskit_aer/backends/name_mapping.py +306 -0
  16. qiskit_aer/backends/qasm_simulator.py +925 -0
  17. qiskit_aer/backends/statevector_simulator.py +330 -0
  18. qiskit_aer/backends/unitary_simulator.py +316 -0
  19. qiskit_aer/jobs/__init__.py +35 -0
  20. qiskit_aer/jobs/aerjob.py +143 -0
  21. qiskit_aer/jobs/utils.py +66 -0
  22. qiskit_aer/library/__init__.py +204 -0
  23. qiskit_aer/library/control_flow_instructions/__init__.py +16 -0
  24. qiskit_aer/library/control_flow_instructions/jump.py +47 -0
  25. qiskit_aer/library/control_flow_instructions/mark.py +30 -0
  26. qiskit_aer/library/control_flow_instructions/store.py +29 -0
  27. qiskit_aer/library/default_qubits.py +44 -0
  28. qiskit_aer/library/instructions_table.csv +21 -0
  29. qiskit_aer/library/save_instructions/__init__.py +44 -0
  30. qiskit_aer/library/save_instructions/save_amplitudes.py +168 -0
  31. qiskit_aer/library/save_instructions/save_clifford.py +63 -0
  32. qiskit_aer/library/save_instructions/save_data.py +129 -0
  33. qiskit_aer/library/save_instructions/save_density_matrix.py +91 -0
  34. qiskit_aer/library/save_instructions/save_expectation_value.py +257 -0
  35. qiskit_aer/library/save_instructions/save_matrix_product_state.py +71 -0
  36. qiskit_aer/library/save_instructions/save_probabilities.py +156 -0
  37. qiskit_aer/library/save_instructions/save_stabilizer.py +70 -0
  38. qiskit_aer/library/save_instructions/save_state.py +79 -0
  39. qiskit_aer/library/save_instructions/save_statevector.py +120 -0
  40. qiskit_aer/library/save_instructions/save_superop.py +62 -0
  41. qiskit_aer/library/save_instructions/save_unitary.py +63 -0
  42. qiskit_aer/library/set_instructions/__init__.py +19 -0
  43. qiskit_aer/library/set_instructions/set_density_matrix.py +78 -0
  44. qiskit_aer/library/set_instructions/set_matrix_product_state.py +83 -0
  45. qiskit_aer/library/set_instructions/set_stabilizer.py +77 -0
  46. qiskit_aer/library/set_instructions/set_statevector.py +78 -0
  47. qiskit_aer/library/set_instructions/set_superop.py +78 -0
  48. qiskit_aer/library/set_instructions/set_unitary.py +78 -0
  49. qiskit_aer/noise/__init__.py +265 -0
  50. qiskit_aer/noise/device/__init__.py +25 -0
  51. qiskit_aer/noise/device/models.py +397 -0
  52. qiskit_aer/noise/device/parameters.py +202 -0
  53. qiskit_aer/noise/errors/__init__.py +30 -0
  54. qiskit_aer/noise/errors/base_quantum_error.py +119 -0
  55. qiskit_aer/noise/errors/pauli_error.py +283 -0
  56. qiskit_aer/noise/errors/pauli_lindblad_error.py +363 -0
  57. qiskit_aer/noise/errors/quantum_error.py +451 -0
  58. qiskit_aer/noise/errors/readout_error.py +355 -0
  59. qiskit_aer/noise/errors/standard_errors.py +498 -0
  60. qiskit_aer/noise/noise_model.py +1231 -0
  61. qiskit_aer/noise/noiseerror.py +30 -0
  62. qiskit_aer/noise/passes/__init__.py +18 -0
  63. qiskit_aer/noise/passes/local_noise_pass.py +160 -0
  64. qiskit_aer/noise/passes/relaxation_noise_pass.py +137 -0
  65. qiskit_aer/primitives/__init__.py +44 -0
  66. qiskit_aer/primitives/estimator.py +751 -0
  67. qiskit_aer/primitives/estimator_v2.py +159 -0
  68. qiskit_aer/primitives/sampler.py +361 -0
  69. qiskit_aer/primitives/sampler_v2.py +256 -0
  70. qiskit_aer/quantum_info/__init__.py +32 -0
  71. qiskit_aer/quantum_info/states/__init__.py +16 -0
  72. qiskit_aer/quantum_info/states/aer_densitymatrix.py +313 -0
  73. qiskit_aer/quantum_info/states/aer_state.py +525 -0
  74. qiskit_aer/quantum_info/states/aer_statevector.py +302 -0
  75. qiskit_aer/utils/__init__.py +44 -0
  76. qiskit_aer/utils/noise_model_inserter.py +66 -0
  77. qiskit_aer/utils/noise_transformation.py +431 -0
  78. qiskit_aer/version.py +86 -0
  79. qiskit_aer-0.17.2.dist-info/METADATA +209 -0
  80. qiskit_aer-0.17.2.dist-info/RECORD +83 -0
  81. qiskit_aer-0.17.2.dist-info/WHEEL +5 -0
  82. qiskit_aer-0.17.2.dist-info/licenses/LICENSE.txt +203 -0
  83. qiskit_aer-0.17.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,363 @@
1
+ # This code is part of Qiskit.
2
+ #
3
+ # (C) Copyright IBM 2018-2024.
4
+ #
5
+ # This code is licensed under the Apache License, Version 2.0. You may
6
+ # obtain a copy of this license in the LICENSE.txt file in the root directory
7
+ # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8
+ #
9
+ # Any modifications or derivative works of this code must retain this
10
+ # copyright notice, and modified files need to carry a notice indicating
11
+ # that they have been altered from the originals.
12
+
13
+ """
14
+ Class for representing a Pauli noise channel generated by a Pauli Lindblad dissipator.
15
+ """
16
+
17
+ from __future__ import annotations
18
+ from collections.abc import Sequence
19
+ import numpy as np
20
+
21
+ from qiskit.quantum_info import Pauli, PauliList, SparsePauliOp, ScalarOp, SuperOp
22
+ from qiskit.quantum_info.operators.mixins import TolerancesMixin
23
+ from .base_quantum_error import BaseQuantumError
24
+ from .quantum_error import QuantumError
25
+ from .pauli_error import PauliError, sort_paulis
26
+ from ..noiseerror import NoiseError
27
+
28
+
29
+ class PauliLindbladError(BaseQuantumError, TolerancesMixin):
30
+ r"""A Pauli channel generated by a Pauli Lindblad dissipator.
31
+
32
+ This operator represents an N-qubit quantum error channel
33
+ :math:`E(ρ) = e^{\sum_j r_j D_{P_j}}(ρ)` generated by Pauli Lindblad dissipators
34
+ :math:`D_P(ρ) = P ρ P - ρ`, where :math:`P_j` are N-qubit :class:`~.Pauli` operators.
35
+
36
+ The list of Pauli generator terms are stored as a :class:`~.PauliList` and can be
37
+ accessed via the :attr:`generators` attribute. The array of dissipator rates
38
+ :math:`r_j` can be accessed via the :attr:`rates` attribute.
39
+
40
+ A Pauli lindblad error is equivalent to a :class:`.PauliError` and can be converted
41
+ using the :meth:`to_pauli_error` method. Though note that for a sparse generated
42
+ ``PauliLindbladError`` there may in general be exponentially many terms in the
43
+ converted :class:`.PauliError` operator. Because of this, this operator can be
44
+ used to more efficiently represent N-qubit Pauli channels for simulation if they
45
+ have few generator terms.
46
+
47
+ The equivalent Pauli error is constructed as a composition of single-Pauli channel terms
48
+
49
+ .. math::
50
+
51
+ e^{\sum_j r_j D_{P_j}} = \prod_j e^{r_j D_{P_j}}
52
+ = prod_j \left( (1 - p_j) S_I + p_j S_{P_j} \right)
53
+
54
+ where :math:`p_j = \frac12 - \frac12 e^{-2 r_j}`.
55
+
56
+ .. note::
57
+
58
+ This operator can also represent a non-physical (non-CPTP) channel if any of the
59
+ rates are negative. Non-physical operators cannot be converted to a
60
+ :class:`~.QuantumError` or used in an :class:`~.AerSimulator` simulation. You can
61
+ check if an operator is physical using the :meth:`is_cptp` method.
62
+ """
63
+
64
+ def __init__(
65
+ self,
66
+ generators: Sequence[Pauli],
67
+ rates: Sequence[float],
68
+ ):
69
+ """Initialize a Pauli-Lindblad dissipator model.
70
+
71
+ Args:
72
+ generators: A list of Pauli's corresponding to the Lindblad dissipator generator terms.
73
+ rates: The Pauli Lindblad dissipator generator rates.
74
+ """
75
+ self._generators = PauliList(generators)
76
+ self._rates = np.asarray(rates, dtype=float)
77
+ if self._rates.shape != (len(self._generators),):
78
+ raise NoiseError("Input generators and rates are different lengths.")
79
+ super().__init__(num_qubits=self._generators.num_qubits)
80
+
81
+ def __repr__(self):
82
+ return f"{type(self).__name__}({self.generators.to_labels()}, {self.rates.tolist()})"
83
+
84
+ def __eq__(self, other):
85
+ # Use BaseOperator eq to check type and shape
86
+ if not super().__eq__(other):
87
+ return False
88
+ lhs = self.simplify()
89
+ rhs = other.simplify()
90
+ if lhs.size != rhs.size:
91
+ return False
92
+ lpaulis, lrates = sort_paulis(lhs.generators, lhs.rates)
93
+ rpaulis, rrates = sort_paulis(rhs.generators, rhs.rates)
94
+ return np.allclose(lrates, rrates) and lpaulis == rpaulis
95
+
96
+ @property
97
+ def size(self):
98
+ """Return the number of error circuit."""
99
+ return len(self.generators)
100
+
101
+ @property
102
+ def generators(self) -> PauliList:
103
+ """Return the Pauli Lindblad dissipator generator terms"""
104
+ return self._generators
105
+
106
+ @property
107
+ def rates(self) -> np.ndarray:
108
+ """Return the Lindblad dissipator generator rates"""
109
+ return self._rates
110
+
111
+ @property
112
+ def settings(self):
113
+ """Settings for IBM RuntimeEncoder JSON encoding"""
114
+ return {
115
+ "generators": self.generators,
116
+ "rates": self.rates,
117
+ }
118
+
119
+ def ideal(self) -> bool:
120
+ """Return True if this error object is composed only of identity operations.
121
+ Note that the identity check is best effort and up to global phase."""
122
+ return np.allclose(self.rates, 0) or not (
123
+ np.any(self.generators.z) or np.any(self.generators.x)
124
+ )
125
+
126
+ def is_cptp(self, atol: float | None = None, rtol: float | None = None) -> bool:
127
+ """Return True if completely-positive trace-preserving (CPTP)."""
128
+ return self.is_cp(atol=atol, rtol=rtol)
129
+
130
+ def is_tp(self, atol: float | None = None, rtol: float | None = None) -> bool:
131
+ """Test if a channel is trace-preserving (TP)"""
132
+ # pylint: disable = unused-argument
133
+ # This error is TP by construction regardless of rates.
134
+ return True
135
+
136
+ def is_cp(self, atol: float | None = None, rtol: float | None = None) -> bool:
137
+ """Test if Choi-matrix is completely-positive (CP)"""
138
+ if atol is None:
139
+ atol = self.atol
140
+ if rtol is None:
141
+ rtol = self.rtol
142
+ neg_rates = self.rates[self.rates < 0]
143
+ return np.allclose(neg_rates, 0, atol=atol, rtol=rtol)
144
+
145
+ def tensor(self, other: PauliLindbladError) -> PauliLindbladError:
146
+ if not isinstance(other, PauliLindbladError):
147
+ raise NoiseError("other must be a PauliLindbladError")
148
+ zeros_l = np.zeros(self.num_qubits, dtype=bool)
149
+ zeros_r = np.zeros(other.num_qubits, dtype=bool)
150
+ gens_left = self.generators.tensor(Pauli((zeros_r, zeros_r)))
151
+ gens_right = other.generators.expand(Pauli((zeros_l, zeros_l)))
152
+ generators = gens_left + gens_right
153
+ rates = np.concatenate([self.rates, other.rates])
154
+ return PauliLindbladError(generators, rates)
155
+
156
+ def expand(self, other) -> PauliLindbladError:
157
+ if not isinstance(other, PauliLindbladError):
158
+ raise NoiseError("other must be a PauliLindbladError")
159
+ return other.tensor(self)
160
+
161
+ def compose(self, other, qargs=None, front=False) -> PauliLindbladError:
162
+ if qargs is None:
163
+ qargs = getattr(other, "qargs", None)
164
+ if not isinstance(other, PauliLindbladError):
165
+ raise NoiseError("other must be a PauliLindbladError")
166
+ # Validate composition dimensions and qargs match
167
+ self._op_shape.compose(other._op_shape, qargs, front)
168
+ # pylint: disable = unused-argument
169
+ padded = ScalarOp(self.num_qubits * (2,)).compose(
170
+ SparsePauliOp(other.generators, copy=False, ignore_pauli_phase=True), qargs
171
+ )
172
+ generators = self.generators + padded.paulis
173
+ rates = np.concatenate([self.rates, other.rates])
174
+ return PauliLindbladError(generators, rates)
175
+
176
+ def power(self, n: float) -> PauliLindbladError:
177
+ return PauliLindbladError(self.generators, n * self.rates)
178
+
179
+ def inverse(self) -> PauliLindbladError:
180
+ """Return the inverse (non-CPTP) channel"""
181
+ return PauliLindbladError(self.generators, -self.rates)
182
+
183
+ def simplify(
184
+ self, atol: float | None = None, rtol: float | None = None
185
+ ) -> "PauliLindbladError":
186
+ """Simplify PauliList by combining duplicates and removing zeros.
187
+
188
+ Args:
189
+ atol (float): Optional. Absolute tolerance for checking if
190
+ coefficients are zero (Default: 1e-8).
191
+ rtol (float): Optional. relative tolerance for checking if
192
+ coefficients are zero (Default: 1e-5).
193
+
194
+ Returns:
195
+ SparsePauliOp: the simplified SparsePauliOp operator.
196
+ """
197
+ if atol is None:
198
+ atol = self.atol
199
+ if rtol is None:
200
+ rtol = self.rtol
201
+ # Remove identity terms
202
+ non_iden = np.any(np.logical_or(self.generators.z, self.generators.x), axis=1)
203
+ simplified = SparsePauliOp(
204
+ self.generators[non_iden],
205
+ self.rates[non_iden],
206
+ copy=False,
207
+ ignore_pauli_phase=True,
208
+ ).simplify(atol=atol, rtol=rtol)
209
+ return PauliLindbladError(simplified.paulis, simplified.coeffs.real)
210
+
211
+ def subsystem_errors(self) -> list[tuple[PauliLindbladError, tuple[int, ...]]]:
212
+ """Return a list errors for the subsystem decomposed error.
213
+
214
+ .. note::
215
+
216
+ This uses a greedy algorithm to find the largest non-identity subsystem
217
+ Pauli, checks if its non identity terms are covered by any previously
218
+ selected Pauli's, and if not adds it to list of covering subsystems.
219
+ This is repeated until no generators remain.
220
+
221
+ In terms of the number of Pauli terms this has runtime
222
+ `O(num_terms * num_coverings)`,
223
+ which in the worst case is `O(num_terms ** 2)`.
224
+
225
+ Returns:
226
+ A list of pairs of component PauliLindbladErrors and subsystem indices for the
227
+ that decompose the current errors.
228
+ """
229
+ # Find non-identity paulis we wish to cover
230
+ paulis = self.generators
231
+ non_iden = np.logical_or(paulis.z, paulis.x)
232
+
233
+ # Mask to exclude generator terms that are trivial (identities)
234
+ non_trivial = np.any(non_iden, axis=1)
235
+
236
+ # Paulis that aren't covered yet
237
+ uncovered = np.arange(non_iden.shape[0])[non_trivial]
238
+
239
+ # Indices that cover each Pauli
240
+ # Note that trivial terms will be left at -1
241
+ covered = -1 * np.ones(non_iden.shape[0], dtype=int)
242
+ coverings = []
243
+
244
+ # In general finding optimal coverings is NP-hard (I think?)
245
+ # so we do a heuristic of just greedily finding the largest
246
+ # pauli that isn't covered, and checking if it is covered
247
+ # by any previous coverings, if not adding it to coverings
248
+ # and repeat until we run out of paulis
249
+ while uncovered.size:
250
+ imax = np.argmax(np.sum(non_iden[uncovered], axis=1))
251
+ rmax = uncovered[imax]
252
+ add_covering = True
253
+ for rcov in coverings:
254
+ if np.all(non_iden[rcov][non_iden[rmax]]):
255
+ add_covering = False
256
+ covered[rmax] = rcov
257
+ break
258
+ if add_covering:
259
+ covered[rmax] = rmax
260
+ coverings.append(rmax)
261
+ uncovered = uncovered[uncovered != rmax]
262
+
263
+ # Extract subsystem errors and qinds of non-identity terms
264
+ sub_errors = []
265
+ for cover in coverings:
266
+ pinds = covered == cover
267
+ qinds = np.any(non_iden[pinds], axis=0)
268
+ sub_z = paulis.z[pinds][:, qinds]
269
+ sub_x = paulis.x[pinds][:, qinds]
270
+ sub_gens = PauliList.from_symplectic(sub_z, sub_x)
271
+ sub_err = PauliLindbladError(sub_gens, self.rates[pinds])
272
+ sub_qubits = tuple(np.where(qinds)[0])
273
+ sub_errors.append((sub_err, sub_qubits))
274
+
275
+ return sub_errors
276
+
277
+ def to_pauli_error(self, simplify: bool = True) -> PauliError:
278
+ """Convert to a PauliError operator.
279
+
280
+ .. note::
281
+
282
+ If this objects represents an non-CPTP inverse channel with negative
283
+ rates the returned "probabilities" will be a quasi-probability
284
+ distribution containing negative values.
285
+
286
+ Args:
287
+ simplify: If True call :meth:`~.PauliError.simplify` each single
288
+ Pauli channel composition to reduce the number of duplicate terms.
289
+
290
+ Returns:
291
+ The :class:`~.PauliError` of the current Pauli channel.
292
+ """
293
+ chan_z = np.zeros((1, self.num_qubits), dtype=bool)
294
+ chan_x = np.zeros_like(chan_z)
295
+ chan_p = np.ones(1, dtype=float)
296
+ for term_z, term_x, term_r in zip(self.generators.z, self.generators.x, self.rates):
297
+ term_p = 0.5 - 0.5 * np.exp(-2 * term_r)
298
+ chan_z = np.concatenate([chan_z, np.logical_xor(chan_z, term_z)], axis=0)
299
+ chan_x = np.concatenate([chan_x, chan_x ^ term_x])
300
+ chan_p = np.concatenate([(1 - term_p) * chan_p, term_p * chan_p])
301
+ if simplify:
302
+ error_op = PauliError(PauliList.from_symplectic(chan_z, chan_x), chan_p).simplify()
303
+ chan_z, chan_x, chan_p = (
304
+ error_op.paulis.z,
305
+ error_op.paulis.x,
306
+ error_op.probabilities,
307
+ )
308
+ return PauliError(PauliList.from_symplectic(chan_z, chan_x), chan_p)
309
+
310
+ def to_quantum_error(self) -> QuantumError:
311
+ """Convert to a general QuantumError object."""
312
+ if not self.is_cptp():
313
+ raise NoiseError("Cannot convert non-CPTP PauliLindbladError to a QuantumError")
314
+ return self.to_pauli_error().to_quantum_error()
315
+
316
+ def to_quantumchannel(self) -> SuperOp:
317
+ """Convert to a dense N-qubit QuantumChannel"""
318
+ return self.to_pauli_error().to_quantumchannel()
319
+
320
+ def to_dict(self):
321
+ """Return the current error as a dictionary."""
322
+ # Assemble noise circuits for Aer simulator
323
+ qubits = list(range(self.num_qubits))
324
+ instructions = [
325
+ [{"name": "pauli", "params": [pauli.to_label()], "qubits": qubits}]
326
+ for pauli in self.generators
327
+ ]
328
+ # Construct error dict
329
+ error = {
330
+ "type": "plerror",
331
+ "id": self.id,
332
+ "operations": [],
333
+ "instructions": instructions,
334
+ "rates": self.rates.tolist(),
335
+ }
336
+ return error
337
+
338
+ @staticmethod
339
+ def from_dict(error: dict) -> PauliLindbladError:
340
+ """Implement current error from a dictionary."""
341
+ # check if dictionary
342
+ if not isinstance(error, dict):
343
+ raise NoiseError("error is not a dictionary")
344
+ # check expected keys "type, id, operations, instructions, probabilities"
345
+ if (
346
+ ("type" not in error)
347
+ or ("id" not in error)
348
+ or ("operations" not in error)
349
+ or ("instructions" not in error)
350
+ or ("rates" not in error)
351
+ ):
352
+ raise NoiseError("error dictionary not containing expected keys")
353
+ instructions = error["instructions"]
354
+ rates = error["rates"]
355
+ if len(instructions) != len(rates):
356
+ raise NoiseError("rates not matching with instructions")
357
+ # parse instructions and turn to noise_ops
358
+ generators = []
359
+ for inst in instructions:
360
+ if len(inst) != 1 or inst[0]["name"] != "pauli":
361
+ raise NoiseError("Invalid PauliLindbladError dict")
362
+ generators.append(inst[0]["params"][0])
363
+ return PauliLindbladError(generators, rates)