From c7c0fa8cbf0ac9148a89a1d70246f1230d176907 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Tue, 14 Dec 2021 14:30:27 -0800 Subject: [PATCH] Insertion noise model (#4672) This PR is part of #4640. It adds the `InsertionNoiseModel`, which injects noise based on a user-defined map. #4671 is a prerequisite for this PR. The only files that need to be reviewed in this PR are: - `cirq-core/cirq/devices/...` - `__init__.py` - `insertion_noise_model[_test].py` --- cirq-core/cirq/devices/__init__.py | 4 + .../cirq/devices/insertion_noise_model.py | 68 ++++++++++++ .../devices/insertion_noise_model_test.py | 103 ++++++++++++++++++ 3 files changed, 175 insertions(+) create mode 100644 cirq-core/cirq/devices/insertion_noise_model.py create mode 100644 cirq-core/cirq/devices/insertion_noise_model_test.py diff --git a/cirq-core/cirq/devices/__init__.py b/cirq-core/cirq/devices/__init__.py index 72f793a7e46..89f5d1e22b5 100644 --- a/cirq-core/cirq/devices/__init__.py +++ b/cirq-core/cirq/devices/__init__.py @@ -48,6 +48,10 @@ draw_placements, ) +from cirq.devices.insertion_noise_model import ( + InsertionNoiseModel, +) + from cirq.devices.noise_utils import ( OpIdentifier, decay_constant_to_xeb_fidelity, diff --git a/cirq-core/cirq/devices/insertion_noise_model.py b/cirq-core/cirq/devices/insertion_noise_model.py new file mode 100644 index 00000000000..6d6cf66a068 --- /dev/null +++ b/cirq-core/cirq/devices/insertion_noise_model.py @@ -0,0 +1,68 @@ +# Copyright 2021 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import dataclasses +from typing import TYPE_CHECKING, Dict, List, Optional, Sequence + +from cirq import devices, ops +from cirq.devices import noise_utils + +if TYPE_CHECKING: + import cirq + + +@dataclasses.dataclass +class InsertionNoiseModel(devices.NoiseModel): + """Simple base noise model for inserting operations. + + Operations generated by this model for a given moment are all added into a + single "noise moment", which is added before or after the original moment + based on `prepend`. + + Args: + ops_added: a map of gate types (and optionally, qubits they act on) to + operations that should be added. + prepend: whether to add the new moment before the current one. + require_physical_tag: whether to only apply noise to operations tagged + with PHYSICAL_GATE_TAG. + """ + + ops_added: Dict[noise_utils.OpIdentifier, 'cirq.Operation'] = dataclasses.field( + default_factory=dict + ) + prepend: bool = False + require_physical_tag: bool = True + + def noisy_moment( + self, moment: 'cirq.Moment', system_qubits: Sequence['cirq.Qid'] + ) -> 'cirq.OP_TREE': + noise_ops: List['cirq.Operation'] = [] + candidate_ops = [ + op + for op in moment + if (not self.require_physical_tag) or noise_utils.PHYSICAL_GATE_TAG in op.tags + ] + for op in candidate_ops: + match_id: Optional[noise_utils.OpIdentifier] = None + candidate_ids = [op_id for op_id in self.ops_added if op in op_id] + for op_id in candidate_ids: + if match_id is None or op_id.is_proper_subtype_of(match_id): + match_id = op_id + if match_id is not None: + noise_ops.append(self.ops_added[match_id]) + if not noise_ops: + return [moment] + if self.prepend: + return [ops.Moment(noise_ops), moment] + return [moment, ops.Moment(noise_ops)] diff --git a/cirq-core/cirq/devices/insertion_noise_model_test.py b/cirq-core/cirq/devices/insertion_noise_model_test.py new file mode 100644 index 00000000000..15216def58c --- /dev/null +++ b/cirq-core/cirq/devices/insertion_noise_model_test.py @@ -0,0 +1,103 @@ +# Copyright 2021 The Cirq Developers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import cirq +from cirq.devices.insertion_noise_model import InsertionNoiseModel +from cirq.devices.noise_utils import ( + PHYSICAL_GATE_TAG, + OpIdentifier, +) + + +def test_insertion_noise(): + q0, q1 = cirq.LineQubit.range(2) + op_id0 = OpIdentifier(cirq.XPowGate, q0) + op_id1 = OpIdentifier(cirq.ZPowGate, q1) + model = InsertionNoiseModel( + {op_id0: cirq.T(q0), op_id1: cirq.H(q1)}, require_physical_tag=False + ) + assert not model.prepend + + moment_0 = cirq.Moment(cirq.X(q0), cirq.X(q1)) + assert model.noisy_moment(moment_0, system_qubits=[q0, q1]) == [ + moment_0, + cirq.Moment(cirq.T(q0)), + ] + + moment_1 = cirq.Moment(cirq.Z(q0), cirq.Z(q1)) + assert model.noisy_moment(moment_1, system_qubits=[q0, q1]) == [ + moment_1, + cirq.Moment(cirq.H(q1)), + ] + + moment_2 = cirq.Moment(cirq.X(q0), cirq.Z(q1)) + assert model.noisy_moment(moment_2, system_qubits=[q0, q1]) == [ + moment_2, + cirq.Moment(cirq.T(q0), cirq.H(q1)), + ] + + moment_3 = cirq.Moment(cirq.Z(q0), cirq.X(q1)) + assert model.noisy_moment(moment_3, system_qubits=[q0, q1]) == [moment_3] + + +def test_prepend(): + q0, q1 = cirq.LineQubit.range(2) + op_id0 = OpIdentifier(cirq.XPowGate, q0) + op_id1 = OpIdentifier(cirq.ZPowGate, q1) + model = InsertionNoiseModel( + {op_id0: cirq.T(q0), op_id1: cirq.H(q1)}, prepend=True, require_physical_tag=False + ) + + moment_0 = cirq.Moment(cirq.X(q0), cirq.Z(q1)) + assert model.noisy_moment(moment_0, system_qubits=[q0, q1]) == [ + cirq.Moment(cirq.T(q0), cirq.H(q1)), + moment_0, + ] + + +def test_require_physical_tag(): + q0, q1 = cirq.LineQubit.range(2) + op_id0 = OpIdentifier(cirq.XPowGate, q0) + op_id1 = OpIdentifier(cirq.ZPowGate, q1) + model = InsertionNoiseModel({op_id0: cirq.T(q0), op_id1: cirq.H(q1)}) + assert model.require_physical_tag + + moment_0 = cirq.Moment(cirq.X(q0).with_tags(PHYSICAL_GATE_TAG), cirq.Z(q1)) + assert model.noisy_moment(moment_0, system_qubits=[q0, q1]) == [ + moment_0, + cirq.Moment(cirq.T(q0)), + ] + + +def test_supertype_matching(): + # Demonstrate that the model applies the closest matching type + # if multiple types match a given gate. + q0 = cirq.LineQubit(0) + op_id0 = OpIdentifier(cirq.Gate, q0) + op_id1 = OpIdentifier(cirq.XPowGate, q0) + model = InsertionNoiseModel( + {op_id0: cirq.T(q0), op_id1: cirq.S(q0)}, require_physical_tag=False + ) + + moment_0 = cirq.Moment(cirq.Rx(rads=1).on(q0)) + assert model.noisy_moment(moment_0, system_qubits=[q0]) == [ + moment_0, + cirq.Moment(cirq.S(q0)), + ] + + moment_1 = cirq.Moment(cirq.Y(q0)) + assert model.noisy_moment(moment_1, system_qubits=[q0]) == [ + moment_1, + cirq.Moment(cirq.T(q0)), + ]