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

Add Google-specific variant for noise properties. #5082

Merged
merged 10 commits into from
Mar 31, 2022
10 changes: 7 additions & 3 deletions cirq-core/cirq/devices/noise_properties.py
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@
import abc
from typing import Iterable, Sequence, TYPE_CHECKING, List

from cirq import _import, ops, protocols, devices
from cirq import _compat, _import, ops, protocols, devices
from cirq.devices.noise_utils import (
PHYSICAL_GATE_TAG,
)
@@ -54,7 +54,7 @@ def __init__(self, noise_properties: NoiseProperties) -> None:
self._noise_properties = noise_properties
self.noise_models = self._noise_properties.build_noise_models()

def virtual_predicate(self, op: 'cirq.Operation') -> bool:
def is_virtual(self, op: 'cirq.Operation') -> bool:
"""Returns True if an operation is virtual.

Device-specific subclasses should implement this method to mark any
@@ -68,6 +68,10 @@ def virtual_predicate(self, op: 'cirq.Operation') -> bool:
"""
return False

@_compat.deprecated(deadline='v0.16', fix='Use is_virtual instead.')
def virtual_predicate(self, op: 'cirq.Operation') -> bool:
return self.is_virtual(op)

def noisy_moments(
self, moments: Iterable['cirq.Moment'], system_qubits: Sequence['cirq.Qid']
) -> Sequence['cirq.OP_TREE']:
@@ -91,7 +95,7 @@ def noisy_moments(
# using `self.virtual_predicate` to determine virtuality.
new_moments = []
for moment in split_measure_moments:
virtual_ops = {op for op in moment if self.virtual_predicate(op)}
virtual_ops = {op for op in moment if self.is_virtual(op)}
physical_ops = [
op.with_tags(PHYSICAL_GATE_TAG) for op in moment if op not in virtual_ops
]
8 changes: 8 additions & 0 deletions cirq-core/cirq/devices/noise_properties_test.py
Original file line number Diff line number Diff line change
@@ -60,3 +60,11 @@ def test_sample_model():
cirq.Moment(cirq.H(q0), cirq.H(q1)),
)
assert noisy_circuit == expected_circuit


def test_deprecated_virtual_predicate():
q0, q1 = cirq.LineQubit.range(2)
props = SampleNoiseProperties([q0, q1], [(q0, q1), (q1, q0)])
model = NoiseModelFromNoiseProperties(props)
with cirq.testing.assert_deprecated("Use is_virtual", deadline="v0.16"):
_ = model.virtual_predicate(cirq.X(q0))
Original file line number Diff line number Diff line change
@@ -35,14 +35,15 @@ class SuperconductingQubitsNoiseProperties(devices.NoiseProperties, abc.ABC):

Args:
gate_times_ns: Dict[type, float] of gate types to their duration on
quantum hardware.
quantum hardware. Used with t(1|phi)_ns to specify thermal noise.
t1_ns: Dict[cirq.Qid, float] of qubits to their T_1 time, in ns.
tphi_ns: Dict[cirq.Qid, float] of qubits to their T_phi time, in ns.
readout_errors: Dict[cirq.Qid, np.ndarray] of qubits to their readout
errors in matrix form: [P(read |1> from |0>), P(read |0> from |1>)].
Used to prepend amplitude damping errors to measurements.
gate_pauli_errors: dict of noise_utils.OpIdentifiers (a gate and the qubits it
targets) to the Pauli error for that operation. Keys in this dict
must have defined qubits.
targets) to the Pauli error for that operation. Used to construct
depolarizing error. Keys in this dict must have defined qubits.
validate: If True, verifies that t1 and tphi qubits sets match, and
that all symmetric two-qubit gates have errors which are
symmetric over the qubits they affect. Defaults to True.
139 changes: 139 additions & 0 deletions cirq-google/cirq_google/devices/google_noise_properties.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Copyright 2022 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.

95-martin-orion marked this conversation as resolved.
Show resolved Hide resolved

"""Class for representing noise on a Google device."""

from dataclasses import dataclass, field
from typing import Dict, List, Set, Type
import numpy as np

import cirq, cirq_google
from cirq import _compat, devices
from cirq.devices import noise_utils
from cirq.transformers.heuristic_decompositions import gate_tabulation_math_utils


SINGLE_QUBIT_GATES: Set[Type['cirq.Gate']] = {
cirq.ZPowGate,
cirq.PhasedXZGate,
cirq.MeasurementGate,
cirq.ResetChannel,
}
SYMMETRIC_TWO_QUBIT_GATES: Set[Type['cirq.Gate']] = {
cirq_google.SycamoreGate,
cirq.FSimGate,
cirq.PhasedFSimGate,
cirq.ISwapPowGate,
cirq.CZPowGate,
}
ASYMMETRIC_TWO_QUBIT_GATES: Set[Type['cirq.Gate']] = set()
Comment on lines +28 to +41
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does these sets indicate what gates are supported or which ones will have noise put on them ? I'm a little confused here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

These are the supported gates - see #4964, where validation will raise an error if metrics are provided for gates outside these sets. The division between sets is also used to determine which types of error to apply for each gate.



@dataclass
class GoogleNoiseProperties(devices.SuperconductingQubitsNoiseProperties):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe call it a FsimDeviceNoiseProperties ? In theory we might eventually want one for CZ at some point ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't think this is strictly necessary - the inclusion of FSim in the supported gates doesn't prevent someone from using this to construct noise for a CZ device (in which case no FSim noise will be added).

"""Noise-defining properties for a Google device.

Inherited args:
Copy link
Collaborator

Choose a reason for hiding this comment

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

It seems strange to have a copy of the same documentation in two locations. Is this standard practice? Perhaps you should instead reference the docstring in devices.SuperconductingQubitsNoiseProperties, and only discuss the difference between this class and its parent.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

In most cases it's OK to omit parent-class documentation, but I wanted to include it here since both this and the parent are @dataclasses. As a result, the source of the "inherited args" is somewhat opaque - documenting them here can save the reader the work of inspecting the parent class.

gate_times_ns: Dict[type, float] of gate types to their duration on
quantum hardware. Used with t(1|phi)_ns to specify thermal noise.
t1_ns: Dict[cirq.Qid, float] of qubits to their T_1 time, in ns.
tphi_ns: Dict[cirq.Qid, float] of qubits to their T_phi time, in ns.
readout_errors: Dict[cirq.Qid, np.ndarray] of qubits to their readout
errors in matrix form: [P(read |1> from |0>), P(read |0> from |1>)].
Used to prepend amplitude damping errors to measurements.
gate_pauli_errors: dict of noise_utils.OpIdentifiers (a gate and the qubits it
targets) to the Pauli error for that operation. Used to construct
depolarizing error. Keys in this dict must have defined qubits.
validate: If True, verifies that t1 and tphi qubits sets match, and
that all symmetric two-qubit gates have errors which are
symmetric over the qubits they affect. Defaults to True.

Additional args:
fsim_errors: Dict[noise_utils.OpIdentifier, cirq.PhasedFSimGate] of gate types
(potentially on specific qubits) to the PhasedFSim fix-up operation
for that gate. Defaults to no-op for all gates.
"""

fsim_errors: Dict[noise_utils.OpIdentifier, cirq.PhasedFSimGate] = field(default_factory=dict)

def __post_init__(self):
super().__post_init__()

# validate two qubit gate errors.
self._validate_symmetric_errors('fsim_errors')

@classmethod
def single_qubit_gates(cls) -> Set[type]:
return SINGLE_QUBIT_GATES

@classmethod
def symmetric_two_qubit_gates(cls) -> Set[type]:
return SYMMETRIC_TWO_QUBIT_GATES

@classmethod
def asymmetric_two_qubit_gates(cls) -> Set[type]:
return ASYMMETRIC_TWO_QUBIT_GATES

@_compat.cached_property
def _depolarizing_error(self) -> Dict[noise_utils.OpIdentifier, float]:
depol_errors = super()._depolarizing_error

def extract_entangling_error(match_id: noise_utils.OpIdentifier):
"""Gets the entangling error component of depol_errors[match_id]."""
unitary_err = cirq.unitary(self.fsim_errors[match_id])
fid = gate_tabulation_math_utils.unitary_entanglement_fidelity(unitary_err, np.eye(4))
return 1 - fid

# Subtract entangling angle error.
for op_id in depol_errors:
if op_id.gate_type not in self.two_qubit_gates():
continue
if op_id in self.fsim_errors:
depol_errors[op_id] -= extract_entangling_error(op_id)
continue
# Find the closest matching supertype, if one is provided.
# Gateset has similar behavior, but cannot be used here
# because depol_errors is a dict, not a set.
match_id = None
candidate_parents = [
parent_id for parent_id in self.fsim_errors if op_id.is_proper_subtype_of(parent_id)
]
for parent_id in candidate_parents:
if match_id is None or parent_id.is_proper_subtype_of(match_id):
match_id = parent_id
if match_id is not None:
depol_errors[op_id] -= extract_entangling_error(match_id)

return depol_errors

def build_noise_models(self) -> List['cirq.NoiseModel']:
"""Construct all NoiseModels associated with NoiseProperties."""
noise_models = super().build_noise_models()

# Insert entangling gate coherent errors after thermal error.
if self.fsim_errors:
fsim_ops = {op_id: gate.on(*op_id.qubits) for op_id, gate in self.fsim_errors.items()}
noise_models.insert(1, devices.InsertionNoiseModel(ops_added=fsim_ops))

return noise_models


class NoiseModelFromGoogleNoiseProperties(devices.NoiseModelFromNoiseProperties):
"""A noise model defined from noise properties of a Google device."""

def is_virtual(self, op: cirq.Operation) -> bool:
return isinstance(op.gate, cirq.ZPowGate) and cirq_google.PhysicalZTag not in op.tags
95-martin-orion marked this conversation as resolved.
Show resolved Hide resolved

# noisy_moments is implemented by the superclass.
Loading