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.
- qiskit_aer/VERSION.txt +1 -0
- qiskit_aer/__init__.py +89 -0
- qiskit_aer/aererror.py +30 -0
- qiskit_aer/aerprovider.py +119 -0
- qiskit_aer/backends/__init__.py +20 -0
- qiskit_aer/backends/aer_compiler.py +1085 -0
- qiskit_aer/backends/aer_simulator.py +1025 -0
- qiskit_aer/backends/aerbackend.py +679 -0
- qiskit_aer/backends/backend_utils.py +567 -0
- qiskit_aer/backends/backendconfiguration.py +395 -0
- qiskit_aer/backends/backendproperties.py +590 -0
- qiskit_aer/backends/compatibility.py +287 -0
- qiskit_aer/backends/controller_wrappers.cp314-win_amd64.pyd +0 -0
- qiskit_aer/backends/libopenblas.dll +0 -0
- qiskit_aer/backends/name_mapping.py +306 -0
- qiskit_aer/backends/qasm_simulator.py +925 -0
- qiskit_aer/backends/statevector_simulator.py +330 -0
- qiskit_aer/backends/unitary_simulator.py +316 -0
- qiskit_aer/jobs/__init__.py +35 -0
- qiskit_aer/jobs/aerjob.py +143 -0
- qiskit_aer/jobs/utils.py +66 -0
- qiskit_aer/library/__init__.py +204 -0
- qiskit_aer/library/control_flow_instructions/__init__.py +16 -0
- qiskit_aer/library/control_flow_instructions/jump.py +47 -0
- qiskit_aer/library/control_flow_instructions/mark.py +30 -0
- qiskit_aer/library/control_flow_instructions/store.py +29 -0
- qiskit_aer/library/default_qubits.py +44 -0
- qiskit_aer/library/instructions_table.csv +21 -0
- qiskit_aer/library/save_instructions/__init__.py +44 -0
- qiskit_aer/library/save_instructions/save_amplitudes.py +168 -0
- qiskit_aer/library/save_instructions/save_clifford.py +63 -0
- qiskit_aer/library/save_instructions/save_data.py +129 -0
- qiskit_aer/library/save_instructions/save_density_matrix.py +91 -0
- qiskit_aer/library/save_instructions/save_expectation_value.py +257 -0
- qiskit_aer/library/save_instructions/save_matrix_product_state.py +71 -0
- qiskit_aer/library/save_instructions/save_probabilities.py +156 -0
- qiskit_aer/library/save_instructions/save_stabilizer.py +70 -0
- qiskit_aer/library/save_instructions/save_state.py +79 -0
- qiskit_aer/library/save_instructions/save_statevector.py +120 -0
- qiskit_aer/library/save_instructions/save_superop.py +62 -0
- qiskit_aer/library/save_instructions/save_unitary.py +63 -0
- qiskit_aer/library/set_instructions/__init__.py +19 -0
- qiskit_aer/library/set_instructions/set_density_matrix.py +78 -0
- qiskit_aer/library/set_instructions/set_matrix_product_state.py +83 -0
- qiskit_aer/library/set_instructions/set_stabilizer.py +77 -0
- qiskit_aer/library/set_instructions/set_statevector.py +78 -0
- qiskit_aer/library/set_instructions/set_superop.py +78 -0
- qiskit_aer/library/set_instructions/set_unitary.py +78 -0
- qiskit_aer/noise/__init__.py +265 -0
- qiskit_aer/noise/device/__init__.py +25 -0
- qiskit_aer/noise/device/models.py +397 -0
- qiskit_aer/noise/device/parameters.py +202 -0
- qiskit_aer/noise/errors/__init__.py +30 -0
- qiskit_aer/noise/errors/base_quantum_error.py +119 -0
- qiskit_aer/noise/errors/pauli_error.py +283 -0
- qiskit_aer/noise/errors/pauli_lindblad_error.py +363 -0
- qiskit_aer/noise/errors/quantum_error.py +451 -0
- qiskit_aer/noise/errors/readout_error.py +355 -0
- qiskit_aer/noise/errors/standard_errors.py +498 -0
- qiskit_aer/noise/noise_model.py +1231 -0
- qiskit_aer/noise/noiseerror.py +30 -0
- qiskit_aer/noise/passes/__init__.py +18 -0
- qiskit_aer/noise/passes/local_noise_pass.py +160 -0
- qiskit_aer/noise/passes/relaxation_noise_pass.py +137 -0
- qiskit_aer/primitives/__init__.py +44 -0
- qiskit_aer/primitives/estimator.py +751 -0
- qiskit_aer/primitives/estimator_v2.py +159 -0
- qiskit_aer/primitives/sampler.py +361 -0
- qiskit_aer/primitives/sampler_v2.py +256 -0
- qiskit_aer/quantum_info/__init__.py +32 -0
- qiskit_aer/quantum_info/states/__init__.py +16 -0
- qiskit_aer/quantum_info/states/aer_densitymatrix.py +313 -0
- qiskit_aer/quantum_info/states/aer_state.py +525 -0
- qiskit_aer/quantum_info/states/aer_statevector.py +302 -0
- qiskit_aer/utils/__init__.py +44 -0
- qiskit_aer/utils/noise_model_inserter.py +66 -0
- qiskit_aer/utils/noise_transformation.py +431 -0
- qiskit_aer/version.py +86 -0
- qiskit_aer-0.17.2.dist-info/METADATA +209 -0
- qiskit_aer-0.17.2.dist-info/RECORD +83 -0
- qiskit_aer-0.17.2.dist-info/WHEEL +5 -0
- qiskit_aer-0.17.2.dist-info/licenses/LICENSE.txt +203 -0
- qiskit_aer-0.17.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,119 @@
|
|
|
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
|
+
Base quantum error class for Aer noise model
|
|
14
|
+
"""
|
|
15
|
+
import copy
|
|
16
|
+
import uuid
|
|
17
|
+
from abc import abstractmethod
|
|
18
|
+
|
|
19
|
+
from qiskit.circuit import QuantumCircuit, Instruction, QuantumRegister
|
|
20
|
+
from qiskit.quantum_info.operators.base_operator import BaseOperator
|
|
21
|
+
from qiskit.quantum_info.operators.channel import Kraus
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class BaseQuantumError(BaseOperator):
|
|
25
|
+
"""Base quantum error class for Aer noise model"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, num_qubits: int):
|
|
28
|
+
"""Base class for a quantum error supported by Aer."""
|
|
29
|
+
# Unique ID for BaseQuantumError
|
|
30
|
+
self._id = uuid.uuid4().hex
|
|
31
|
+
super().__init__(num_qubits=num_qubits)
|
|
32
|
+
|
|
33
|
+
def _repr_name(self) -> str:
|
|
34
|
+
return f"{type(self).__name__}[{self.id}]"
|
|
35
|
+
|
|
36
|
+
def __repr__(self):
|
|
37
|
+
"""Display QuantumError."""
|
|
38
|
+
return f"<{self._repr_name()}>"
|
|
39
|
+
|
|
40
|
+
def __hash__(self):
|
|
41
|
+
return hash(self._id)
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def id(self): # pylint: disable=invalid-name
|
|
45
|
+
"""Return unique ID string for error"""
|
|
46
|
+
return self._id
|
|
47
|
+
|
|
48
|
+
def copy(self):
|
|
49
|
+
"""Make a copy of current QuantumError."""
|
|
50
|
+
# pylint: disable=no-value-for-parameter
|
|
51
|
+
# The constructor of subclasses from raw data should be a copy
|
|
52
|
+
return copy.deepcopy(self)
|
|
53
|
+
|
|
54
|
+
def to_instruction(self):
|
|
55
|
+
"""Convert the QuantumError to a circuit Instruction."""
|
|
56
|
+
return QuantumChannelInstruction(self)
|
|
57
|
+
|
|
58
|
+
@abstractmethod
|
|
59
|
+
def ideal(self):
|
|
60
|
+
"""Return True if this error object is composed only of identity operations.
|
|
61
|
+
Note that the identity check is best effort and up to global phase."""
|
|
62
|
+
|
|
63
|
+
@abstractmethod
|
|
64
|
+
def to_quantumchannel(self):
|
|
65
|
+
"""Convert the QuantumError to a SuperOp quantum channel.
|
|
66
|
+
Required to enable SuperOp(QuantumError)."""
|
|
67
|
+
|
|
68
|
+
@abstractmethod
|
|
69
|
+
def to_dict(self):
|
|
70
|
+
"""Return the current error as a dictionary."""
|
|
71
|
+
|
|
72
|
+
@abstractmethod
|
|
73
|
+
def compose(self, other, qargs=None, front=False):
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
@abstractmethod
|
|
77
|
+
def tensor(self, other):
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
@abstractmethod
|
|
81
|
+
def expand(self, other):
|
|
82
|
+
return other.tensor(self)
|
|
83
|
+
|
|
84
|
+
def __rmul__(self, other):
|
|
85
|
+
raise NotImplementedError(
|
|
86
|
+
f"'{type(self).__name__}' does not support scalar multiplication."
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def __truediv__(self, other):
|
|
90
|
+
raise NotImplementedError(f"'{type(self).__name__}' does not support division.")
|
|
91
|
+
|
|
92
|
+
def __add__(self, other):
|
|
93
|
+
raise NotImplementedError(f"'{type(self).__name__}' does not support addition.")
|
|
94
|
+
|
|
95
|
+
def __sub__(self, other):
|
|
96
|
+
raise NotImplementedError(f"'{type(self).__name__}' does not support subtraction.")
|
|
97
|
+
|
|
98
|
+
def __neg__(self):
|
|
99
|
+
raise NotImplementedError(f"'{type(self).__name__}' does not support negation.")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class QuantumChannelInstruction(Instruction):
|
|
103
|
+
"""Container instruction for adding BaseQuantumError to circuit"""
|
|
104
|
+
|
|
105
|
+
def __init__(self, quantum_error):
|
|
106
|
+
"""Initialize a quantum error circuit instruction.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
quantum_error (BaseQuantumError): the error to add as an instruction.
|
|
110
|
+
"""
|
|
111
|
+
super().__init__("quantum_channel", quantum_error.num_qubits, 0, [])
|
|
112
|
+
self._quantum_error = quantum_error
|
|
113
|
+
|
|
114
|
+
def _define(self):
|
|
115
|
+
"""Allow unrolling to a Kraus instruction"""
|
|
116
|
+
q = QuantumRegister(self.num_qubits, "q")
|
|
117
|
+
qc = QuantumCircuit(q, name=self.name)
|
|
118
|
+
qc._append(Kraus(self._quantum_error).to_instruction(), q, [])
|
|
119
|
+
self.definition = qc
|
|
@@ -0,0 +1,283 @@
|
|
|
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, 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 ..noiseerror import NoiseError
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class PauliError(BaseQuantumError, TolerancesMixin):
|
|
29
|
+
r"""A Pauli channel quantum error.
|
|
30
|
+
|
|
31
|
+
This represents an N-qubit quantum error channel :math:`E(ρ) = \sum_j p_j P_j ρ P_j`
|
|
32
|
+
where :math:`P_j` are N-qubit :class:`~.Pauli` operators.
|
|
33
|
+
|
|
34
|
+
The list of Pauli terms are stored as a :class:`~.PauliList` and can be accessed
|
|
35
|
+
via the :attr:`paulis` attribute. The array of probabilities :math:`p_j` can be
|
|
36
|
+
accessed via the :attr:`probabilities` attribute.
|
|
37
|
+
|
|
38
|
+
.. note::
|
|
39
|
+
|
|
40
|
+
This operator can also represent a non-physical (non-CPTP) channel where some
|
|
41
|
+
probabilities are negative or don't sum to 1. Non-physical operators
|
|
42
|
+
cannot be converted to a :class:`~.QuantumError` or used in an
|
|
43
|
+
:class:`~.AerSimulator` simulation. You can check if an operator is physical
|
|
44
|
+
using the :meth:`is_cptp` method.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
paulis: Sequence[Pauli],
|
|
50
|
+
probabilities: Sequence[float],
|
|
51
|
+
):
|
|
52
|
+
"""Initialize a Pauli error channel.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
paulis: A sequence of Pauli channel terms.
|
|
56
|
+
probabilities: A sequence of the probability for each Pauli channel term.
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
NoiseError: If inputs are invalid.
|
|
60
|
+
"""
|
|
61
|
+
self._paulis = PauliList(paulis)
|
|
62
|
+
self._probabilities = np.asarray(probabilities, dtype=float)
|
|
63
|
+
if self._probabilities.shape != (len(self._paulis),):
|
|
64
|
+
raise NoiseError("Input Paulis and probabilities are different lengths.")
|
|
65
|
+
super().__init__(num_qubits=self._paulis.num_qubits)
|
|
66
|
+
|
|
67
|
+
def __repr__(self):
|
|
68
|
+
return f"{type(self).__name__}({self.paulis.to_labels()}, {self.probabilities.tolist()})"
|
|
69
|
+
|
|
70
|
+
def __eq__(self, other):
|
|
71
|
+
# Use BaseOperator eq to check type and shape
|
|
72
|
+
if not super().__eq__(other):
|
|
73
|
+
return False
|
|
74
|
+
lhs = self.simplify()
|
|
75
|
+
rhs = other.simplify()
|
|
76
|
+
if lhs.size != rhs.size:
|
|
77
|
+
return False
|
|
78
|
+
lpaulis, lprobs = sort_paulis(lhs.paulis, lhs.probabilities)
|
|
79
|
+
rpaulis, rprobs = sort_paulis(rhs.paulis, rhs.probabilities)
|
|
80
|
+
return np.allclose(lprobs, rprobs) and lpaulis == rpaulis
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def size(self):
|
|
84
|
+
"""Return the number of error circuit."""
|
|
85
|
+
return len(self.paulis)
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def paulis(self) -> PauliList:
|
|
89
|
+
"""Return the Pauli channel error terms"""
|
|
90
|
+
return self._paulis
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def probabilities(self) -> np.ndarray:
|
|
94
|
+
"""Return the Pauli channel probabilities"""
|
|
95
|
+
return self._probabilities
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def settings(self):
|
|
99
|
+
"""Settings for IBM RuntimeEncoder JSON encoding"""
|
|
100
|
+
return {
|
|
101
|
+
"paulis": self.paulis,
|
|
102
|
+
"probabilities": self.probabilities,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
def ideal(self) -> bool:
|
|
106
|
+
"""Return True if this error object is composed only of identity operations.
|
|
107
|
+
Note that the identity check is best effort and up to global phase."""
|
|
108
|
+
if not self.is_cptp():
|
|
109
|
+
return False
|
|
110
|
+
non_zero = self.paulis[~np.isclose(self.probabilities, 0)]
|
|
111
|
+
return not (np.any(non_zero.z) or np.any(non_zero.x))
|
|
112
|
+
|
|
113
|
+
def is_cptp(self, atol: float | None = None, rtol: float | None = None) -> bool:
|
|
114
|
+
"""Return True if completely-positive trace-preserving (CPTP)."""
|
|
115
|
+
return self.is_cp(atol=atol, rtol=rtol) and self.is_tp(atol=atol, rtol=rtol)
|
|
116
|
+
|
|
117
|
+
def is_tp(self, atol: float | None = None, rtol: float | None = None) -> bool:
|
|
118
|
+
"""Test if a channel is trace-preserving (TP)"""
|
|
119
|
+
if atol is None:
|
|
120
|
+
atol = self.atol
|
|
121
|
+
if rtol is None:
|
|
122
|
+
rtol = self.rtol
|
|
123
|
+
return np.isclose(np.sum(self.probabilities), 1, atol=atol, rtol=rtol)
|
|
124
|
+
|
|
125
|
+
def is_cp(self, atol: float | None = None, rtol: float | None = None) -> bool:
|
|
126
|
+
"""Test if Choi-matrix is completely-positive (CP)"""
|
|
127
|
+
if atol is None:
|
|
128
|
+
atol = self.atol
|
|
129
|
+
if rtol is None:
|
|
130
|
+
rtol = self.rtol
|
|
131
|
+
neg_probs = self.probabilities[self.probabilities < 0]
|
|
132
|
+
return np.allclose(neg_probs, 0, atol=atol, rtol=rtol)
|
|
133
|
+
|
|
134
|
+
def tensor(self, other: PauliError) -> PauliError:
|
|
135
|
+
if not isinstance(other, PauliError):
|
|
136
|
+
raise NoiseError("other must be a PauliError")
|
|
137
|
+
left = SparsePauliOp(self.paulis, self.probabilities, copy=False, ignore_pauli_phase=True)
|
|
138
|
+
right = SparsePauliOp(
|
|
139
|
+
other.paulis, other.probabilities, copy=False, ignore_pauli_phase=True
|
|
140
|
+
)
|
|
141
|
+
tens = left.tensor(right)
|
|
142
|
+
return PauliError(tens.paulis, tens.coeffs.real)
|
|
143
|
+
|
|
144
|
+
def expand(self, other: PauliError) -> PauliError:
|
|
145
|
+
if not isinstance(other, PauliError):
|
|
146
|
+
raise NoiseError("other must be a PauliError")
|
|
147
|
+
return other.tensor(self)
|
|
148
|
+
|
|
149
|
+
def compose(self, other, qargs=None, front=False) -> PauliError:
|
|
150
|
+
if qargs is None:
|
|
151
|
+
qargs = getattr(other, "qargs", None)
|
|
152
|
+
if not isinstance(other, PauliError):
|
|
153
|
+
raise NoiseError("other must be a PauliError")
|
|
154
|
+
|
|
155
|
+
# This is similar to SparsePauliOp.compose but doesn't need to track
|
|
156
|
+
# phases since it is equivalent to the abeliean Pauli group compose
|
|
157
|
+
|
|
158
|
+
# Validate composition dimensions and qargs match
|
|
159
|
+
self._op_shape.compose(other._op_shape, qargs, front)
|
|
160
|
+
|
|
161
|
+
if qargs is not None:
|
|
162
|
+
x1, z1 = self.paulis.x[:, qargs], self.paulis.z[:, qargs]
|
|
163
|
+
else:
|
|
164
|
+
x1, z1 = self.paulis.x, self.paulis.z
|
|
165
|
+
x2, z2 = other.paulis.x, other.paulis.z
|
|
166
|
+
num_qubits = other.num_qubits
|
|
167
|
+
|
|
168
|
+
x3 = np.logical_xor(x1[:, np.newaxis], x2).reshape((-1, num_qubits))
|
|
169
|
+
z3 = np.logical_xor(z1[:, np.newaxis], z2).reshape((-1, num_qubits))
|
|
170
|
+
|
|
171
|
+
if qargs is None:
|
|
172
|
+
paulis = PauliList.from_symplectic(z3, x3)
|
|
173
|
+
else:
|
|
174
|
+
x4 = np.repeat(self.paulis.x, other.size, axis=0)
|
|
175
|
+
z4 = np.repeat(self.paulis.z, other.size, axis=0)
|
|
176
|
+
x4[:, qargs] = x3
|
|
177
|
+
z4[:, qargs] = z3
|
|
178
|
+
paulis = PauliList.from_symplectic(z4, x4)
|
|
179
|
+
|
|
180
|
+
probabilities = np.multiply.outer(self.probabilities, other.probabilities).ravel()
|
|
181
|
+
return PauliError(paulis, probabilities)
|
|
182
|
+
|
|
183
|
+
def simplify(self, atol: float | None = None, rtol: float | None = None) -> PauliError:
|
|
184
|
+
"""Simplify PauliList by combining duplicates and removing zeros.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
atol (float): Optional. Absolute tolerance for checking if
|
|
188
|
+
coefficients are zero (Default: 1e-8).
|
|
189
|
+
rtol (float): Optional. relative tolerance for checking if
|
|
190
|
+
coefficients are zero (Default: 1e-5).
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
SparsePauliOp: the simplified SparsePauliOp operator.
|
|
194
|
+
"""
|
|
195
|
+
if atol is None:
|
|
196
|
+
atol = self.atol
|
|
197
|
+
if rtol is None:
|
|
198
|
+
rtol = self.rtol
|
|
199
|
+
simplified = SparsePauliOp(self.paulis, self.probabilities).simplify(atol=atol, rtol=rtol)
|
|
200
|
+
return PauliError(simplified.paulis, simplified.coeffs.real)
|
|
201
|
+
|
|
202
|
+
def to_quantum_error(self) -> "QuantumError":
|
|
203
|
+
"""Convert to a general QuantumError object."""
|
|
204
|
+
if not self.is_cptp():
|
|
205
|
+
raise NoiseError("Cannot convert non-CPTP PauliError to a QuantumError")
|
|
206
|
+
return QuantumError(list(zip(self.paulis, self.probabilities)))
|
|
207
|
+
|
|
208
|
+
def to_quantumchannel(self) -> SuperOp:
|
|
209
|
+
"""Convert to a dense N-qubit QuantumChannel"""
|
|
210
|
+
# Sum terms as superoperator
|
|
211
|
+
# We could do this more efficiently as a PTM or Chi, but would need
|
|
212
|
+
# to map Pauli terms to integer index.
|
|
213
|
+
chan = SuperOp(np.zeros(2 * [4**self.num_qubits]))
|
|
214
|
+
for pauli, coeff in zip(self.paulis, self.probabilities):
|
|
215
|
+
chan += coeff * SuperOp(pauli)
|
|
216
|
+
return chan
|
|
217
|
+
|
|
218
|
+
def to_dict(self) -> dict:
|
|
219
|
+
"""Return the current error as a dictionary."""
|
|
220
|
+
# Assemble noise circuits for Aer simulator
|
|
221
|
+
qubits = list(range(self.num_qubits))
|
|
222
|
+
instructions = [
|
|
223
|
+
[{"name": "pauli", "params": [pauli.to_label()], "qubits": qubits}]
|
|
224
|
+
for pauli in self.paulis
|
|
225
|
+
]
|
|
226
|
+
# Construct error dict
|
|
227
|
+
error = {
|
|
228
|
+
"type": "qerror",
|
|
229
|
+
"id": self.id,
|
|
230
|
+
"operations": [],
|
|
231
|
+
"instructions": instructions,
|
|
232
|
+
"probabilities": self.probabilities.tolist(),
|
|
233
|
+
}
|
|
234
|
+
return error
|
|
235
|
+
|
|
236
|
+
@staticmethod
|
|
237
|
+
def from_dict(error: dict) -> PauliError:
|
|
238
|
+
"""Implement current error from a dictionary."""
|
|
239
|
+
# check if dictionary
|
|
240
|
+
if not isinstance(error, dict):
|
|
241
|
+
raise NoiseError("error is not a dictionary")
|
|
242
|
+
# check expected keys "type, id, operations, instructions, probabilities"
|
|
243
|
+
if (
|
|
244
|
+
("type" not in error)
|
|
245
|
+
or ("id" not in error)
|
|
246
|
+
or ("operations" not in error)
|
|
247
|
+
or ("instructions" not in error)
|
|
248
|
+
or ("probabilities" not in error)
|
|
249
|
+
):
|
|
250
|
+
raise NoiseError("error dictionary not containing expected keys")
|
|
251
|
+
instructions = error["instructions"]
|
|
252
|
+
probabilities = error["probabilities"]
|
|
253
|
+
if len(instructions) != len(probabilities):
|
|
254
|
+
raise NoiseError("probabilities not matching with instructions")
|
|
255
|
+
# parse instructions and turn to noise_ops
|
|
256
|
+
paulis = []
|
|
257
|
+
for inst in instructions:
|
|
258
|
+
if len(inst) != 1 or inst[0]["name"] != "pauli":
|
|
259
|
+
raise NoiseError("Invalid PauliError dict")
|
|
260
|
+
paulis.append(inst[0]["params"][0])
|
|
261
|
+
|
|
262
|
+
return PauliError(paulis, probabilities)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def sort_paulis(paulis: PauliList, coeffs: Sequence | None = None) -> tuple[PauliList, Sequence]:
|
|
266
|
+
"""Sort terms in a way that can be used for equality checks between simplified error ops"""
|
|
267
|
+
if coeffs is not None and len(coeffs) != len(paulis):
|
|
268
|
+
raise ValueError("paulis and coefffs must have the same length.")
|
|
269
|
+
|
|
270
|
+
# Get packed bigs tableau of Paulis
|
|
271
|
+
# Use numpy sorted and enumerate to implement an argsort of
|
|
272
|
+
# rows based on python tuple sorting
|
|
273
|
+
tableau = np.hstack([paulis.x, paulis.z])
|
|
274
|
+
packed = np.packbits(tableau, axis=1)
|
|
275
|
+
if coeffs is None:
|
|
276
|
+
unsorted = ((*row.tolist(), i) for i, row in enumerate(packed))
|
|
277
|
+
else:
|
|
278
|
+
unsorted = ((*row.tolist(), coeff, i) for i, (row, coeff) in enumerate(zip(packed, coeffs)))
|
|
279
|
+
index = [tup[-1] for tup in sorted(unsorted)]
|
|
280
|
+
|
|
281
|
+
if coeffs is None:
|
|
282
|
+
return paulis[index]
|
|
283
|
+
return paulis[index], coeffs[index]
|