Skip to content

Commit bc4cd6d

Browse files
Added random single-qubit CUE and Clifford gates (#6670)
* added cue and clifford gates * promoted rng check to the parent function * rename variables * minor fixes * minor formatting fix * added test coverage for ValueError * more formatting change --------- Co-authored-by: Seneca Meeks <[email protected]>
1 parent 45a6bbb commit bc4cd6d

File tree

2 files changed

+128
-55
lines changed

2 files changed

+128
-55
lines changed

cirq-core/cirq/transformers/randomized_measurements.py

Lines changed: 105 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -13,97 +13,158 @@
1313
# limitations under the License.
1414

1515
from collections.abc import Sequence
16-
from typing import Any, Literal
16+
from typing import Any
1717

1818
import cirq
1919
import numpy as np
20+
from cirq.ops import SingleQubitCliffordGate
2021
from cirq.transformers import transformer_api
2122

2223

2324
@transformer_api.transformer
2425
class RandomizedMeasurements:
25-
"""A transformer that appends a moment of random rotations to map qubits to
26-
random pauli bases."""
26+
"""A transformer that appends a moment of random rotations from a given unitary ensemble (pauli,
27+
clifford, cue)"""
2728

2829
def __init__(self, subsystem: Sequence[int] | None = None):
2930
"""Class structure for performing and analyzing a general randomized measurement protocol.
3031
For more details on the randomized measurement toolbox see https://arxiv.org/abs/2203.11374
3132
3233
Args:
3334
subsystem: The specific subsystem (e.g qubit index) to measure in random basis
35+
rest of the qubits are measured in the computational basis
3436
"""
3537
self.subsystem = subsystem
3638

3739
def __call__(
3840
self,
39-
circuit: 'cirq.AbstractCircuit',
41+
circuit: "cirq.AbstractCircuit",
42+
unitary_ensemble: str = "pauli",
4043
rng: np.random.Generator | None = None,
4144
*,
4245
context: transformer_api.TransformerContext | None = None,
43-
):
46+
) -> "cirq.Circuit":
4447
"""Apply the transformer to the given circuit. Given an input circuit returns
45-
a list of circuits with the pre-measurement unitaries. If no arguments are specified,
46-
it will default to computing the entropy of the entire circuit.
48+
a new circuit with the pre-measurement unitaries and measurements gates added.
49+
to the qubits in the subsystem provided.If no subsystem is specified in the
50+
construction of this class it defaults to measuring all the qubits in the
51+
randomized bases.
4752
4853
Args:
4954
circuit: The circuit to add randomized measurements to.
50-
rng: Random number generator.
55+
unitary_ensemble: Choice of unitary ensemble (pauli/clifford/cue(circular
56+
unitary ensemble))
5157
context: Not used; to satisfy transformer API.
58+
rng: Random number generator.
5259
5360
Returns:
54-
List of circuits with pre-measurement unitaries and measurements added
61+
A circuit with pre-measurement unitaries and measurements added
5562
"""
63+
64+
all_qubits = sorted(circuit.all_qubits())
65+
if self.subsystem is None:
66+
subsystem_qubits = all_qubits
67+
else:
68+
subsystem_qubits = [all_qubits[s] for s in self.subsystem]
5669
if rng is None:
5770
rng = np.random.default_rng()
5871

59-
qubits = sorted(circuit.all_qubits())
60-
num_qubits = len(qubits)
61-
62-
pre_measurement_unitaries_list = self._generate_unitaries_list(rng, num_qubits)
63-
pre_measurement_moment = self.unitaries_to_moment(pre_measurement_unitaries_list, qubits)
72+
pre_measurement_moment = self.random_single_qubit_unitary_moment(
73+
unitary_ensemble, subsystem_qubits, rng
74+
)
6475

6576
return cirq.Circuit.from_moments(
66-
*circuit.moments, pre_measurement_moment, cirq.M(*qubits, key='m')
77+
*circuit.moments, pre_measurement_moment, cirq.M(*subsystem_qubits, key="m")
6778
)
6879

69-
def _generate_unitaries_list(self, rng: np.random.Generator, num_qubits: int) -> Sequence[Any]:
70-
"""Generates a list of pre-measurement unitaries."""
71-
72-
pauli_strings = rng.choice(["X", "Y", "Z"], size=num_qubits)
73-
74-
if self.subsystem is not None:
75-
for i in range(pauli_strings.shape[0]):
76-
if i not in self.subsystem:
77-
pauli_strings[i] = np.array("Z")
78-
79-
return pauli_strings.tolist()
80-
81-
def unitaries_to_moment(
82-
self, unitaries: Sequence[Literal["X", "Y", "Z"]], qubits: Sequence[Any]
83-
) -> 'cirq.Moment':
80+
def random_single_qubit_unitary_moment(
81+
self, unitary_ensemble: str, qubits: Sequence[Any], rng: np.random.Generator
82+
) -> "cirq.Moment":
8483
"""Outputs the cirq moment associated with the pre-measurement rotations.
84+
8585
Args:
86-
unitaries: List of pre-measurement unitaries
86+
unitary_ensemble: clifford, pauli, cue
8787
qubits: List of qubits
88+
rng: Random number generator to be used in sampling.
8889
89-
Returns: The cirq moment associated with the pre-measurement rotations
90+
Returns:
91+
The cirq moment associated with the pre-measurement rotations
92+
93+
Raises:
94+
ValueError: When unitary_ensemble is not one of "cue", "pauli" or "clifford"
9095
"""
96+
97+
if unitary_ensemble.lower() == "pauli":
98+
unitaries = [_pauli_basis_rotation(rng) for _ in range(len(qubits))]
99+
100+
elif unitary_ensemble.lower() == "clifford":
101+
unitaries = [_single_qubit_clifford(rng) for _ in range(len(qubits))]
102+
103+
elif unitary_ensemble.lower() == "cue":
104+
unitaries = [_single_qubit_cue(rng) for _ in range(len(qubits))]
105+
106+
else:
107+
raise ValueError("Only pauli, clifford and cue unitaries are available")
108+
91109
op_list: list[cirq.Operation] = []
92-
for idx, pauli in enumerate(unitaries):
93-
op_list.append(_pauli_basis_rotation(pauli).on(qubits[idx]))
110+
111+
for idx, unitary in enumerate(unitaries):
112+
op_list.append(unitary.on(qubits[idx]))
94113

95114
return cirq.Moment.from_ops(*op_list)
96115

97116

98-
def _pauli_basis_rotation(basis: Literal["X", "Y", "Z"]) -> 'cirq.Gate':
99-
"""Given a measurement basis returns the associated rotation.
117+
def _pauli_basis_rotation(rng: np.random.Generator) -> "cirq.Gate":
118+
"""Randomly generate a Pauli basis rotation.
119+
100120
Args:
101-
basis: Measurement basis
102-
Returns: The cirq gate for associated with measurement basis
121+
rng: Random number generator
122+
123+
Returns:
124+
cirq gate
125+
"""
126+
basis_idx = rng.choice(np.arange(3))
127+
128+
if basis_idx == 0:
129+
gate: "cirq.Gate" = cirq.Ry(rads=-np.pi / 2)
130+
elif basis_idx == 1:
131+
gate = cirq.Rx(rads=np.pi / 2)
132+
else:
133+
gate = cirq.I
134+
return gate
135+
136+
137+
def _single_qubit_clifford(rng: np.random.Generator) -> "cirq.Gate":
138+
"""Randomly generate a single-qubit Clifford rotation.
139+
140+
Args:
141+
rng: Random number generator
142+
143+
Returns:
144+
cirq gate
145+
"""
146+
147+
# there are 24 distinct single-qubit Clifford gates
148+
clifford_idx = rng.choice(np.arange(24))
149+
150+
return SingleQubitCliffordGate.to_phased_xz_gate(
151+
SingleQubitCliffordGate.all_single_qubit_cliffords[clifford_idx]
152+
)
153+
154+
155+
def _single_qubit_cue(rng: np.random.Generator) -> "cirq.Gate":
156+
"""Randomly generate a CUE gate.
157+
158+
Args:
159+
rng: Random number generator
160+
161+
Returns:
162+
cirq gate
103163
"""
104-
if basis == "X":
105-
return cirq.Ry(rads=-np.pi / 2)
106-
elif basis == "Y":
107-
return cirq.Rx(rads=np.pi / 2)
108-
elif basis == "Z":
109-
return cirq.I
164+
165+
# phasedxz parameters are distinct between -1 and +1
166+
x_exponent, z_exponent, axis_phase_exponent = 1 - 2 * rng.random(size=3)
167+
168+
return cirq.PhasedXZGate(
169+
x_exponent=x_exponent, z_exponent=z_exponent, axis_phase_exponent=axis_phase_exponent
170+
)

cirq-core/cirq/transformers/randomized_measurements_test.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,23 @@
1414

1515
import cirq
1616
import cirq.transformers.randomized_measurements as rand_meas
17+
import pytest
1718

1819

1920
def test_randomized_measurements_appends_two_moments_on_returned_circuit():
2021
# Create a 4-qubit circuit
2122
q0, q1, q2, q3 = cirq.LineQubit.range(4)
22-
circuit = cirq.Circuit([cirq.H(q0), cirq.CNOT(q0, q1), cirq.CNOT(q1, q2), cirq.CNOT(q2, q3)])
23-
num_moments_pre = len(circuit.moments)
23+
circuit_pre = cirq.Circuit(
24+
[cirq.H(q0), cirq.CNOT(q0, q1), cirq.CNOT(q1, q2), cirq.CNOT(q2, q3)]
25+
)
26+
num_moments_pre = len(circuit_pre.moments)
2427

2528
# Append randomized measurements to subsystem
26-
circuit = rand_meas.RandomizedMeasurements()(circuit)
27-
28-
num_moments_post = len(circuit.moments)
29-
assert num_moments_post == num_moments_pre + 2
29+
unitary_ensembles = ['pauli', 'clifford', 'cue']
30+
for u in unitary_ensembles:
31+
circuit_post = rand_meas.RandomizedMeasurements()(circuit_pre, unitary_ensemble=u)
32+
num_moments_post = len(circuit_post.moments)
33+
assert num_moments_post == num_moments_pre + 2
3034

3135

3236
def test_append_randomized_measurements_leaves_qubits_not_in_specified_subsystem_unchanged():
@@ -36,10 +40,9 @@ def test_append_randomized_measurements_leaves_qubits_not_in_specified_subsystem
3640

3741
# Append randomized measurements to subsystem
3842
circuit = rand_meas.RandomizedMeasurements(subsystem=(0, 1))(circuit)
39-
4043
# assert latter subsystems were not changed.
41-
assert circuit.operation_at(q2, 4) == cirq.I(q2)
42-
assert circuit.operation_at(q3, 4) == cirq.I(q3)
44+
assert circuit.operation_at(q2, 4) is None
45+
assert circuit.operation_at(q3, 4) is None
4346

4447

4548
def test_append_randomized_measurements_leaves_qubits_not_in_noncontinuous_subsystem_unchanged():
@@ -51,5 +54,14 @@ def test_append_randomized_measurements_leaves_qubits_not_in_noncontinuous_subsy
5154
circuit = rand_meas.RandomizedMeasurements(subsystem=(0, 2))(circuit)
5255

5356
# assert latter subsystems were not changed.
54-
assert circuit.operation_at(q1, 4) == cirq.I(q1)
55-
assert circuit.operation_at(q3, 4) == cirq.I(q3)
57+
assert circuit.operation_at(q1, 4) is None
58+
assert circuit.operation_at(q3, 4) is None
59+
60+
61+
def test_exception():
62+
q0, q1, q2, q3 = cirq.LineQubit.range(4)
63+
circuit = cirq.Circuit([cirq.H(q0), cirq.CNOT(q0, q1), cirq.CNOT(q1, q2), cirq.CNOT(q2, q3)])
64+
65+
# Append randomized measurements to subsystem
66+
with pytest.raises(ValueError):
67+
rand_meas.RandomizedMeasurements()(circuit, unitary_ensemble="coe")

0 commit comments

Comments
 (0)