diff --git a/cirq-core/cirq/devices/noise_utils.py b/cirq-core/cirq/devices/noise_utils.py index 7fb30a040ba..917f873dace 100644 --- a/cirq-core/cirq/devices/noise_utils.py +++ b/cirq-core/cirq/devices/noise_utils.py @@ -13,10 +13,9 @@ # limitations under the License. from typing import TYPE_CHECKING, Any, Dict, Tuple, Type, Union -import numpy as np -from cirq import ops, protocols, value -from cirq._compat import proper_repr +from cirq import ops, protocols, value, qis +from cirq._compat import proper_repr, deprecated if TYPE_CHECKING: import cirq @@ -97,8 +96,10 @@ def _from_json_dict_(cls, gate_type, qubits, **kwargs) -> 'OpIdentifier': return cls(gate_type, *qubits) -# TODO: expose all from top-level cirq? -def decay_constant_to_xeb_fidelity(decay_constant: float, num_qubits: int = 2) -> float: +@deprecated(deadline='v2.0', fix='use cirq.qis.decay_constant_to_xeb_fidelity') +def decay_constant_to_xeb_fidelity( + decay_constant: float, num_qubits: int = 2 +) -> float: # pragma: no cover """Calculates the XEB fidelity from the depolarization decay constant. Args: @@ -108,11 +109,13 @@ def decay_constant_to_xeb_fidelity(decay_constant: float, num_qubits: int = 2) - Returns: Calculated XEB fidelity. """ - N = 2**num_qubits - return 1 - ((1 - decay_constant) * (1 - 1 / N)) + return qis.decay_constant_to_xeb_fidelity(decay_constant, num_qubits) -def decay_constant_to_pauli_error(decay_constant: float, num_qubits: int = 1) -> float: +@deprecated(deadline='v2.0', fix='use cirq.qis.decay_constant_to_pauli_error') +def decay_constant_to_pauli_error( + decay_constant: float, num_qubits: int = 1 +) -> float: # pragma: no cover """Calculates pauli error from the depolarization decay constant. Args: @@ -122,11 +125,13 @@ def decay_constant_to_pauli_error(decay_constant: float, num_qubits: int = 1) -> Returns: Calculated Pauli error. """ - N = 2**num_qubits - return (1 - decay_constant) * (1 - 1 / N / N) + return qis.decay_constant_to_pauli_error(decay_constant, num_qubits) -def pauli_error_to_decay_constant(pauli_error: float, num_qubits: int = 1) -> float: +@deprecated(deadline='v2.0', fix='use cirq.qis.pauli_error_to_decay_constant') +def pauli_error_to_decay_constant( + pauli_error: float, num_qubits: int = 1 +) -> float: # pragma: no cover """Calculates depolarization decay constant from pauli error. Args: @@ -136,11 +141,13 @@ def pauli_error_to_decay_constant(pauli_error: float, num_qubits: int = 1) -> fl Returns: Calculated depolarization decay constant. """ - N = 2**num_qubits - return 1 - (pauli_error / (1 - 1 / N / N)) + return qis.pauli_error_to_decay_constant(pauli_error, num_qubits) -def xeb_fidelity_to_decay_constant(xeb_fidelity: float, num_qubits: int = 2) -> float: +@deprecated(deadline='v2.0', fix='use cirq.qis.xeb_fidelity_to_decay_constant') +def xeb_fidelity_to_decay_constant( + xeb_fidelity: float, num_qubits: int = 2 +) -> float: # pragma: no cover """Calculates the depolarization decay constant from XEB fidelity. Args: @@ -150,11 +157,11 @@ def xeb_fidelity_to_decay_constant(xeb_fidelity: float, num_qubits: int = 2) -> Returns: Calculated depolarization decay constant. """ - N = 2**num_qubits - return 1 - (1 - xeb_fidelity) / (1 - 1 / N) + return qis.xeb_fidelity_to_decay_constant(xeb_fidelity, num_qubits) -def pauli_error_from_t1(t_ns: float, t1_ns: float) -> float: +@deprecated(deadline='v2.0', fix='use cirq.qis.pauli_error_from_t1') +def pauli_error_from_t1(t_ns: float, t1_ns: float) -> float: # pragma: no cover """Calculates the pauli error from T1 decay constant. This computes error for a specific duration, `t`. @@ -166,11 +173,11 @@ def pauli_error_from_t1(t_ns: float, t1_ns: float) -> float: Returns: Calculated Pauli error resulting from T1 decay. """ - t2 = 2 * t1_ns - return (1 - np.exp(-t_ns / t2)) / 2 + (1 - np.exp(-t_ns / t1_ns)) / 4 + return qis.pauli_error_from_t1(t_ns, t1_ns) -def average_error(decay_constant: float, num_qubits: int = 1) -> float: +@deprecated(deadline='v2.0', fix='use cirq.qis.average_error') +def average_error(decay_constant: float, num_qubits: int = 1) -> float: # pragma: no cover """Calculates the average error from the depolarization decay constant. Args: @@ -180,11 +187,13 @@ def average_error(decay_constant: float, num_qubits: int = 1) -> float: Returns: Calculated average error. """ - N = 2**num_qubits - return (1 - decay_constant) * (1 - 1 / N) + return qis.average_error(decay_constant, num_qubits) -def decoherence_pauli_error(t1_ns: float, tphi_ns: float, gate_time_ns: float) -> float: +@deprecated(deadline='v2.0', fix='use cirq.qis.decoherence_pauli_error') +def decoherence_pauli_error( + t1_ns: float, tphi_ns: float, gate_time_ns: float +) -> float: # pragma: no cover """The component of Pauli error caused by decoherence on a single qubit. Args: @@ -195,11 +204,4 @@ def decoherence_pauli_error(t1_ns: float, tphi_ns: float, gate_time_ns: float) - Returns: Calculated Pauli error resulting from decoherence. """ - gamma_2 = (1 / (2 * t1_ns)) + 1 / tphi_ns - - exp1 = np.exp(-gate_time_ns / t1_ns) - exp2 = np.exp(-gate_time_ns * gamma_2) - px = 0.25 * (1 - exp1) - py = px - pz = 0.5 * (1 - exp2) - px - return px + py + pz + return qis.decoherence_pauli_error(t1_ns, tphi_ns, gate_time_ns) diff --git a/cirq-core/cirq/devices/noise_utils_test.py b/cirq-core/cirq/devices/noise_utils_test.py index df9833424ec..26aef22433f 100644 --- a/cirq-core/cirq/devices/noise_utils_test.py +++ b/cirq-core/cirq/devices/noise_utils_test.py @@ -12,20 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -import numpy as np -import pytest - import cirq -from cirq.devices.noise_utils import ( - OpIdentifier, - decay_constant_to_xeb_fidelity, - decay_constant_to_pauli_error, - pauli_error_to_decay_constant, - xeb_fidelity_to_decay_constant, - pauli_error_from_t1, - average_error, - decoherence_pauli_error, -) +from cirq.devices.noise_utils import OpIdentifier def test_op_identifier(): @@ -67,74 +55,3 @@ def test_op_id_instance(): gate = cirq.SingleQubitCliffordGate.from_xz_map((cirq.X, False), (cirq.Z, False)) op_id = OpIdentifier(gate, q0) cirq.testing.assert_equivalent_repr(op_id) - - -@pytest.mark.parametrize( - 'decay_constant,num_qubits,expected_output', - [(0.01, 1, 1 - (0.99 * 1 / 2)), (0.05, 2, 1 - (0.95 * 3 / 4))], -) -def test_decay_constant_to_xeb_fidelity(decay_constant, num_qubits, expected_output): - val = decay_constant_to_xeb_fidelity(decay_constant, num_qubits) - assert val == expected_output - - -@pytest.mark.parametrize( - 'decay_constant,num_qubits,expected_output', - [(0.01, 1, 0.99 * 3 / 4), (0.05, 2, 0.95 * 15 / 16)], -) -def test_decay_constant_to_pauli_error(decay_constant, num_qubits, expected_output): - val = decay_constant_to_pauli_error(decay_constant, num_qubits) - assert val == expected_output - - -@pytest.mark.parametrize( - 'pauli_error,num_qubits,expected_output', - [(0.01, 1, 1 - (0.01 / (3 / 4))), (0.05, 2, 1 - (0.05 / (15 / 16)))], -) -def test_pauli_error_to_decay_constant(pauli_error, num_qubits, expected_output): - val = pauli_error_to_decay_constant(pauli_error, num_qubits) - assert val == expected_output - - -@pytest.mark.parametrize( - 'xeb_fidelity,num_qubits,expected_output', - [(0.01, 1, 1 - 0.99 / (1 / 2)), (0.05, 2, 1 - 0.95 / (3 / 4))], -) -def test_xeb_fidelity_to_decay_constant(xeb_fidelity, num_qubits, expected_output): - val = xeb_fidelity_to_decay_constant(xeb_fidelity, num_qubits) - assert val == expected_output - - -@pytest.mark.parametrize( - 't,t1_ns,expected_output', - [ - (20, 1e5, (1 - np.exp(-20 / 2e5)) / 2 + (1 - np.exp(-20 / 1e5)) / 4), - (4000, 1e4, (1 - np.exp(-4000 / 2e4)) / 2 + (1 - np.exp(-4000 / 1e4)) / 4), - ], -) -def test_pauli_error_from_t1(t, t1_ns, expected_output): - val = pauli_error_from_t1(t, t1_ns) - assert val == expected_output - - -@pytest.mark.parametrize( - 'decay_constant,num_qubits,expected_output', [(0.01, 1, 0.99 * 1 / 2), (0.05, 2, 0.95 * 3 / 4)] -) -def test_average_error(decay_constant, num_qubits, expected_output): - val = average_error(decay_constant, num_qubits) - assert val == expected_output - - -@pytest.mark.parametrize( - 'T1_ns,Tphi_ns,gate_time_ns', [(1e4, 2e4, 25), (1e5, 2e3, 25), (1e4, 2e4, 4000)] -) -def test_decoherence_pauli_error(T1_ns, Tphi_ns, gate_time_ns): - val = decoherence_pauli_error(T1_ns, Tphi_ns, gate_time_ns) - # Expected value is of the form: - # - # (1/4) * [1 - e^(-t/T1)] + (1/2) * [1 - e^(-t/(2*T1) - t/Tphi] - # - expected_output = 0.25 * (1 - np.exp(-gate_time_ns / T1_ns)) + 0.5 * ( - 1 - np.exp(-gate_time_ns * ((1 / (2 * T1_ns)) + 1 / Tphi_ns)) - ) - assert val == expected_output diff --git a/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py b/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py index f1d1e2fdc3b..9a387162f48 100644 --- a/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py +++ b/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py @@ -20,7 +20,7 @@ from functools import cached_property from typing import Dict, TYPE_CHECKING, List, Set, Type -from cirq import ops, devices +from cirq import ops, devices, qis from cirq.devices import noise_utils if TYPE_CHECKING: @@ -129,7 +129,7 @@ def expected_gates(cls) -> Set[Type[ops.Gate]]: def _get_pauli_error(self, p_error: float, op_id: noise_utils.OpIdentifier): time_ns = float(self.gate_times_ns[op_id.gate_type]) for q in op_id.qubits: - p_error -= noise_utils.decoherence_pauli_error(self.t1_ns[q], self.tphi_ns[q], time_ns) + p_error -= qis.decoherence_pauli_error(self.t1_ns[q], self.tphi_ns[q], time_ns) return p_error @cached_property diff --git a/cirq-core/cirq/qis/__init__.py b/cirq-core/cirq/qis/__init__.py index b2b8b0e4fcb..12913d6e8cc 100644 --- a/cirq-core/cirq/qis/__init__.py +++ b/cirq-core/cirq/qis/__init__.py @@ -50,3 +50,13 @@ validate_qid_shape, validate_normalized_state_vector, ) + +from cirq.qis.noise_utils import ( + decay_constant_to_xeb_fidelity, + decay_constant_to_pauli_error, + pauli_error_to_decay_constant, + xeb_fidelity_to_decay_constant, + pauli_error_from_t1, + average_error, + decoherence_pauli_error, +) diff --git a/cirq-core/cirq/qis/noise_utils.py b/cirq-core/cirq/qis/noise_utils.py new file mode 100644 index 00000000000..931f40de8ad --- /dev/null +++ b/cirq-core/cirq/qis/noise_utils.py @@ -0,0 +1,123 @@ +# Copyright 2021 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np + + +# TODO: expose all from top-level cirq? +def decay_constant_to_xeb_fidelity(decay_constant: float, num_qubits: int = 2) -> float: + """Calculates the XEB fidelity from the depolarization decay constant. + + Args: + decay_constant: Depolarization decay constant. + num_qubits: Number of qubits. + + Returns: + Calculated XEB fidelity. + """ + N = 2**num_qubits + return 1 - ((1 - decay_constant) * (1 - 1 / N)) + + +def decay_constant_to_pauli_error(decay_constant: float, num_qubits: int = 1) -> float: + """Calculates pauli error from the depolarization decay constant. + + Args: + decay_constant: Depolarization decay constant. + num_qubits: Number of qubits. + + Returns: + Calculated Pauli error. + """ + N = 2**num_qubits + return (1 - decay_constant) * (1 - 1 / N / N) + + +def pauli_error_to_decay_constant(pauli_error: float, num_qubits: int = 1) -> float: + """Calculates depolarization decay constant from pauli error. + + Args: + pauli_error: The pauli error. + num_qubits: Number of qubits. + + Returns: + Calculated depolarization decay constant. + """ + N = 2**num_qubits + return 1 - (pauli_error / (1 - 1 / N / N)) + + +def xeb_fidelity_to_decay_constant(xeb_fidelity: float, num_qubits: int = 2) -> float: + """Calculates the depolarization decay constant from XEB fidelity. + + Args: + xeb_fidelity: The XEB fidelity. + num_qubits: Number of qubits. + + Returns: + Calculated depolarization decay constant. + """ + N = 2**num_qubits + return 1 - (1 - xeb_fidelity) / (1 - 1 / N) + + +def pauli_error_from_t1(t_ns: float, t1_ns: float) -> float: + """Calculates the pauli error from T1 decay constant. + + This computes error for a specific duration, `t`. + + Args: + t_ns: The duration of the gate in ns. + t1_ns: The T1 decay constant in ns. + + Returns: + Calculated Pauli error resulting from T1 decay. + """ + t2 = 2 * t1_ns + return (1 - np.exp(-t_ns / t2)) / 2 + (1 - np.exp(-t_ns / t1_ns)) / 4 + + +def average_error(decay_constant: float, num_qubits: int = 1) -> float: + """Calculates the average error from the depolarization decay constant. + + Args: + decay_constant: Depolarization decay constant. + num_qubits: Number of qubits. + + Returns: + Calculated average error. + """ + N = 2**num_qubits + return (1 - decay_constant) * (1 - 1 / N) + + +def decoherence_pauli_error(t1_ns: float, tphi_ns: float, gate_time_ns: float) -> float: + """The component of Pauli error caused by decoherence on a single qubit. + + Args: + t1_ns: T1 time in nanoseconds. + tphi_ns: Tphi time in nanoseconds. + gate_time_ns: Duration in nanoseconds of the gate affected by this error. + + Returns: + Calculated Pauli error resulting from decoherence. + """ + gamma_2 = (1 / (2 * t1_ns)) + 1 / tphi_ns + + exp1 = np.exp(-gate_time_ns / t1_ns) + exp2 = np.exp(-gate_time_ns * gamma_2) + px = 0.25 * (1 - exp1) + py = px + pz = 0.5 * (1 - exp2) - px + return px + py + pz diff --git a/cirq-core/cirq/qis/noise_utils_test.py b/cirq-core/cirq/qis/noise_utils_test.py new file mode 100644 index 00000000000..d7ed1bd5f7d --- /dev/null +++ b/cirq-core/cirq/qis/noise_utils_test.py @@ -0,0 +1,97 @@ +# Copyright 2021 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import numpy as np +import pytest + +from cirq.qis.noise_utils import ( + decay_constant_to_xeb_fidelity, + decay_constant_to_pauli_error, + pauli_error_to_decay_constant, + xeb_fidelity_to_decay_constant, + pauli_error_from_t1, + average_error, + decoherence_pauli_error, +) + + +@pytest.mark.parametrize( + 'decay_constant,num_qubits,expected_output', + [(0.01, 1, 1 - (0.99 * 1 / 2)), (0.05, 2, 1 - (0.95 * 3 / 4))], +) +def test_decay_constant_to_xeb_fidelity(decay_constant, num_qubits, expected_output): + val = decay_constant_to_xeb_fidelity(decay_constant, num_qubits) + assert val == expected_output + + +@pytest.mark.parametrize( + 'decay_constant,num_qubits,expected_output', + [(0.01, 1, 0.99 * 3 / 4), (0.05, 2, 0.95 * 15 / 16)], +) +def test_decay_constant_to_pauli_error(decay_constant, num_qubits, expected_output): + val = decay_constant_to_pauli_error(decay_constant, num_qubits) + assert val == expected_output + + +@pytest.mark.parametrize( + 'pauli_error,num_qubits,expected_output', + [(0.01, 1, 1 - (0.01 / (3 / 4))), (0.05, 2, 1 - (0.05 / (15 / 16)))], +) +def test_pauli_error_to_decay_constant(pauli_error, num_qubits, expected_output): + val = pauli_error_to_decay_constant(pauli_error, num_qubits) + assert val == expected_output + + +@pytest.mark.parametrize( + 'xeb_fidelity,num_qubits,expected_output', + [(0.01, 1, 1 - 0.99 / (1 / 2)), (0.05, 2, 1 - 0.95 / (3 / 4))], +) +def test_xeb_fidelity_to_decay_constant(xeb_fidelity, num_qubits, expected_output): + val = xeb_fidelity_to_decay_constant(xeb_fidelity, num_qubits) + assert val == expected_output + + +@pytest.mark.parametrize( + 't,t1_ns,expected_output', + [ + (20, 1e5, (1 - np.exp(-20 / 2e5)) / 2 + (1 - np.exp(-20 / 1e5)) / 4), + (4000, 1e4, (1 - np.exp(-4000 / 2e4)) / 2 + (1 - np.exp(-4000 / 1e4)) / 4), + ], +) +def test_pauli_error_from_t1(t, t1_ns, expected_output): + val = pauli_error_from_t1(t, t1_ns) + assert val == expected_output + + +@pytest.mark.parametrize( + 'decay_constant,num_qubits,expected_output', [(0.01, 1, 0.99 * 1 / 2), (0.05, 2, 0.95 * 3 / 4)] +) +def test_average_error(decay_constant, num_qubits, expected_output): + val = average_error(decay_constant, num_qubits) + assert val == expected_output + + +@pytest.mark.parametrize( + 'T1_ns,Tphi_ns,gate_time_ns', [(1e4, 2e4, 25), (1e5, 2e3, 25), (1e4, 2e4, 4000)] +) +def test_decoherence_pauli_error(T1_ns, Tphi_ns, gate_time_ns): + val = decoherence_pauli_error(T1_ns, Tphi_ns, gate_time_ns) + # Expected value is of the form: + # + # (1/4) * [1 - e^(-t/T1)] + (1/2) * [1 - e^(-t/(2*T1) - t/Tphi] + # + expected_output = 0.25 * (1 - np.exp(-gate_time_ns / T1_ns)) + 0.5 * ( + 1 - np.exp(-gate_time_ns * ((1 / (2 * T1_ns)) + 1 / Tphi_ns)) + ) + assert val == expected_output