-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add noise amplification transformer #6665
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 all commits
b184463
813704e
d7547da
57382e5
69355c5
6409766
bccffec
4078f86
1119ad9
65a5e16
9472dfd
174553d
8555599
71c10dd
a76f382
6a34db2
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,114 @@ | ||||||
# Copyright 2024 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 collections.abc import Mapping | ||||||
|
||||||
from typing import cast | ||||||
from cirq import ops, circuits | ||||||
from cirq.transformers import transformer_api | ||||||
import numpy as np | ||||||
|
||||||
|
||||||
def _gate_in_moment(gate: ops.Gate, moment: circuits.Moment) -> bool: | ||||||
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. nit for performance: return any(op.gate == gate for op in moment) |
||||||
"""Check whether `gate` is in `moment`.""" | ||||||
return any(op.gate == gate for op in moment) | ||||||
|
||||||
|
||||||
@transformer_api.transformer | ||||||
class DepolerizingNoiseTransformer: | ||||||
"""Add local depolarizing noise after two-qubit gates in a specified circuit. More specifically, | ||||||
with probability p, append a random non-identity two-qubit Pauli operator after each specified | ||||||
two-qubit gate. | ||||||
|
||||||
Attrs: | ||||||
p: The probability with which to add noise. | ||||||
target_gate: Add depolarizing nose after this type of gate | ||||||
""" | ||||||
|
||||||
def __init__( | ||||||
self, p: float | Mapping[tuple[ops.Qid, ops.Qid], float], target_gate: ops.Gate = ops.CZ | ||||||
): | ||||||
"""Initialize the depolarizing noise transformer with some depolarizing probability and | ||||||
target gate. | ||||||
|
||||||
Args: | ||||||
p: The depolarizing probability, either a single float or a mapping from pairs of qubits | ||||||
to floats. | ||||||
target_gate: The gate after which to add depolarizing noise. | ||||||
|
||||||
Raises: | ||||||
TypeError: If `p` is not either be a float or a mapping from sorted qubit pairs to | ||||||
floats. | ||||||
""" | ||||||
|
||||||
if not isinstance(p, (Mapping, float)): | ||||||
raise TypeError( # pragma: no cover | ||||||
"p must either be a float or a mapping from" # pragma: no cover | ||||||
+ "sorted qubit pairs to floats" # pragma: no cover | ||||||
) # pragma: no cover | ||||||
self.p = p | ||||||
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. you can resolve
Suggested change
|
||||||
self.p_func = ( | ||||||
(lambda _: p) | ||||||
if isinstance(p, (int, float)) | ||||||
else (lambda pair: cast(Mapping, p).get(pair, 0.0)) | ||||||
) | ||||||
self.target_gate = target_gate | ||||||
|
||||||
def __call__( | ||||||
self, | ||||||
circuit: circuits.AbstractCircuit, | ||||||
rng: np.random.Generator | None = None, | ||||||
*, | ||||||
context: transformer_api.TransformerContext | None = None, | ||||||
): | ||||||
"""Apply the transformer to the given circuit. | ||||||
|
||||||
Args: | ||||||
circuit: The circuit to add noise to. | ||||||
context: Not used; to satisfy transformer API. | ||||||
|
||||||
Returns: | ||||||
The transformed circuit. | ||||||
""" | ||||||
if rng is None: | ||||||
rng = np.random.default_rng() | ||||||
target_gate = self.target_gate | ||||||
|
||||||
# add random Pauli gates with probability p after each of the specified gate | ||||||
assert target_gate.num_qubits() == 2, "`target_gate` must be a two-qubit gate." | ||||||
paulis = [ops.I, ops.X, ops.Y, ops.Z] | ||||||
new_moments = [] | ||||||
for moment in circuit: | ||||||
new_moments.append(moment) | ||||||
if _gate_in_moment(target_gate, moment): | ||||||
# add a new moment with the Paulis | ||||||
target_pairs = { | ||||||
tuple(sorted(op.qubits)) for op in moment.operations if op.gate == target_gate | ||||||
} | ||||||
added_moment_ops = [] | ||||||
for pair in target_pairs: | ||||||
pair_sorted_tuple = (pair[0], pair[1]) | ||||||
p_i = self.p_func(pair_sorted_tuple) | ||||||
apply = rng.choice([True, False], p=[p_i, 1 - p_i]) | ||||||
if apply: | ||||||
choices = [ | ||||||
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. [optional suggestion] this creates 17 objects for each pair, 16 pauli pairs + the numpy array. consider doing it this way pauli_a_idx = np.choice(4)
if pauli_a_idx == 0:
pauli_b_idx = np.choice(3) + 1
else:
pauli_b_idx = np.choice(4)
paulit_to_apply = paulis[pauli_a_idx](pair[0]), paulis[pauli_b_idx](pair[1]) 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. Thanks, Nour. I agree that this would be more efficient code, but I'm going to leave it as is for now so that I can move on. (It is already very fast as written.) |
||||||
(pauli_a(pair[0]), pauli_b(pair[1])) | ||||||
for pauli_a in paulis | ||||||
for pauli_b in paulis | ||||||
][1:] | ||||||
pauli_to_apply = rng.choice(np.array(choices, dtype=object)) | ||||||
added_moment_ops.append(pauli_to_apply) | ||||||
if len(added_moment_ops) > 0: | ||||||
new_moments.append(circuits.Moment(*added_moment_ops)) | ||||||
return circuits.Circuit.from_moments(*new_moments) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
# Copyright 2024 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 cirq import ops, circuits, devices | ||
import cirq.transformers.noise_adding as na | ||
import numpy as np | ||
|
||
|
||
def test_noise_adding(): | ||
qubits = devices.LineQubit.range(4) | ||
one_layer = circuits.Circuit(ops.CZ(*qubits[:2]), ops.CZ(*qubits[2:])) | ||
circuit = one_layer * 10 | ||
|
||
# test that p=0 does nothing | ||
transformed_circuit_p0 = na.DepolerizingNoiseTransformer(0.0)(circuit) | ||
assert transformed_circuit_p0 == circuit | ||
|
||
# test that p=1 doubles the circuit depth | ||
transformed_circuit_p1 = na.DepolerizingNoiseTransformer(1.0)(circuit) | ||
assert len(transformed_circuit_p1) == 20 | ||
|
||
# test that we get a deterministic result when using a specific rng | ||
rng = np.random.default_rng(0) | ||
transformed_circuit_p0_03 = na.DepolerizingNoiseTransformer(0.03)(circuit, rng=rng) | ||
expected_circuit = ( | ||
one_layer * 2 | ||
+ circuits.Circuit(ops.I(qubits[2]), ops.Z(qubits[3])) | ||
+ one_layer * 4 | ||
+ circuits.Circuit(ops.Z(qubits[0]), ops.X(qubits[1])) | ||
+ one_layer * 4 | ||
+ circuits.Circuit(ops.I(qubits[2]), ops.X(qubits[3])) | ||
) | ||
assert transformed_circuit_p0_03 == expected_circuit | ||
|
||
# test that supplying a dictionary for p works | ||
transformed_circuit_p_dict = na.DepolerizingNoiseTransformer( | ||
{tuple(qubits[:2]): 1.0, tuple(qubits[2:]): 0.0} | ||
)(circuit) | ||
assert len(transformed_circuit_p_dict) == 20 # depth should be doubled | ||
assert transformed_circuit_p_dict[1::2].all_qubits() == frozenset( | ||
qubits[:2] | ||
) # no single-qubit gates get added to qubits[2:] |
Uh oh!
There was an error while loading. Please reload this page.