From 0651a22a630d41bb164a8514f3c52d24ff361aa8 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 7 Feb 2022 15:39:08 -0800 Subject: [PATCH 1/6] Add SuperconductingQubitsNoiseProperties. --- cirq-core/cirq/__init__.py | 1 + cirq-core/cirq/devices/__init__.py | 4 + ...superconducting_qubits_noise_properties.py | 269 ++++++++++++ ...conducting_qubits_noise_properties_test.py | 383 ++++++++++++++++++ cirq-core/cirq/json_resolver_cache.py | 1 + .../SuperconductingQubitsNoiseProperties.json | 122 ++++++ .../SuperconductingQubitsNoiseProperties.repr | 21 + 7 files changed, 801 insertions(+) create mode 100644 cirq-core/cirq/devices/superconducting_qubits_noise_properties.py create mode 100644 cirq-core/cirq/devices/superconducting_qubits_noise_properties_test.py create mode 100644 cirq-core/cirq/protocols/json_test_data/SuperconductingQubitsNoiseProperties.json create mode 100644 cirq-core/cirq/protocols/json_test_data/SuperconductingQubitsNoiseProperties.repr diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index ae2bf4f457f..38024e76135 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -96,6 +96,7 @@ NoiseModelFromNoiseProperties, NoiseProperties, OpIdentifier, + SuperconductingQubitsNoiseProperties, SymmetricalQidPair, UNCONSTRAINED_DEVICE, NamedTopology, diff --git a/cirq-core/cirq/devices/__init__.py b/cirq-core/cirq/devices/__init__.py index 49b0708a5f0..1c9c68f5f59 100644 --- a/cirq-core/cirq/devices/__init__.py +++ b/cirq-core/cirq/devices/__init__.py @@ -67,6 +67,10 @@ NoiseProperties, ) +from cirq.devices.superconducting_qubits_noise_properties import ( + SuperconductingQubitsNoiseProperties, +) + from cirq.devices.noise_utils import ( OpIdentifier, decay_constant_to_xeb_fidelity, diff --git a/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py b/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py new file mode 100644 index 00000000000..d5507ff7036 --- /dev/null +++ b/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py @@ -0,0 +1,269 @@ +# 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. + +from dataclasses import dataclass, field +from typing import Dict, TYPE_CHECKING, List, Set + +from cirq import ops, protocols, devices +from cirq.devices.noise_utils import ( + OpIdentifier, + decoherence_pauli_error, +) + +if TYPE_CHECKING: + import cirq + +SINGLE_QUBIT_GATES: Set[type] = { + ops.ZPowGate, + ops.PhasedXZGate, + ops.MeasurementGate, + ops.ResetChannel, +} +SYMMETRIC_TWO_QUBIT_GATES: Set[type] = { + ops.FSimGate, + ops.PhasedFSimGate, + ops.ISwapPowGate, + ops.CZPowGate, +} +ASYMMETRIC_TWO_QUBIT_GATES: Set[type] = set() +TWO_QUBIT_GATES = SYMMETRIC_TWO_QUBIT_GATES | ASYMMETRIC_TWO_QUBIT_GATES + + +# TODO: missing per-device defaults +@dataclass +class SuperconductingQubitsNoiseProperties(devices.NoiseProperties): + """Noise-defining properties for a quantum device. + + Args: + 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. + ro_fidelities: Dict[cirq.Qid, np.ndarray] of qubits to their readout + fidelity matrix. + gate_pauli_errors: dict of OpIdentifiers (a gate and the qubits it + targets) to the Pauli error for that operation. Keys in this dict + must have defined qubits. + validate: If True, performs validation on input arguments. Defaults + to True. + """ + + gate_times_ns: Dict[type, float] + t1_ns: Dict['cirq.Qid', float] + tphi_ns: Dict['cirq.Qid', float] + ro_fidelities: Dict['cirq.Qid', List[float]] + gate_pauli_errors: Dict[OpIdentifier, float] + + validate: bool = True + _qubits: List['cirq.Qid'] = field(init=False, default_factory=list) + _depolarizing_error: Dict[OpIdentifier, float] = field(init=False, default_factory=dict) + + def __post_init__(self): + if not self.validate: + return + t1_qubits = set(self.t1_ns) + tphi_qubits = set(self.tphi_ns) + + if t1_qubits != tphi_qubits: + raise ValueError('Keys specified for T1 and Tphi are not identical.') + + # validate two qubit gate errors. + self._validate_symmetric_errors('gate_pauli_errors') + + def _validate_symmetric_errors(self, field_name: str) -> None: + gate_error_dict = getattr(self, field_name) + for op_id in gate_error_dict: + if len(op_id.qubits) != 2: + # single qubit op_ids also present, or generic values are + # specified. Skip these cases + if len(op_id.qubits) > 2: + raise ValueError( + f'Found gate {op_id.gate_type} with {len(op_id.qubits)} qubits. ' + 'Symmetric errors can only apply to 2-qubit gates.' + ) + elif op_id.gate_type not in self.two_qubit_gates(): + raise ValueError( + f'Found gate {op_id.gate_type} which does not appear in the ' + 'symmetric or asymmetric gate sets.' + ) + else: + # TODO: this assumes op is symmetric. + # If asymmetric gates are added, we will need to update it. + op_id_swapped = OpIdentifier(op_id.gate_type, *op_id.qubits[::-1]) + if op_id_swapped not in gate_error_dict: + raise ValueError( + f'Operation {op_id} of field {field_name} has errors ' + f'but its symmetric id {op_id_swapped} does not.' + ) + + @property + def qubits(self) -> List['cirq.Qid']: + """Qubits for which we have data""" + if not self._qubits: + self._qubits = sorted(self.t1_ns) + return self._qubits + + @classmethod + def single_qubit_gates(cls) -> Set[type]: + return SINGLE_QUBIT_GATES + + @classmethod + def two_qubit_gates(cls) -> Set[type]: + return TWO_QUBIT_GATES + + @classmethod + def expected_gates(cls) -> Set[type]: + return cls.single_qubit_gates() | cls.two_qubit_gates() + + def get_depolarizing_error(self) -> Dict[OpIdentifier, float]: + """Returns the portion of Pauli error from depolarization. + + The result of this method is memoized. + """ + if self._depolarizing_error: + return self._depolarizing_error + + depol_errors = {} + for op_id, p_error in self.gate_pauli_errors.items(): + gate_type = op_id.gate_type + if gate_type in self.single_qubit_gates(): + if issubclass(gate_type, ops.MeasurementGate): + # Non-measurement error can be ignored on measurement gates. + continue + if len(op_id.qubits) != 1: + raise ValueError( + f'Gate {gate_type} only takes one qubit, but {op_id.qubits} were given.' + ) + time_ns = float(self.gate_times_ns[gate_type]) + q0 = op_id.qubits[0] + # Subtract decoherence error. + if q0 in self.t1_ns: + p_error -= decoherence_pauli_error(self.t1_ns[q0], self.tphi_ns[q0], time_ns) + + else: + # This must be a 2-qubit gate. + if gate_type not in self.two_qubit_gates(): + raise ValueError(f'Gate {gate_type} is not in the supported gate list.') + if len(op_id.qubits) != 2: + raise ValueError( + f'Gate {gate_type} takes two qubits, but {op_id.qubits} were given.' + ) + time_ns = float(self.gate_times_ns[gate_type]) + # Subtract decoherence error. + q0, q1 = op_id.qubits + if q0 in self.t1_ns: + p_error -= decoherence_pauli_error(self.t1_ns[q0], self.tphi_ns[q0], time_ns) + if q1 in self.t1_ns: + p_error -= decoherence_pauli_error(self.t1_ns[q1], self.tphi_ns[q1], time_ns) + + depol_errors[op_id] = p_error + # memoization is OK + self._depolarizing_error = depol_errors + return self._depolarizing_error + + def build_noise_models(self) -> List['cirq.NoiseModel']: + noise_models: List['cirq.NoiseModel'] = [] + + if set(self.t1_ns) != set(self.tphi_ns): + raise ValueError( + f'T1 data has qubits {set(self.t1_ns)}, but Tphi has qubits {set(self.tphi_ns)}.' + ) + if self.t1_ns: # level 1 sophistication + noise_models.append( + devices.ThermalNoiseModel( + set(self.t1_ns.keys()), + self.gate_times_ns, + cool_rate_GHz={q: 1 / T1 for q, T1 in self.t1_ns.items()}, + dephase_rate_GHz={q: 1 / Tp for q, Tp in self.tphi_ns.items()}, + ) + ) + + gate_types = set(op_id.gate_type for op_id in self.gate_pauli_errors) + if not gate_types.issubset(self.expected_gates()): + raise ValueError( + 'Some gates are not in the supported set.' + f'\nGates: {gate_types}\nSupported: {self.expected_gates()}' + ) + + depolarizing_error = self.get_depolarizing_error() + added_pauli_errors = { + op_id: ops.depolarize(p_error, len(op_id.qubits)).on(*op_id.qubits) + for op_id, p_error in depolarizing_error.items() + if p_error > 0 + } + + # This adds per-qubit pauli error after ops on those qubits. + noise_models.append(devices.InsertionNoiseModel(ops_added=added_pauli_errors)) + + # This adds per-qubit measurement error BEFORE measurement on those qubits. + if self.ro_fidelities: + added_measure_errors: Dict[OpIdentifier, 'cirq.Operation'] = {} + for qubit in self.ro_fidelities: + p_00, p_11 = self.ro_fidelities[qubit] + p = p_11 / (p_00 + p_11) + gamma = p_11 / p + added_measure_errors[ + OpIdentifier(ops.MeasurementGate, qubit) + ] = ops.generalized_amplitude_damp(p, gamma).on(qubit) + + noise_models.append( + devices.InsertionNoiseModel(ops_added=added_measure_errors, prepend=True) + ) + + return noise_models + + def __str__(self) -> str: + return 'SuperconductingQubitsNoiseProperties' + + def __repr__(self) -> str: + args = [] + gate_times_repr = ', '.join( + f'{key.__module__}.{key.__qualname__}: {val}' for key, val in self.gate_times_ns.items() + ) + args.append(f' gate_times_ns={{{gate_times_repr}}}') + args.append(f' t1_ns={self.t1_ns!r}') + args.append(f' tphi_ns={self.tphi_ns!r}') + args.append(f' ro_fidelities={self.ro_fidelities!r}') + args.append(f' gate_pauli_errors={self.gate_pauli_errors!r}') + args_str = ',\n'.join(args) + return f'cirq.SuperconductingQubitsNoiseProperties(\n{args_str}\n)' + + def _json_dict_(self): + storage_gate_times = { + protocols.json_cirq_type(key): val for key, val in self.gate_times_ns.items() + } + return { + # JSON requires mappings to have keys of basic types. + # Pairs must be sorted to ensure consistent serialization. + 'gate_times_ns': sorted(storage_gate_times.items(), key=str), + 't1_ns': sorted(self.t1_ns.items()), + 'tphi_ns': sorted(self.tphi_ns.items()), + 'ro_fidelities': sorted(self.ro_fidelities.items()), + 'gate_pauli_errors': sorted(self.gate_pauli_errors.items(), key=str), + 'validate': self.validate, + } + + @classmethod + def _from_json_dict_( + cls, gate_times_ns, t1_ns, tphi_ns, ro_fidelities, gate_pauli_errors, validate, **kwargs + ): + gate_type_times = {protocols.cirq_type_from_json(gate): val for gate, val in gate_times_ns} + return SuperconductingQubitsNoiseProperties( + gate_times_ns=gate_type_times, + t1_ns=dict(t1_ns), + tphi_ns=dict(tphi_ns), + ro_fidelities=dict(ro_fidelities), + gate_pauli_errors=dict(gate_pauli_errors), + validate=validate, + ) diff --git a/cirq-core/cirq/devices/superconducting_qubits_noise_properties_test.py b/cirq-core/cirq/devices/superconducting_qubits_noise_properties_test.py new file mode 100644 index 00000000000..006a3476829 --- /dev/null +++ b/cirq-core/cirq/devices/superconducting_qubits_noise_properties_test.py @@ -0,0 +1,383 @@ +# 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. + +from typing import Dict, List, Tuple +import numpy as np +import cirq +import pytest + +from cirq.devices.noise_properties import ( + NoiseModelFromNoiseProperties, +) +from cirq.devices.superconducting_qubits_noise_properties import ( + SuperconductingQubitsNoiseProperties, + SINGLE_QUBIT_GATES, + TWO_QUBIT_GATES, +) +from cirq.devices.noise_utils import ( + OpIdentifier, + PHYSICAL_GATE_TAG, +) + + +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, + 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]] +) -> SuperconductingQubitsNoiseProperties: + return SuperconductingQubitsNoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + t1_ns={q: 1e5 for q in system_qubits}, + tphi_ns={q: 2e5 for q in system_qubits}, + ro_fidelities={q: [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 TWO_QUBIT_GATES for q0, q1 in qubit_pairs}, + }, + ) + + +def test_str(): + q0 = cirq.LineQubit(0) + props = sample_noise_properties([q0], []) + assert str(props) == 'SuperconductingQubitsNoiseProperties' + + +def test_repr_evaluation(): + q0 = cirq.LineQubit(0) + props = sample_noise_properties([q0], []) + props_from_repr = eval(repr(props)) + assert props_from_repr == props + + +def test_json_serialization(): + q0 = cirq.LineQubit(0) + props = sample_noise_properties([q0], []) + props_json = cirq.to_json(props) + props_from_json = cirq.read_json(json_text=props_json) + assert props_from_json == props + + +def test_init_validation(): + q0, q1, q2 = cirq.LineQubit.range(3) + with pytest.raises(ValueError, match='Keys specified for T1 and Tphi are not identical.'): + _ = SuperconductingQubitsNoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + t1_ns={}, + tphi_ns={q0: 1}, + ro_fidelities={q0: [0.1, 0.2]}, + gate_pauli_errors={}, + ) + + with pytest.raises(ValueError, match='Symmetric errors can only apply to 2-qubit gates.'): + _ = SuperconductingQubitsNoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + t1_ns={q0: 1}, + tphi_ns={q0: 1}, + ro_fidelities={q0: [0.1, 0.2]}, + gate_pauli_errors={OpIdentifier(cirq.CCNOT, q0, q1, q2): 0.1}, + ) + + with pytest.raises(ValueError, match='does not appear in the symmetric or asymmetric'): + _ = SuperconductingQubitsNoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + t1_ns={q0: 1}, + tphi_ns={q0: 1}, + ro_fidelities={q0: [0.1, 0.2]}, + gate_pauli_errors={ + OpIdentifier(cirq.CNOT, q0, q1): 0.1, + OpIdentifier(cirq.CNOT, q1, q0): 0.1, + }, + ) + + with pytest.raises(ValueError, match='has errors but its symmetric id'): + _ = SuperconductingQubitsNoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + t1_ns={q0: 1}, + tphi_ns={q0: 1}, + ro_fidelities={q0: [0.1, 0.2]}, + gate_pauli_errors={OpIdentifier(cirq.CZPowGate, q0, q1): 0.1}, + ) + + # Single-qubit gates are ignored in symmetric-gate validation. + _ = SuperconductingQubitsNoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + t1_ns={q0: 1}, + tphi_ns={q0: 1}, + ro_fidelities={q0: [0.1, 0.2]}, + gate_pauli_errors={ + OpIdentifier(cirq.ZPowGate, q0): 0.1, + OpIdentifier(cirq.CZPowGate, q0, q1): 0.1, + OpIdentifier(cirq.CZPowGate, q1, q0): 0.1, + }, + ) + + # All errors are ignored if validation is disabled. + _ = SuperconductingQubitsNoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + t1_ns={}, + tphi_ns={q0: 1}, + ro_fidelities={q0: [0.1, 0.2]}, + gate_pauli_errors={ + OpIdentifier(cirq.CCNOT, q0, q1, q2): 0.1, + OpIdentifier(cirq.CNOT, q0, q1): 0.1, + }, + validate=False, + ) + + +def test_qubits(): + q0 = cirq.LineQubit(0) + props = sample_noise_properties([q0], []) + assert props.qubits == [q0] + # Confirm memoization behavior. + assert props.qubits == [q0] + + +def test_depol_memoization(): + # Verify that depolarizing error is memoized. + q0 = cirq.LineQubit(0) + props = sample_noise_properties([q0], []) + depol_error_a = props.get_depolarizing_error() + depol_error_b = props.get_depolarizing_error() + assert depol_error_a == depol_error_b + assert depol_error_a is depol_error_b + + +def test_depol_validation(): + q0, q1, q2 = cirq.LineQubit.range(3) + # Create unvalidated properties with too many qubits on a Z gate. + z_2q_props = SuperconductingQubitsNoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + t1_ns={q0: 1}, + tphi_ns={q0: 1}, + ro_fidelities={q0: [0.1, 0.2]}, + gate_pauli_errors={OpIdentifier(cirq.ZPowGate, q0, q1): 0.1}, + validate=False, + ) + with pytest.raises(ValueError, match='only takes one qubit'): + _ = z_2q_props.get_depolarizing_error() + + # Create unvalidated properties with an unsupported gate. + toffoli_props = SuperconductingQubitsNoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + t1_ns={q0: 1}, + tphi_ns={q0: 1}, + ro_fidelities={q0: [0.1, 0.2]}, + gate_pauli_errors={OpIdentifier(cirq.CCNOT, q0, q1, q2): 0.1}, + validate=False, + ) + with pytest.raises(ValueError, match='not in the supported gate list'): + _ = toffoli_props.get_depolarizing_error() + + # Create unvalidated properties with too many qubits on a CZ gate. + cz_3q_props = SuperconductingQubitsNoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + t1_ns={q0: 1}, + tphi_ns={q0: 1}, + ro_fidelities={q0: [0.1, 0.2]}, + gate_pauli_errors={OpIdentifier(cirq.CZPowGate, q0, q1, q2): 0.1}, + validate=False, + ) + with pytest.raises(ValueError, match='takes two qubits'): + _ = cz_3q_props.get_depolarizing_error() + + # If t1_ns is missing, values are filled in as needed. + + +def test_build_noise_model_validation(): + q0, q1, q2 = cirq.LineQubit.range(3) + # Create unvalidated properties with mismatched T1 and Tphi qubits. + t1_tphi_props = SuperconductingQubitsNoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + t1_ns={}, + tphi_ns={q0: 1}, + ro_fidelities={q0: [0.1, 0.2]}, + gate_pauli_errors={}, + validate=False, + ) + with pytest.raises(ValueError, match='but Tphi has qubits'): + _ = t1_tphi_props.build_noise_models() + + # Create unvalidated properties with unsupported gates. + toffoli_props = SuperconductingQubitsNoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + t1_ns={q0: 1}, + tphi_ns={q0: 1}, + ro_fidelities={q0: [0.1, 0.2]}, + gate_pauli_errors={OpIdentifier(cirq.CCNOT, q0, q1, q2): 0.1}, + validate=False, + ) + with pytest.raises(ValueError, match='Some gates are not in the supported set.'): + _ = toffoli_props.build_noise_models() + + +@pytest.mark.parametrize( + 'op', + [ + cirq.Z(cirq.LineQubit(0)) ** 0.3, + 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 = NoiseModelFromNoiseProperties(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) + + # 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], + ], + ) + + +@pytest.mark.parametrize( + 'op', + [ + cirq.ISWAP(*cirq.LineQubit.range(2)) ** 0.6, + cirq.CZ(*cirq.LineQubit.range(2)) ** 0.3, + ], +) +def test_two_qubit_gates(op): + q0, q1 = cirq.LineQubit.range(2) + props = sample_noise_properties([q0, q1], [(q0, q1), (q1, q0)]) + model = NoiseModelFromNoiseProperties(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.00952008) + + # Thermal noise + assert len(noisy_circuit.moments[2].operations) == 2 + thermal_op_0 = noisy_circuit.moments[2].operation_at(q0) + thermal_op_1 = noisy_circuit.moments[2].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) + + +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 = NoiseModelFromNoiseProperties(props) + op = cirq.measure(*qubits, key='m') + circuit = cirq.Circuit(cirq.measure(*qubits, key='m')) + noisy_circuit = circuit.with_noise(model) + assert len(noisy_circuit.moments) == 2 + + # 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 = NoiseModelFromNoiseProperties(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], + ], + ) diff --git a/cirq-core/cirq/json_resolver_cache.py b/cirq-core/cirq/json_resolver_cache.py index f15252c3256..f03c5d4cf1a 100644 --- a/cirq-core/cirq/json_resolver_cache.py +++ b/cirq-core/cirq/json_resolver_cache.py @@ -160,6 +160,7 @@ def _parallel_gate_op(gate, qubits): 'SqrtIswapTargetGateset': cirq.SqrtIswapTargetGateset, 'StabilizerStateChForm': cirq.StabilizerStateChForm, 'StatePreparationChannel': cirq.StatePreparationChannel, + 'SuperconductingQubitsNoiseProperties': cirq.SuperconductingQubitsNoiseProperties, 'SwapPowGate': cirq.SwapPowGate, 'SymmetricalQidPair': cirq.SymmetricalQidPair, 'SympyCondition': cirq.SympyCondition, diff --git a/cirq-core/cirq/protocols/json_test_data/SuperconductingQubitsNoiseProperties.json b/cirq-core/cirq/protocols/json_test_data/SuperconductingQubitsNoiseProperties.json new file mode 100644 index 00000000000..496c1340121 --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/SuperconductingQubitsNoiseProperties.json @@ -0,0 +1,122 @@ +{ + "cirq_type": "SuperconductingQubitsNoiseProperties", + "gate_times_ns": [ + [ + "CZPowGate", + 32.0 + ], + [ + "FSimGate", + 32.0 + ], + [ + "ISwapPowGate", + 32.0 + ], + [ + "MeasurementGate", + 4000.0 + ], + [ + "PhasedFSimGate", + 32.0 + ], + [ + "PhasedXZGate", + 25.0 + ], + [ + "ResetChannel", + 250.0 + ], + [ + "ZPowGate", + 25.0 + ] + ], + "t1_ns": [ + [ + { + "cirq_type": "LineQubit", + "x": 0 + }, + 100000.0 + ] + ], + "tphi_ns": [ + [ + { + "cirq_type": "LineQubit", + "x": 0 + }, + 200000.0 + ] + ], + "ro_fidelities": [ + [ + { + "cirq_type": "LineQubit", + "x": 0 + }, + [ + 0.001, + 0.01 + ] + ] + ], + "gate_pauli_errors": [ + [ + { + "cirq_type": "OpIdentifier", + "gate_type": "ResetChannel", + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + } + ] + }, + 0.001 + ], + [ + { + "cirq_type": "OpIdentifier", + "gate_type": "ZPowGate", + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + } + ] + }, + 0.001 + ], + [ + { + "cirq_type": "OpIdentifier", + "gate_type": "MeasurementGate", + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + } + ] + }, + 0.001 + ], + [ + { + "cirq_type": "OpIdentifier", + "gate_type": "PhasedXZGate", + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + } + ] + }, + 0.001 + ] + ], + "validate": true +} diff --git a/cirq-core/cirq/protocols/json_test_data/SuperconductingQubitsNoiseProperties.repr b/cirq-core/cirq/protocols/json_test_data/SuperconductingQubitsNoiseProperties.repr new file mode 100644 index 00000000000..8f36d7cb86c --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/SuperconductingQubitsNoiseProperties.repr @@ -0,0 +1,21 @@ +cirq.SuperconductingQubitsNoiseProperties( + gate_times_ns={ + cirq.ops.common_gates.ZPowGate: 25.0, + cirq.ops.measurement_gate.MeasurementGate: 4000.0, + cirq.ops.common_channels.ResetChannel: 250.0, + cirq.ops.phased_x_z_gate.PhasedXZGate: 25.0, + cirq.ops.fsim_gate.FSimGate: 32.0, + cirq.ops.fsim_gate.PhasedFSimGate: 32.0, + cirq.ops.swap_gates.ISwapPowGate: 32.0, + cirq.ops.common_gates.CZPowGate: 32.0 + }, + t1_ns={cirq.LineQubit(0): 100000.0}, + tphi_ns={cirq.LineQubit(0): 200000.0}, + ro_fidelities={cirq.LineQubit(0): [0.001, 0.01]}, + gate_pauli_errors={ + cirq.devices.noise_utils.OpIdentifier(cirq.ops.phased_x_z_gate.PhasedXZGate, cirq.LineQubit(0)): 0.001, + cirq.devices.noise_utils.OpIdentifier(cirq.ops.measurement_gate.MeasurementGate, cirq.LineQubit(0)): 0.001, + cirq.devices.noise_utils.OpIdentifier(cirq.ops.common_gates.ZPowGate, cirq.LineQubit(0)): 0.001, + cirq.devices.noise_utils.OpIdentifier(cirq.ops.common_channels.ResetChannel, cirq.LineQubit(0)): 0.001 + } +) From 35b8f6a07d4769c4033d0ad985fc4de12988ce83 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 25 Feb 2022 13:20:51 -0800 Subject: [PATCH 2/6] Address review comments. --- ...superconducting_qubits_noise_properties.py | 185 ++++++------------ ...conducting_qubits_noise_properties_test.py | 176 ++++++++--------- cirq-core/cirq/json_resolver_cache.py | 1 - .../SuperconductingQubitsNoiseProperties.json | 122 ------------ .../SuperconductingQubitsNoiseProperties.repr | 21 -- 5 files changed, 143 insertions(+), 362 deletions(-) delete mode 100644 cirq-core/cirq/protocols/json_test_data/SuperconductingQubitsNoiseProperties.json delete mode 100644 cirq-core/cirq/protocols/json_test_data/SuperconductingQubitsNoiseProperties.repr diff --git a/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py b/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py index d5507ff7036..79c18ea0669 100644 --- a/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py +++ b/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py @@ -12,10 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +import abc from dataclasses import dataclass, field +import functools from typing import Dict, TYPE_CHECKING, List, Set -from cirq import ops, protocols, devices +from cirq import ops, devices from cirq.devices.noise_utils import ( OpIdentifier, decoherence_pauli_error, @@ -24,25 +26,10 @@ if TYPE_CHECKING: import cirq -SINGLE_QUBIT_GATES: Set[type] = { - ops.ZPowGate, - ops.PhasedXZGate, - ops.MeasurementGate, - ops.ResetChannel, -} -SYMMETRIC_TWO_QUBIT_GATES: Set[type] = { - ops.FSimGate, - ops.PhasedFSimGate, - ops.ISwapPowGate, - ops.CZPowGate, -} -ASYMMETRIC_TWO_QUBIT_GATES: Set[type] = set() -TWO_QUBIT_GATES = SYMMETRIC_TWO_QUBIT_GATES | ASYMMETRIC_TWO_QUBIT_GATES - # TODO: missing per-device defaults @dataclass -class SuperconductingQubitsNoiseProperties(devices.NoiseProperties): +class SuperconductingQubitsNoiseProperties(devices.NoiseProperties, abc.ABC): """Noise-defining properties for a quantum device. Args: @@ -50,24 +37,24 @@ class SuperconductingQubitsNoiseProperties(devices.NoiseProperties): 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. - ro_fidelities: Dict[cirq.Qid, np.ndarray] of qubits to their readout - fidelity matrix. + 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 OpIdentifiers (a gate and the qubits it targets) to the Pauli error for that operation. Keys in this dict must have defined qubits. - validate: If True, performs validation on input arguments. Defaults - to True. + 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. """ gate_times_ns: Dict[type, float] t1_ns: Dict['cirq.Qid', float] tphi_ns: Dict['cirq.Qid', float] - ro_fidelities: Dict['cirq.Qid', List[float]] + readout_errors: Dict['cirq.Qid', List[float]] gate_pauli_errors: Dict[OpIdentifier, float] validate: bool = True _qubits: List['cirq.Qid'] = field(init=False, default_factory=list) - _depolarizing_error: Dict[OpIdentifier, float] = field(init=False, default_factory=dict) def __post_init__(self): if not self.validate: @@ -92,20 +79,19 @@ def _validate_symmetric_errors(self, field_name: str) -> None: f'Found gate {op_id.gate_type} with {len(op_id.qubits)} qubits. ' 'Symmetric errors can only apply to 2-qubit gates.' ) - elif op_id.gate_type not in self.two_qubit_gates(): - raise ValueError( - f'Found gate {op_id.gate_type} which does not appear in the ' - 'symmetric or asymmetric gate sets.' - ) - else: - # TODO: this assumes op is symmetric. - # If asymmetric gates are added, we will need to update it. + elif op_id.gate_type in self.symmetric_two_qubit_gates(): op_id_swapped = OpIdentifier(op_id.gate_type, *op_id.qubits[::-1]) if op_id_swapped not in gate_error_dict: raise ValueError( f'Operation {op_id} of field {field_name} has errors ' f'but its symmetric id {op_id_swapped} does not.' ) + elif op_id.gate_type not in self.asymmetric_two_qubit_gates(): + # Asymmetric gates do not require validation. + raise ValueError( + f'Found gate {op_id.gate_type} which does not appear in the ' + 'symmetric or asymmetric gate sets.' + ) @property def qubits(self) -> List['cirq.Qid']: @@ -114,72 +100,58 @@ def qubits(self) -> List['cirq.Qid']: self._qubits = sorted(self.t1_ns) return self._qubits - @classmethod + @abc.abstractclassmethod def single_qubit_gates(cls) -> Set[type]: - return SINGLE_QUBIT_GATES + """Returns the set of single-qubit gates this class supports.""" + + @abc.abstractclassmethod + def symmetric_two_qubit_gates(cls) -> Set[type]: + """Returns the set of symmetric two-qubit gates this class supports.""" + + @abc.abstractclassmethod + def asymmetric_two_qubit_gates(cls) -> Set[type]: + """Returns the set of asymmetric two-qubit gates this class supports.""" @classmethod def two_qubit_gates(cls) -> Set[type]: - return TWO_QUBIT_GATES + """Returns the set of all two-qubit gates this class supports.""" + return cls.symmetric_two_qubit_gates() | cls.asymmetric_two_qubit_gates() @classmethod def expected_gates(cls) -> Set[type]: + """Returns the set of all gates this class supports.""" return cls.single_qubit_gates() | cls.two_qubit_gates() - def get_depolarizing_error(self) -> Dict[OpIdentifier, float]: - """Returns the portion of Pauli error from depolarization. - - The result of this method is memoized. - """ - if self._depolarizing_error: - return self._depolarizing_error + def _get_pauli_error(self, p_error: float, op_id: OpIdentifier): + time_ns = float(self.gate_times_ns[op_id.gate_type]) + for q in op_id.qubits: + p_error -= decoherence_pauli_error(self.t1_ns[q], self.tphi_ns[q], time_ns) + return p_error + @functools.cached_property + def _depolarizing_error(self) -> Dict[OpIdentifier, float]: + """Returns the portion of Pauli error from depolarization.""" depol_errors = {} for op_id, p_error in self.gate_pauli_errors.items(): gate_type = op_id.gate_type - if gate_type in self.single_qubit_gates(): - if issubclass(gate_type, ops.MeasurementGate): - # Non-measurement error can be ignored on measurement gates. - continue - if len(op_id.qubits) != 1: - raise ValueError( - f'Gate {gate_type} only takes one qubit, but {op_id.qubits} were given.' - ) - time_ns = float(self.gate_times_ns[gate_type]) - q0 = op_id.qubits[0] - # Subtract decoherence error. - if q0 in self.t1_ns: - p_error -= decoherence_pauli_error(self.t1_ns[q0], self.tphi_ns[q0], time_ns) - - else: - # This must be a 2-qubit gate. - if gate_type not in self.two_qubit_gates(): - raise ValueError(f'Gate {gate_type} is not in the supported gate list.') - if len(op_id.qubits) != 2: - raise ValueError( - f'Gate {gate_type} takes two qubits, but {op_id.qubits} were given.' - ) - time_ns = float(self.gate_times_ns[gate_type]) - # Subtract decoherence error. - q0, q1 = op_id.qubits - if q0 in self.t1_ns: - p_error -= decoherence_pauli_error(self.t1_ns[q0], self.tphi_ns[q0], time_ns) - if q1 in self.t1_ns: - p_error -= decoherence_pauli_error(self.t1_ns[q1], self.tphi_ns[q1], time_ns) - - depol_errors[op_id] = p_error - # memoization is OK - self._depolarizing_error = depol_errors - return self._depolarizing_error + if gate_type not in self.expected_gates(): + raise ValueError(f'Gate {gate_type} is not in the supported gate list.') + if issubclass(gate_type, ops.MeasurementGate): + # Non-measurement error can be ignored on measurement gates. + continue + expected_qubits = 1 if gate_type in self.single_qubit_gates() else 2 + if len(op_id.qubits) != expected_qubits: + raise ValueError( + f'Gate {gate_type} takes {expected_qubits} qubit(s), ' + f'but {op_id.qubits} were given.' + ) + depol_errors[op_id] = self._get_pauli_error(p_error, op_id) + return depol_errors def build_noise_models(self) -> List['cirq.NoiseModel']: noise_models: List['cirq.NoiseModel'] = [] - if set(self.t1_ns) != set(self.tphi_ns): - raise ValueError( - f'T1 data has qubits {set(self.t1_ns)}, but Tphi has qubits {set(self.tphi_ns)}.' - ) - if self.t1_ns: # level 1 sophistication + if self.t1_ns: noise_models.append( devices.ThermalNoiseModel( set(self.t1_ns.keys()), @@ -196,7 +168,7 @@ def build_noise_models(self) -> List['cirq.NoiseModel']: f'\nGates: {gate_types}\nSupported: {self.expected_gates()}' ) - depolarizing_error = self.get_depolarizing_error() + depolarizing_error = self._depolarizing_error added_pauli_errors = { op_id: ops.depolarize(p_error, len(op_id.qubits)).on(*op_id.qubits) for op_id, p_error in depolarizing_error.items() @@ -207,10 +179,10 @@ def build_noise_models(self) -> List['cirq.NoiseModel']: noise_models.append(devices.InsertionNoiseModel(ops_added=added_pauli_errors)) # This adds per-qubit measurement error BEFORE measurement on those qubits. - if self.ro_fidelities: + if self.readout_errors: added_measure_errors: Dict[OpIdentifier, 'cirq.Operation'] = {} - for qubit in self.ro_fidelities: - p_00, p_11 = self.ro_fidelities[qubit] + for qubit in self.readout_errors: + p_00, p_11 = self.readout_errors[qubit] p = p_11 / (p_00 + p_11) gamma = p_11 / p added_measure_errors[ @@ -222,48 +194,3 @@ def build_noise_models(self) -> List['cirq.NoiseModel']: ) return noise_models - - def __str__(self) -> str: - return 'SuperconductingQubitsNoiseProperties' - - def __repr__(self) -> str: - args = [] - gate_times_repr = ', '.join( - f'{key.__module__}.{key.__qualname__}: {val}' for key, val in self.gate_times_ns.items() - ) - args.append(f' gate_times_ns={{{gate_times_repr}}}') - args.append(f' t1_ns={self.t1_ns!r}') - args.append(f' tphi_ns={self.tphi_ns!r}') - args.append(f' ro_fidelities={self.ro_fidelities!r}') - args.append(f' gate_pauli_errors={self.gate_pauli_errors!r}') - args_str = ',\n'.join(args) - return f'cirq.SuperconductingQubitsNoiseProperties(\n{args_str}\n)' - - def _json_dict_(self): - storage_gate_times = { - protocols.json_cirq_type(key): val for key, val in self.gate_times_ns.items() - } - return { - # JSON requires mappings to have keys of basic types. - # Pairs must be sorted to ensure consistent serialization. - 'gate_times_ns': sorted(storage_gate_times.items(), key=str), - 't1_ns': sorted(self.t1_ns.items()), - 'tphi_ns': sorted(self.tphi_ns.items()), - 'ro_fidelities': sorted(self.ro_fidelities.items()), - 'gate_pauli_errors': sorted(self.gate_pauli_errors.items(), key=str), - 'validate': self.validate, - } - - @classmethod - def _from_json_dict_( - cls, gate_times_ns, t1_ns, tphi_ns, ro_fidelities, gate_pauli_errors, validate, **kwargs - ): - gate_type_times = {protocols.cirq_type_from_json(gate): val for gate, val in gate_times_ns} - return SuperconductingQubitsNoiseProperties( - gate_times_ns=gate_type_times, - t1_ns=dict(t1_ns), - tphi_ns=dict(tphi_ns), - ro_fidelities=dict(ro_fidelities), - gate_pauli_errors=dict(gate_pauli_errors), - validate=validate, - ) diff --git a/cirq-core/cirq/devices/superconducting_qubits_noise_properties_test.py b/cirq-core/cirq/devices/superconducting_qubits_noise_properties_test.py index 006a3476829..82f125df084 100644 --- a/cirq-core/cirq/devices/superconducting_qubits_noise_properties_test.py +++ b/cirq-core/cirq/devices/superconducting_qubits_noise_properties_test.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Dict, List, Tuple +from typing import Dict, List, Set, Tuple import numpy as np import cirq import pytest @@ -22,8 +22,6 @@ ) from cirq.devices.superconducting_qubits_noise_properties import ( SuperconductingQubitsNoiseProperties, - SINGLE_QUBIT_GATES, - TWO_QUBIT_GATES, ) from cirq.devices.noise_utils import ( OpIdentifier, @@ -44,70 +42,80 @@ } -# 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]] -) -> SuperconductingQubitsNoiseProperties: - return SuperconductingQubitsNoiseProperties( - gate_times_ns=DEFAULT_GATE_NS, - t1_ns={q: 1e5 for q in system_qubits}, - tphi_ns={q: 2e5 for q in system_qubits}, - ro_fidelities={q: [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 TWO_QUBIT_GATES for q0, q1 in qubit_pairs}, +def default_props(system_qubits: List[cirq.Qid], qubit_pairs: List[Tuple[cirq.Qid, cirq.Qid]]): + return { + '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: [0.001, 0.01] for q in system_qubits}, + 'gate_pauli_errors': { + **{ + OpIdentifier(g, q): 0.001 + for g in TestNoiseProperties.single_qubit_gates() + for q in system_qubits + }, + **{ + OpIdentifier(g, q0, q1): 0.01 + for g in TestNoiseProperties.two_qubit_gates() + for q0, q1 in qubit_pairs + }, }, - ) + } -def test_str(): - q0 = cirq.LineQubit(0) - props = sample_noise_properties([q0], []) - assert str(props) == 'SuperconductingQubitsNoiseProperties' - +class TestNoiseProperties(SuperconductingQubitsNoiseProperties): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) -def test_repr_evaluation(): - q0 = cirq.LineQubit(0) - props = sample_noise_properties([q0], []) - props_from_repr = eval(repr(props)) - assert props_from_repr == props + @classmethod + def single_qubit_gates(cls) -> Set[type]: + return { + cirq.ZPowGate, + cirq.PhasedXZGate, + cirq.MeasurementGate, + cirq.ResetChannel, + } + @classmethod + def symmetric_two_qubit_gates(cls) -> Set[type]: + return { + cirq.FSimGate, + cirq.PhasedFSimGate, + cirq.ISwapPowGate, + cirq.CZPowGate, + } -def test_json_serialization(): - q0 = cirq.LineQubit(0) - props = sample_noise_properties([q0], []) - props_json = cirq.to_json(props) - props_from_json = cirq.read_json(json_text=props_json) - assert props_from_json == props + @classmethod + def asymmetric_two_qubit_gates(cls) -> Set[type]: + return set() def test_init_validation(): q0, q1, q2 = cirq.LineQubit.range(3) with pytest.raises(ValueError, match='Keys specified for T1 and Tphi are not identical.'): - _ = SuperconductingQubitsNoiseProperties( + _ = TestNoiseProperties( gate_times_ns=DEFAULT_GATE_NS, t1_ns={}, tphi_ns={q0: 1}, - ro_fidelities={q0: [0.1, 0.2]}, + readout_errors={q0: [0.1, 0.2]}, gate_pauli_errors={}, ) with pytest.raises(ValueError, match='Symmetric errors can only apply to 2-qubit gates.'): - _ = SuperconductingQubitsNoiseProperties( + _ = TestNoiseProperties( gate_times_ns=DEFAULT_GATE_NS, t1_ns={q0: 1}, tphi_ns={q0: 1}, - ro_fidelities={q0: [0.1, 0.2]}, + readout_errors={q0: [0.1, 0.2]}, gate_pauli_errors={OpIdentifier(cirq.CCNOT, q0, q1, q2): 0.1}, ) with pytest.raises(ValueError, match='does not appear in the symmetric or asymmetric'): - _ = SuperconductingQubitsNoiseProperties( + _ = TestNoiseProperties( gate_times_ns=DEFAULT_GATE_NS, t1_ns={q0: 1}, tphi_ns={q0: 1}, - ro_fidelities={q0: [0.1, 0.2]}, + readout_errors={q0: [0.1, 0.2]}, gate_pauli_errors={ OpIdentifier(cirq.CNOT, q0, q1): 0.1, OpIdentifier(cirq.CNOT, q1, q0): 0.1, @@ -115,20 +123,20 @@ def test_init_validation(): ) with pytest.raises(ValueError, match='has errors but its symmetric id'): - _ = SuperconductingQubitsNoiseProperties( + _ = TestNoiseProperties( gate_times_ns=DEFAULT_GATE_NS, t1_ns={q0: 1}, tphi_ns={q0: 1}, - ro_fidelities={q0: [0.1, 0.2]}, + readout_errors={q0: [0.1, 0.2]}, gate_pauli_errors={OpIdentifier(cirq.CZPowGate, q0, q1): 0.1}, ) # Single-qubit gates are ignored in symmetric-gate validation. - _ = SuperconductingQubitsNoiseProperties( + _ = TestNoiseProperties( gate_times_ns=DEFAULT_GATE_NS, t1_ns={q0: 1}, tphi_ns={q0: 1}, - ro_fidelities={q0: [0.1, 0.2]}, + readout_errors={q0: [0.1, 0.2]}, gate_pauli_errors={ OpIdentifier(cirq.ZPowGate, q0): 0.1, OpIdentifier(cirq.CZPowGate, q0, q1): 0.1, @@ -137,11 +145,11 @@ def test_init_validation(): ) # All errors are ignored if validation is disabled. - _ = SuperconductingQubitsNoiseProperties( + _ = TestNoiseProperties( gate_times_ns=DEFAULT_GATE_NS, t1_ns={}, tphi_ns={q0: 1}, - ro_fidelities={q0: [0.1, 0.2]}, + readout_errors={q0: [0.1, 0.2]}, gate_pauli_errors={ OpIdentifier(cirq.CCNOT, q0, q1, q2): 0.1, OpIdentifier(cirq.CNOT, q0, q1): 0.1, @@ -152,7 +160,7 @@ def test_init_validation(): def test_qubits(): q0 = cirq.LineQubit(0) - props = sample_noise_properties([q0], []) + props = TestNoiseProperties(**default_props([q0], [])) assert props.qubits == [q0] # Confirm memoization behavior. assert props.qubits == [q0] @@ -161,9 +169,9 @@ def test_qubits(): def test_depol_memoization(): # Verify that depolarizing error is memoized. q0 = cirq.LineQubit(0) - props = sample_noise_properties([q0], []) - depol_error_a = props.get_depolarizing_error() - depol_error_b = props.get_depolarizing_error() + props = TestNoiseProperties(**default_props([q0], [])) + depol_error_a = props._depolarizing_error + depol_error_b = props._depolarizing_error assert depol_error_a == depol_error_b assert depol_error_a is depol_error_b @@ -171,64 +179,52 @@ def test_depol_memoization(): def test_depol_validation(): q0, q1, q2 = cirq.LineQubit.range(3) # Create unvalidated properties with too many qubits on a Z gate. - z_2q_props = SuperconductingQubitsNoiseProperties( + z_2q_props = TestNoiseProperties( gate_times_ns=DEFAULT_GATE_NS, t1_ns={q0: 1}, tphi_ns={q0: 1}, - ro_fidelities={q0: [0.1, 0.2]}, + readout_errors={q0: [0.1, 0.2]}, gate_pauli_errors={OpIdentifier(cirq.ZPowGate, q0, q1): 0.1}, validate=False, ) - with pytest.raises(ValueError, match='only takes one qubit'): - _ = z_2q_props.get_depolarizing_error() + with pytest.raises(ValueError, match='takes 1 qubit'): + _ = z_2q_props._depolarizing_error # Create unvalidated properties with an unsupported gate. - toffoli_props = SuperconductingQubitsNoiseProperties( + toffoli_props = TestNoiseProperties( gate_times_ns=DEFAULT_GATE_NS, t1_ns={q0: 1}, tphi_ns={q0: 1}, - ro_fidelities={q0: [0.1, 0.2]}, + readout_errors={q0: [0.1, 0.2]}, gate_pauli_errors={OpIdentifier(cirq.CCNOT, q0, q1, q2): 0.1}, validate=False, ) with pytest.raises(ValueError, match='not in the supported gate list'): - _ = toffoli_props.get_depolarizing_error() + _ = toffoli_props._depolarizing_error # Create unvalidated properties with too many qubits on a CZ gate. - cz_3q_props = SuperconductingQubitsNoiseProperties( + cz_3q_props = TestNoiseProperties( gate_times_ns=DEFAULT_GATE_NS, t1_ns={q0: 1}, tphi_ns={q0: 1}, - ro_fidelities={q0: [0.1, 0.2]}, + readout_errors={q0: [0.1, 0.2]}, gate_pauli_errors={OpIdentifier(cirq.CZPowGate, q0, q1, q2): 0.1}, validate=False, ) - with pytest.raises(ValueError, match='takes two qubits'): - _ = cz_3q_props.get_depolarizing_error() + with pytest.raises(ValueError, match='takes 2 qubit'): + _ = cz_3q_props._depolarizing_error # If t1_ns is missing, values are filled in as needed. def test_build_noise_model_validation(): q0, q1, q2 = cirq.LineQubit.range(3) - # Create unvalidated properties with mismatched T1 and Tphi qubits. - t1_tphi_props = SuperconductingQubitsNoiseProperties( - gate_times_ns=DEFAULT_GATE_NS, - t1_ns={}, - tphi_ns={q0: 1}, - ro_fidelities={q0: [0.1, 0.2]}, - gate_pauli_errors={}, - validate=False, - ) - with pytest.raises(ValueError, match='but Tphi has qubits'): - _ = t1_tphi_props.build_noise_models() - # Create unvalidated properties with unsupported gates. - toffoli_props = SuperconductingQubitsNoiseProperties( + toffoli_props = TestNoiseProperties( gate_times_ns=DEFAULT_GATE_NS, t1_ns={q0: 1}, tphi_ns={q0: 1}, - ro_fidelities={q0: [0.1, 0.2]}, + readout_errors={q0: [0.1, 0.2]}, gate_pauli_errors={OpIdentifier(cirq.CCNOT, q0, q1, q2): 0.1}, validate=False, ) @@ -247,7 +243,7 @@ def test_build_noise_model_validation(): ) def test_single_qubit_gates(op): q0 = cirq.LineQubit(0) - props = sample_noise_properties([q0], []) + props = TestNoiseProperties(**default_props([q0], [])) model = NoiseModelFromNoiseProperties(props) circuit = cirq.Circuit(op) noisy_circuit = circuit.with_noise(model) @@ -286,7 +282,7 @@ def test_single_qubit_gates(op): ) def test_two_qubit_gates(op): q0, q1 = cirq.LineQubit.range(2) - props = sample_noise_properties([q0, q1], [(q0, q1), (q1, q0)]) + props = TestNoiseProperties(**default_props([q0, q1], [(q0, q1), (q1, q0)])) model = NoiseModelFromNoiseProperties(props) circuit = cirq.Circuit(op) noisy_circuit = circuit.with_noise(model) @@ -323,18 +319,20 @@ def test_two_qubit_gates(op): 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), - ], + props = TestNoiseProperties( + **default_props( + qubits, + [ + (q00, q01), + (q01, q00), + (q10, q11), + (q11, q10), + (q00, q10), + (q10, q00), + (q01, q11), + (q11, q01), + ], + ) ) model = NoiseModelFromNoiseProperties(props) op = cirq.measure(*qubits, key='m') @@ -358,7 +356,7 @@ def test_measure_gates(): def test_wait_gates(): q0 = cirq.LineQubit(0) - props = sample_noise_properties([q0], []) + props = TestNoiseProperties(**default_props([q0], [])) model = NoiseModelFromNoiseProperties(props) op = cirq.wait(q0, nanos=100) circuit = cirq.Circuit(op) diff --git a/cirq-core/cirq/json_resolver_cache.py b/cirq-core/cirq/json_resolver_cache.py index f03c5d4cf1a..f15252c3256 100644 --- a/cirq-core/cirq/json_resolver_cache.py +++ b/cirq-core/cirq/json_resolver_cache.py @@ -160,7 +160,6 @@ def _parallel_gate_op(gate, qubits): 'SqrtIswapTargetGateset': cirq.SqrtIswapTargetGateset, 'StabilizerStateChForm': cirq.StabilizerStateChForm, 'StatePreparationChannel': cirq.StatePreparationChannel, - 'SuperconductingQubitsNoiseProperties': cirq.SuperconductingQubitsNoiseProperties, 'SwapPowGate': cirq.SwapPowGate, 'SymmetricalQidPair': cirq.SymmetricalQidPair, 'SympyCondition': cirq.SympyCondition, diff --git a/cirq-core/cirq/protocols/json_test_data/SuperconductingQubitsNoiseProperties.json b/cirq-core/cirq/protocols/json_test_data/SuperconductingQubitsNoiseProperties.json deleted file mode 100644 index 496c1340121..00000000000 --- a/cirq-core/cirq/protocols/json_test_data/SuperconductingQubitsNoiseProperties.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "cirq_type": "SuperconductingQubitsNoiseProperties", - "gate_times_ns": [ - [ - "CZPowGate", - 32.0 - ], - [ - "FSimGate", - 32.0 - ], - [ - "ISwapPowGate", - 32.0 - ], - [ - "MeasurementGate", - 4000.0 - ], - [ - "PhasedFSimGate", - 32.0 - ], - [ - "PhasedXZGate", - 25.0 - ], - [ - "ResetChannel", - 250.0 - ], - [ - "ZPowGate", - 25.0 - ] - ], - "t1_ns": [ - [ - { - "cirq_type": "LineQubit", - "x": 0 - }, - 100000.0 - ] - ], - "tphi_ns": [ - [ - { - "cirq_type": "LineQubit", - "x": 0 - }, - 200000.0 - ] - ], - "ro_fidelities": [ - [ - { - "cirq_type": "LineQubit", - "x": 0 - }, - [ - 0.001, - 0.01 - ] - ] - ], - "gate_pauli_errors": [ - [ - { - "cirq_type": "OpIdentifier", - "gate_type": "ResetChannel", - "qubits": [ - { - "cirq_type": "LineQubit", - "x": 0 - } - ] - }, - 0.001 - ], - [ - { - "cirq_type": "OpIdentifier", - "gate_type": "ZPowGate", - "qubits": [ - { - "cirq_type": "LineQubit", - "x": 0 - } - ] - }, - 0.001 - ], - [ - { - "cirq_type": "OpIdentifier", - "gate_type": "MeasurementGate", - "qubits": [ - { - "cirq_type": "LineQubit", - "x": 0 - } - ] - }, - 0.001 - ], - [ - { - "cirq_type": "OpIdentifier", - "gate_type": "PhasedXZGate", - "qubits": [ - { - "cirq_type": "LineQubit", - "x": 0 - } - ] - }, - 0.001 - ] - ], - "validate": true -} diff --git a/cirq-core/cirq/protocols/json_test_data/SuperconductingQubitsNoiseProperties.repr b/cirq-core/cirq/protocols/json_test_data/SuperconductingQubitsNoiseProperties.repr deleted file mode 100644 index 8f36d7cb86c..00000000000 --- a/cirq-core/cirq/protocols/json_test_data/SuperconductingQubitsNoiseProperties.repr +++ /dev/null @@ -1,21 +0,0 @@ -cirq.SuperconductingQubitsNoiseProperties( - gate_times_ns={ - cirq.ops.common_gates.ZPowGate: 25.0, - cirq.ops.measurement_gate.MeasurementGate: 4000.0, - cirq.ops.common_channels.ResetChannel: 250.0, - cirq.ops.phased_x_z_gate.PhasedXZGate: 25.0, - cirq.ops.fsim_gate.FSimGate: 32.0, - cirq.ops.fsim_gate.PhasedFSimGate: 32.0, - cirq.ops.swap_gates.ISwapPowGate: 32.0, - cirq.ops.common_gates.CZPowGate: 32.0 - }, - t1_ns={cirq.LineQubit(0): 100000.0}, - tphi_ns={cirq.LineQubit(0): 200000.0}, - ro_fidelities={cirq.LineQubit(0): [0.001, 0.01]}, - gate_pauli_errors={ - cirq.devices.noise_utils.OpIdentifier(cirq.ops.phased_x_z_gate.PhasedXZGate, cirq.LineQubit(0)): 0.001, - cirq.devices.noise_utils.OpIdentifier(cirq.ops.measurement_gate.MeasurementGate, cirq.LineQubit(0)): 0.001, - cirq.devices.noise_utils.OpIdentifier(cirq.ops.common_gates.ZPowGate, cirq.LineQubit(0)): 0.001, - cirq.devices.noise_utils.OpIdentifier(cirq.ops.common_channels.ResetChannel, cirq.LineQubit(0)): 0.001 - } -) From ac0045e231aa5d23786e84106283ec6a2863e985 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 25 Feb 2022 14:19:53 -0800 Subject: [PATCH 3/6] Recover from breakages --- ...superconducting_qubits_noise_properties.py | 30 +++++++++++++------ ...conducting_qubits_noise_properties_test.py | 10 +++---- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py b/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py index 79c18ea0669..dc4b993af3a 100644 --- a/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py +++ b/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py @@ -28,7 +28,9 @@ # TODO: missing per-device defaults -@dataclass +# Type-ignored because mypy cannot handle abstract dataclasses: +# https://github.com/python/mypy/issues/5374 +@dataclass # type: ignore class SuperconductingQubitsNoiseProperties(devices.NoiseProperties, abc.ABC): """Noise-defining properties for a quantum device. @@ -55,6 +57,7 @@ class SuperconductingQubitsNoiseProperties(devices.NoiseProperties, abc.ABC): validate: bool = True _qubits: List['cirq.Qid'] = field(init=False, default_factory=list) + _depolarizing_error: Dict[OpIdentifier, float] = field(init=False, default_factory=dict) def __post_init__(self): if not self.validate: @@ -100,15 +103,18 @@ def qubits(self) -> List['cirq.Qid']: self._qubits = sorted(self.t1_ns) return self._qubits - @abc.abstractclassmethod + @classmethod + @abc.abstractmethod def single_qubit_gates(cls) -> Set[type]: """Returns the set of single-qubit gates this class supports.""" - @abc.abstractclassmethod + @classmethod + @abc.abstractmethod def symmetric_two_qubit_gates(cls) -> Set[type]: """Returns the set of symmetric two-qubit gates this class supports.""" - @abc.abstractclassmethod + @classmethod + @abc.abstractmethod def asymmetric_two_qubit_gates(cls) -> Set[type]: """Returns the set of asymmetric two-qubit gates this class supports.""" @@ -128,9 +134,13 @@ def _get_pauli_error(self, p_error: float, op_id: OpIdentifier): p_error -= decoherence_pauli_error(self.t1_ns[q], self.tphi_ns[q], time_ns) return p_error - @functools.cached_property - def _depolarizing_error(self) -> Dict[OpIdentifier, float]: - """Returns the portion of Pauli error from depolarization.""" + def _get_depolarizing_error(self) -> Dict[OpIdentifier, float]: + """Returns the portion of Pauli error from depolarization. + + The result of this method is memoized.""" + if self._depolarizing_error: + return self._depolarizing_error + depol_errors = {} for op_id, p_error in self.gate_pauli_errors.items(): gate_type = op_id.gate_type @@ -146,7 +156,9 @@ def _depolarizing_error(self) -> Dict[OpIdentifier, float]: f'but {op_id.qubits} were given.' ) depol_errors[op_id] = self._get_pauli_error(p_error, op_id) - return depol_errors + # memoization is OK + self._depolarizing_error = depol_errors + return self._depolarizing_error def build_noise_models(self) -> List['cirq.NoiseModel']: noise_models: List['cirq.NoiseModel'] = [] @@ -168,7 +180,7 @@ def build_noise_models(self) -> List['cirq.NoiseModel']: f'\nGates: {gate_types}\nSupported: {self.expected_gates()}' ) - depolarizing_error = self._depolarizing_error + depolarizing_error = self._get_depolarizing_error() added_pauli_errors = { op_id: ops.depolarize(p_error, len(op_id.qubits)).on(*op_id.qubits) for op_id, p_error in depolarizing_error.items() diff --git a/cirq-core/cirq/devices/superconducting_qubits_noise_properties_test.py b/cirq-core/cirq/devices/superconducting_qubits_noise_properties_test.py index 82f125df084..7b88898b625 100644 --- a/cirq-core/cirq/devices/superconducting_qubits_noise_properties_test.py +++ b/cirq-core/cirq/devices/superconducting_qubits_noise_properties_test.py @@ -170,8 +170,8 @@ def test_depol_memoization(): # Verify that depolarizing error is memoized. q0 = cirq.LineQubit(0) props = TestNoiseProperties(**default_props([q0], [])) - depol_error_a = props._depolarizing_error - depol_error_b = props._depolarizing_error + depol_error_a = props._get_depolarizing_error() + depol_error_b = props._get_depolarizing_error() assert depol_error_a == depol_error_b assert depol_error_a is depol_error_b @@ -188,7 +188,7 @@ def test_depol_validation(): validate=False, ) with pytest.raises(ValueError, match='takes 1 qubit'): - _ = z_2q_props._depolarizing_error + _ = z_2q_props._get_depolarizing_error() # Create unvalidated properties with an unsupported gate. toffoli_props = TestNoiseProperties( @@ -200,7 +200,7 @@ def test_depol_validation(): validate=False, ) with pytest.raises(ValueError, match='not in the supported gate list'): - _ = toffoli_props._depolarizing_error + _ = toffoli_props._get_depolarizing_error() # Create unvalidated properties with too many qubits on a CZ gate. cz_3q_props = TestNoiseProperties( @@ -212,7 +212,7 @@ def test_depol_validation(): validate=False, ) with pytest.raises(ValueError, match='takes 2 qubit'): - _ = cz_3q_props._depolarizing_error + _ = cz_3q_props._get_depolarizing_error() # If t1_ns is missing, values are filled in as needed. From 8f66c536a20068ae0a3731835ff0eb0e0b5b91d6 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 25 Feb 2022 14:41:22 -0800 Subject: [PATCH 4/6] linter wrangling --- .../cirq/devices/superconducting_qubits_noise_properties.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py b/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py index dc4b993af3a..a9def858c45 100644 --- a/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py +++ b/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py @@ -14,7 +14,6 @@ import abc from dataclasses import dataclass, field -import functools from typing import Dict, TYPE_CHECKING, List, Set from cirq import ops, devices From 7d42659e8a7ab559412ede5a2400e412978c1cda Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Wed, 2 Mar 2022 09:45:07 -0800 Subject: [PATCH 5/6] use cached_property --- ...superconducting_qubits_noise_properties.py | 19 ++++++------------- ...conducting_qubits_noise_properties_test.py | 10 +++++----- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py b/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py index a9def858c45..9c572b47709 100644 --- a/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py +++ b/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py @@ -16,7 +16,7 @@ from dataclasses import dataclass, field from typing import Dict, TYPE_CHECKING, List, Set -from cirq import ops, devices +from cirq import _compat, ops, devices from cirq.devices.noise_utils import ( OpIdentifier, decoherence_pauli_error, @@ -56,7 +56,6 @@ class SuperconductingQubitsNoiseProperties(devices.NoiseProperties, abc.ABC): validate: bool = True _qubits: List['cirq.Qid'] = field(init=False, default_factory=list) - _depolarizing_error: Dict[OpIdentifier, float] = field(init=False, default_factory=dict) def __post_init__(self): if not self.validate: @@ -133,13 +132,9 @@ def _get_pauli_error(self, p_error: float, op_id: OpIdentifier): p_error -= decoherence_pauli_error(self.t1_ns[q], self.tphi_ns[q], time_ns) return p_error - def _get_depolarizing_error(self) -> Dict[OpIdentifier, float]: - """Returns the portion of Pauli error from depolarization. - - The result of this method is memoized.""" - if self._depolarizing_error: - return self._depolarizing_error - + @_compat.cached_property + def _depolarizing_error(self) -> Dict[OpIdentifier, float]: + """Returns the portion of Pauli error from depolarization.""" depol_errors = {} for op_id, p_error in self.gate_pauli_errors.items(): gate_type = op_id.gate_type @@ -155,9 +150,7 @@ def _get_depolarizing_error(self) -> Dict[OpIdentifier, float]: f'but {op_id.qubits} were given.' ) depol_errors[op_id] = self._get_pauli_error(p_error, op_id) - # memoization is OK - self._depolarizing_error = depol_errors - return self._depolarizing_error + return depol_errors def build_noise_models(self) -> List['cirq.NoiseModel']: noise_models: List['cirq.NoiseModel'] = [] @@ -179,7 +172,7 @@ def build_noise_models(self) -> List['cirq.NoiseModel']: f'\nGates: {gate_types}\nSupported: {self.expected_gates()}' ) - depolarizing_error = self._get_depolarizing_error() + depolarizing_error = self._depolarizing_error added_pauli_errors = { op_id: ops.depolarize(p_error, len(op_id.qubits)).on(*op_id.qubits) for op_id, p_error in depolarizing_error.items() diff --git a/cirq-core/cirq/devices/superconducting_qubits_noise_properties_test.py b/cirq-core/cirq/devices/superconducting_qubits_noise_properties_test.py index 7b88898b625..82f125df084 100644 --- a/cirq-core/cirq/devices/superconducting_qubits_noise_properties_test.py +++ b/cirq-core/cirq/devices/superconducting_qubits_noise_properties_test.py @@ -170,8 +170,8 @@ def test_depol_memoization(): # Verify that depolarizing error is memoized. q0 = cirq.LineQubit(0) props = TestNoiseProperties(**default_props([q0], [])) - depol_error_a = props._get_depolarizing_error() - depol_error_b = props._get_depolarizing_error() + depol_error_a = props._depolarizing_error + depol_error_b = props._depolarizing_error assert depol_error_a == depol_error_b assert depol_error_a is depol_error_b @@ -188,7 +188,7 @@ def test_depol_validation(): validate=False, ) with pytest.raises(ValueError, match='takes 1 qubit'): - _ = z_2q_props._get_depolarizing_error() + _ = z_2q_props._depolarizing_error # Create unvalidated properties with an unsupported gate. toffoli_props = TestNoiseProperties( @@ -200,7 +200,7 @@ def test_depol_validation(): validate=False, ) with pytest.raises(ValueError, match='not in the supported gate list'): - _ = toffoli_props._get_depolarizing_error() + _ = toffoli_props._depolarizing_error # Create unvalidated properties with too many qubits on a CZ gate. cz_3q_props = TestNoiseProperties( @@ -212,7 +212,7 @@ def test_depol_validation(): validate=False, ) with pytest.raises(ValueError, match='takes 2 qubit'): - _ = cz_3q_props._get_depolarizing_error() + _ = cz_3q_props._depolarizing_error # If t1_ns is missing, values are filled in as needed. From 25632762051d9ef0703e7fee159056016ed60024 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 7 Mar 2022 09:11:14 -0800 Subject: [PATCH 6/6] nits nits nits --- ...superconducting_qubits_noise_properties.py | 47 ++++++++----------- ...conducting_qubits_noise_properties_test.py | 30 ++++-------- 2 files changed, 27 insertions(+), 50 deletions(-) diff --git a/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py b/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py index 9c572b47709..43a165e89f9 100644 --- a/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py +++ b/cirq-core/cirq/devices/superconducting_qubits_noise_properties.py @@ -12,15 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. + +"""Class for representing noise on a superconducting qubit device.""" + import abc from dataclasses import dataclass, field -from typing import Dict, TYPE_CHECKING, List, Set +from typing import Dict, TYPE_CHECKING, List, Set, Type from cirq import _compat, ops, devices -from cirq.devices.noise_utils import ( - OpIdentifier, - decoherence_pauli_error, -) +from cirq.devices import noise_utils if TYPE_CHECKING: import cirq @@ -31,7 +31,7 @@ # https://github.com/python/mypy/issues/5374 @dataclass # type: ignore class SuperconductingQubitsNoiseProperties(devices.NoiseProperties, abc.ABC): - """Noise-defining properties for a quantum device. + """Noise-defining properties for a superconducting-qubit-based device. Args: gate_times_ns: Dict[type, float] of gate types to their duration on @@ -40,7 +40,7 @@ class SuperconductingQubitsNoiseProperties(devices.NoiseProperties, abc.ABC): 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 OpIdentifiers (a gate and the qubits it + 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. validate: If True, verifies that t1 and tphi qubits sets match, and @@ -52,7 +52,7 @@ class SuperconductingQubitsNoiseProperties(devices.NoiseProperties, abc.ABC): t1_ns: Dict['cirq.Qid', float] tphi_ns: Dict['cirq.Qid', float] readout_errors: Dict['cirq.Qid', List[float]] - gate_pauli_errors: Dict[OpIdentifier, float] + gate_pauli_errors: Dict[noise_utils.OpIdentifier, float] validate: bool = True _qubits: List['cirq.Qid'] = field(init=False, default_factory=list) @@ -81,7 +81,7 @@ def _validate_symmetric_errors(self, field_name: str) -> None: 'Symmetric errors can only apply to 2-qubit gates.' ) elif op_id.gate_type in self.symmetric_two_qubit_gates(): - op_id_swapped = OpIdentifier(op_id.gate_type, *op_id.qubits[::-1]) + op_id_swapped = noise_utils.OpIdentifier(op_id.gate_type, *op_id.qubits[::-1]) if op_id_swapped not in gate_error_dict: raise ValueError( f'Operation {op_id} of field {field_name} has errors ' @@ -103,43 +103,41 @@ def qubits(self) -> List['cirq.Qid']: @classmethod @abc.abstractmethod - def single_qubit_gates(cls) -> Set[type]: + def single_qubit_gates(cls) -> Set[Type[ops.Gate]]: """Returns the set of single-qubit gates this class supports.""" @classmethod @abc.abstractmethod - def symmetric_two_qubit_gates(cls) -> Set[type]: + def symmetric_two_qubit_gates(cls) -> Set[Type[ops.Gate]]: """Returns the set of symmetric two-qubit gates this class supports.""" @classmethod @abc.abstractmethod - def asymmetric_two_qubit_gates(cls) -> Set[type]: + def asymmetric_two_qubit_gates(cls) -> Set[Type[ops.Gate]]: """Returns the set of asymmetric two-qubit gates this class supports.""" @classmethod - def two_qubit_gates(cls) -> Set[type]: + def two_qubit_gates(cls) -> Set[Type[ops.Gate]]: """Returns the set of all two-qubit gates this class supports.""" return cls.symmetric_two_qubit_gates() | cls.asymmetric_two_qubit_gates() @classmethod - def expected_gates(cls) -> Set[type]: + def expected_gates(cls) -> Set[Type[ops.Gate]]: """Returns the set of all gates this class supports.""" return cls.single_qubit_gates() | cls.two_qubit_gates() - def _get_pauli_error(self, p_error: float, op_id: OpIdentifier): + 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 -= decoherence_pauli_error(self.t1_ns[q], self.tphi_ns[q], time_ns) + p_error -= noise_utils.decoherence_pauli_error(self.t1_ns[q], self.tphi_ns[q], time_ns) return p_error @_compat.cached_property - def _depolarizing_error(self) -> Dict[OpIdentifier, float]: + def _depolarizing_error(self) -> Dict[noise_utils.OpIdentifier, float]: """Returns the portion of Pauli error from depolarization.""" depol_errors = {} for op_id, p_error in self.gate_pauli_errors.items(): gate_type = op_id.gate_type - if gate_type not in self.expected_gates(): - raise ValueError(f'Gate {gate_type} is not in the supported gate list.') if issubclass(gate_type, ops.MeasurementGate): # Non-measurement error can be ignored on measurement gates. continue @@ -165,13 +163,6 @@ def build_noise_models(self) -> List['cirq.NoiseModel']: ) ) - gate_types = set(op_id.gate_type for op_id in self.gate_pauli_errors) - if not gate_types.issubset(self.expected_gates()): - raise ValueError( - 'Some gates are not in the supported set.' - f'\nGates: {gate_types}\nSupported: {self.expected_gates()}' - ) - depolarizing_error = self._depolarizing_error added_pauli_errors = { op_id: ops.depolarize(p_error, len(op_id.qubits)).on(*op_id.qubits) @@ -184,13 +175,13 @@ def build_noise_models(self) -> List['cirq.NoiseModel']: # This adds per-qubit measurement error BEFORE measurement on those qubits. if self.readout_errors: - added_measure_errors: Dict[OpIdentifier, 'cirq.Operation'] = {} + added_measure_errors: Dict[noise_utils.OpIdentifier, 'cirq.Operation'] = {} for qubit in self.readout_errors: p_00, p_11 = self.readout_errors[qubit] p = p_11 / (p_00 + p_11) gamma = p_11 / p added_measure_errors[ - OpIdentifier(ops.MeasurementGate, qubit) + noise_utils.OpIdentifier(ops.MeasurementGate, qubit) ] = ops.generalized_amplitude_damp(p, gamma).on(qubit) noise_models.append( diff --git a/cirq-core/cirq/devices/superconducting_qubits_noise_properties_test.py b/cirq-core/cirq/devices/superconducting_qubits_noise_properties_test.py index 82f125df084..e5457a00dcf 100644 --- a/cirq-core/cirq/devices/superconducting_qubits_noise_properties_test.py +++ b/cirq-core/cirq/devices/superconducting_qubits_noise_properties_test.py @@ -132,7 +132,7 @@ def test_init_validation(): ) # Single-qubit gates are ignored in symmetric-gate validation. - _ = TestNoiseProperties( + test_props = TestNoiseProperties( gate_times_ns=DEFAULT_GATE_NS, t1_ns={q0: 1}, tphi_ns={q0: 1}, @@ -143,6 +143,9 @@ def test_init_validation(): OpIdentifier(cirq.CZPowGate, q1, q0): 0.1, }, ) + assert test_props.expected_gates() == ( + TestNoiseProperties.single_qubit_gates() | TestNoiseProperties.symmetric_two_qubit_gates() + ) # All errors are ignored if validation is disabled. _ = TestNoiseProperties( @@ -190,18 +193,6 @@ def test_depol_validation(): with pytest.raises(ValueError, match='takes 1 qubit'): _ = z_2q_props._depolarizing_error - # Create unvalidated properties with an unsupported gate. - toffoli_props = TestNoiseProperties( - gate_times_ns=DEFAULT_GATE_NS, - t1_ns={q0: 1}, - tphi_ns={q0: 1}, - readout_errors={q0: [0.1, 0.2]}, - gate_pauli_errors={OpIdentifier(cirq.CCNOT, q0, q1, q2): 0.1}, - validate=False, - ) - with pytest.raises(ValueError, match='not in the supported gate list'): - _ = toffoli_props._depolarizing_error - # Create unvalidated properties with too many qubits on a CZ gate. cz_3q_props = TestNoiseProperties( gate_times_ns=DEFAULT_GATE_NS, @@ -214,22 +205,17 @@ def test_depol_validation(): with pytest.raises(ValueError, match='takes 2 qubit'): _ = cz_3q_props._depolarizing_error - # If t1_ns is missing, values are filled in as needed. - - -def test_build_noise_model_validation(): - q0, q1, q2 = cirq.LineQubit.range(3) - # Create unvalidated properties with unsupported gates. + # Unsupported gates are only checked in constructor. toffoli_props = TestNoiseProperties( gate_times_ns=DEFAULT_GATE_NS, t1_ns={q0: 1}, tphi_ns={q0: 1}, readout_errors={q0: [0.1, 0.2]}, - gate_pauli_errors={OpIdentifier(cirq.CCNOT, q0, q1, q2): 0.1}, + gate_pauli_errors={OpIdentifier(cirq.CCXPowGate, q0, q1, q2): 0.1}, validate=False, ) - with pytest.raises(ValueError, match='Some gates are not in the supported set.'): - _ = toffoli_props.build_noise_models() + with pytest.raises(ValueError): + _ = toffoli_props._depolarizing_error @pytest.mark.parametrize(