-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add Google-specific variant for noise properties. #5082
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
Changes from 4 commits
93b0496
e1f37fa
fc1ba46
e18677a
57b86e6
1e92b1c
4a4d283
3caecbc
b49101f
f02c04f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
# 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. | ||
|
||
|
||
"""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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 ? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
gate_times_ns: Dict[type, float] of gate types to their duration on | ||
quantum hardware. | ||
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>)]. | ||
gate_pauli_errors: dict of noise_utils.noise_utils.OpIdentifiers (a gate and the qubits it | ||
95-martin-orion marked this conversation as resolved.
Show resolved
Hide resolved
|
||
targets) to the Pauli error for that operation. 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 | ||
|
||
for op_id in depol_errors: | ||
if op_id.gate_type not in self.two_qubit_gates(): | ||
continue | ||
# Subtract entangling angle error. | ||
if op_id in self.fsim_errors: | ||
depol_errors[op_id] -= extract_entangling_error(op_id) | ||
else: | ||
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 virtual_predicate(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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,258 @@ | ||
# 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. | ||
|
||
from typing import Dict, List, Tuple | ||
from cirq.ops.fsim_gate import PhasedFSimGate | ||
import numpy as np | ||
import pytest | ||
import cirq, cirq_google | ||
|
||
from cirq_google.devices.google_noise_properties import ( | ||
SYMMETRIC_TWO_QUBIT_GATES, | ||
SINGLE_QUBIT_GATES, | ||
) | ||
from cirq.devices.noise_utils import ( | ||
OpIdentifier, | ||
PHYSICAL_GATE_TAG, | ||
) | ||
|
||
from cirq_google.devices.google_noise_properties import ( | ||
GoogleNoiseProperties, | ||
NoiseModelFromGoogleNoiseProperties, | ||
) | ||
|
||
|
||
DEFAULT_GATE_NS: Dict[type, float] = { | ||
cirq.ZPowGate: 25.0, | ||
cirq.MeasurementGate: 4000.0, | ||
cirq.ResetChannel: 250.0, | ||
cirq.PhasedXZGate: 25.0, | ||
cirq.FSimGate: 32.0, | ||
# SYC is normally 12ns, but setting it equal to other two-qubit gates | ||
# simplifies the tests. | ||
cirq_google.SycamoreGate: 32.0, | ||
cirq.PhasedFSimGate: 32.0, | ||
cirq.ISwapPowGate: 32.0, | ||
cirq.CZPowGate: 32.0, | ||
# cirq.WaitGate is a special case. | ||
} | ||
|
||
|
||
# These properties are for testing purposes only - they are not representative | ||
# of device behavior for any existing hardware. | ||
def sample_noise_properties( | ||
system_qubits: List[cirq.Qid], qubit_pairs: List[Tuple[cirq.Qid, cirq.Qid]] | ||
): | ||
# Known false positive: https://github.com/PyCQA/pylint/issues/5857 | ||
return GoogleNoiseProperties( # pylint: disable=unexpected-keyword-arg | ||
gate_times_ns=DEFAULT_GATE_NS, | ||
t1_ns={q: 1e5 for q in system_qubits}, | ||
tphi_ns={q: 2e5 for q in system_qubits}, | ||
readout_errors={q: np.array([0.001, 0.01]) for q in system_qubits}, | ||
gate_pauli_errors={ | ||
**{OpIdentifier(g, q): 0.001 for g in SINGLE_QUBIT_GATES for q in system_qubits}, | ||
**{ | ||
OpIdentifier(g, q0, q1): 0.01 | ||
for g in SYMMETRIC_TWO_QUBIT_GATES | ||
for q0, q1 in qubit_pairs | ||
}, | ||
}, | ||
fsim_errors={ | ||
OpIdentifier(g, q0, q1): cirq.PhasedFSimGate(0.01, 0.03, 0.04, 0.05, 0.02) | ||
for g in SYMMETRIC_TWO_QUBIT_GATES | ||
for q0, q1 in qubit_pairs | ||
}, | ||
) | ||
|
||
|
||
def test_zphase_gates(): | ||
q0 = cirq.LineQubit(0) | ||
props = sample_noise_properties([q0], []) | ||
model = NoiseModelFromGoogleNoiseProperties(props) | ||
circuit = cirq.Circuit(cirq.Z(q0) ** 0.3) | ||
noisy_circuit = circuit.with_noise(model) | ||
assert noisy_circuit == circuit | ||
|
||
|
||
@pytest.mark.parametrize( | ||
'op', | ||
[ | ||
(cirq.Z(cirq.LineQubit(0)) ** 0.3).with_tags(cirq_google.PhysicalZTag), | ||
cirq.PhasedXZGate(x_exponent=0.8, z_exponent=0.2, axis_phase_exponent=0.1).on( | ||
cirq.LineQubit(0) | ||
), | ||
], | ||
) | ||
def test_single_qubit_gates(op): | ||
q0 = cirq.LineQubit(0) | ||
props = sample_noise_properties([q0], []) | ||
model = NoiseModelFromGoogleNoiseProperties(props) | ||
circuit = cirq.Circuit(op) | ||
noisy_circuit = circuit.with_noise(model) | ||
assert len(noisy_circuit.moments) == 3 | ||
assert len(noisy_circuit.moments[0].operations) == 1 | ||
assert noisy_circuit.moments[0].operations[0] == op.with_tags(PHYSICAL_GATE_TAG) | ||
|
||
# Depolarizing noise | ||
assert len(noisy_circuit.moments[1].operations) == 1 | ||
depol_op = noisy_circuit.moments[1].operations[0] | ||
assert isinstance(depol_op.gate, cirq.DepolarizingChannel) | ||
assert np.isclose(depol_op.gate.p, 0.00081252) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where did There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment for Thermal noise There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have a couple answers for this, but neither seems very helpful:
Is one of these sufficient, or is there something else you have in mind? How did you verify the internal version? |
||
|
||
# Thermal noise | ||
assert len(noisy_circuit.moments[2].operations) == 1 | ||
thermal_op = noisy_circuit.moments[2].operations[0] | ||
assert isinstance(thermal_op.gate, cirq.KrausChannel) | ||
thermal_choi = cirq.kraus_to_choi(cirq.kraus(thermal_op)) | ||
assert np.allclose( | ||
thermal_choi, | ||
[ | ||
[1, 0, 0, 9.99750031e-01], | ||
[0, 2.49968753e-04, 0, 0], | ||
[0, 0, 0, 0], | ||
[9.99750031e-01, 0, 0, 9.99750031e-01], | ||
], | ||
) | ||
|
||
95-martin-orion marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
@pytest.mark.parametrize( | ||
'op', | ||
[ | ||
cirq.ISWAP(*cirq.LineQubit.range(2)) ** 0.6, | ||
cirq.CZ(*cirq.LineQubit.range(2)) ** 0.3, | ||
cirq_google.SYC(*cirq.LineQubit.range(2)), | ||
], | ||
) | ||
def test_two_qubit_gates(op): | ||
q0, q1 = cirq.LineQubit.range(2) | ||
props = sample_noise_properties([q0, q1], [(q0, q1), (q1, q0)]) | ||
model = NoiseModelFromGoogleNoiseProperties(props) | ||
circuit = cirq.Circuit(op) | ||
noisy_circuit = circuit.with_noise(model) | ||
assert len(noisy_circuit.moments) == 4 | ||
assert len(noisy_circuit.moments[0].operations) == 1 | ||
assert noisy_circuit.moments[0].operations[0] == op.with_tags(PHYSICAL_GATE_TAG) | ||
|
||
# Depolarizing noise | ||
assert len(noisy_circuit.moments[1].operations) == 1 | ||
depol_op = noisy_circuit.moments[1].operations[0] | ||
assert isinstance(depol_op.gate, cirq.DepolarizingChannel) | ||
assert np.isclose(depol_op.gate.p, 0.00719705) | ||
|
||
# FSim angle corrections | ||
assert len(noisy_circuit.moments[2].operations) == 1 | ||
fsim_op = noisy_circuit.moments[2].operations[0] | ||
assert isinstance(fsim_op.gate, cirq.PhasedFSimGate) | ||
assert fsim_op == PhasedFSimGate(theta=0.01, zeta=0.03, chi=0.04, gamma=0.05, phi=0.02).on( | ||
q0, q1 | ||
) | ||
|
||
# Thermal noise | ||
assert len(noisy_circuit.moments[3].operations) == 2 | ||
thermal_op_0 = noisy_circuit.moments[3].operation_at(q0) | ||
thermal_op_1 = noisy_circuit.moments[3].operation_at(q1) | ||
assert isinstance(thermal_op_0.gate, cirq.KrausChannel) | ||
assert isinstance(thermal_op_1.gate, cirq.KrausChannel) | ||
thermal_choi_0 = cirq.kraus_to_choi(cirq.kraus(thermal_op_0)) | ||
thermal_choi_1 = cirq.kraus_to_choi(cirq.kraus(thermal_op_1)) | ||
expected_thermal_choi = np.array( | ||
[ | ||
[1, 0, 0, 9.99680051e-01], | ||
[0, 3.19948805e-04, 0, 0], | ||
[0, 0, 0, 0], | ||
[9.99680051e-01, 0, 0, 9.99680051e-01], | ||
] | ||
) | ||
assert np.allclose(thermal_choi_0, expected_thermal_choi) | ||
assert np.allclose(thermal_choi_1, expected_thermal_choi) | ||
95-martin-orion marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
def test_supertype_match(): | ||
# Verifies that ops in gate_pauli_errors which only appear as their | ||
# supertypes in fsim_errors are properly accounted for. | ||
q0, q1 = cirq.LineQubit.range(2) | ||
op_id = OpIdentifier(cirq_google.SycamoreGate, q0, q1) | ||
test_props = sample_noise_properties([q0, q1], [(q0, q1), (q1, q0)]) | ||
expected_err = test_props._depolarizing_error[op_id] | ||
|
||
props = sample_noise_properties([q0, q1], [(q0, q1), (q1, q0)]) | ||
props.fsim_errors = { | ||
k: cirq.PhasedFSimGate(0.5, 0.4, 0.3, 0.2, 0.1) | ||
for k in [OpIdentifier(cirq.FSimGate, q0, q1), OpIdentifier(cirq.FSimGate, q1, q0)] | ||
} | ||
assert props._depolarizing_error[op_id] != expected_err | ||
|
||
|
||
def test_measure_gates(): | ||
q00, q01, q10, q11 = cirq.GridQubit.rect(2, 2) | ||
qubits = [q00, q01, q10, q11] | ||
props = sample_noise_properties( | ||
qubits, | ||
[ | ||
(q00, q01), | ||
(q01, q00), | ||
(q10, q11), | ||
(q11, q10), | ||
(q00, q10), | ||
(q10, q00), | ||
(q01, q11), | ||
(q11, q01), | ||
], | ||
) | ||
model = NoiseModelFromGoogleNoiseProperties(props) | ||
95-martin-orion marked this conversation as resolved.
Show resolved
Hide resolved
|
||
op = cirq.measure(*qubits, key='m') | ||
circuit = cirq.Circuit(cirq.measure(*qubits, key='m')) | ||
noisy_circuit = circuit.with_noise(model) | ||
print(noisy_circuit.moments) | ||
95-martin-orion marked this conversation as resolved.
Show resolved
Hide resolved
|
||
assert len(noisy_circuit.moments) == 2 | ||
95-martin-orion marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
# Amplitude damping before measurement | ||
assert len(noisy_circuit.moments[0].operations) == 4 | ||
for q in qubits: | ||
op = noisy_circuit.moments[0].operation_at(q) | ||
assert isinstance(op.gate, cirq.GeneralizedAmplitudeDampingChannel), q | ||
assert np.isclose(op.gate.p, 0.90909090), q | ||
assert np.isclose(op.gate.gamma, 0.011), q | ||
|
||
# Original measurement is after the noise. | ||
assert len(noisy_circuit.moments[1].operations) == 1 | ||
# Measurements are untagged during reconstruction. | ||
assert noisy_circuit.moments[1] == circuit.moments[0] | ||
|
||
|
||
def test_wait_gates(): | ||
q0 = cirq.LineQubit(0) | ||
props = sample_noise_properties([q0], []) | ||
model = NoiseModelFromGoogleNoiseProperties(props) | ||
op = cirq.wait(q0, nanos=100) | ||
circuit = cirq.Circuit(op) | ||
noisy_circuit = circuit.with_noise(model) | ||
assert len(noisy_circuit.moments) == 2 | ||
assert noisy_circuit.moments[0].operations[0] == op.with_tags(PHYSICAL_GATE_TAG) | ||
|
||
# No depolarizing noise because WaitGate has none. | ||
|
||
assert len(noisy_circuit.moments[1].operations) == 1 | ||
thermal_op = noisy_circuit.moments[1].operations[0] | ||
assert isinstance(thermal_op.gate, cirq.KrausChannel) | ||
thermal_choi = cirq.kraus_to_choi(cirq.kraus(thermal_op)) | ||
assert np.allclose( | ||
thermal_choi, | ||
[ | ||
[1, 0, 0, 9.990005e-01], | ||
[0, 9.99500167e-04, 0, 0], | ||
[0, 0, 0, 0], | ||
[9.990005e-01, 0, 0, 9.990005e-01], | ||
], | ||
) |
Uh oh!
There was an error while loading. Please reload this page.