Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Refactoring]: split devices.noise_utils into qis.noise_utils and devices.noise_utils #6453

Merged
merged 12 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 33 additions & 31 deletions cirq-core/cirq/devices/noise_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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`.
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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)
85 changes: 1 addition & 84 deletions cirq-core/cirq/devices/noise_utils_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions cirq-core/cirq/qis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
123 changes: 123 additions & 0 deletions cirq-core/cirq/qis/noise_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Copyright 2021 The Cirq Developers
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we update the year?

#
# 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
Loading
Loading