Skip to content

Commit e852d58

Browse files
authored
Add cirq.StabilizerSampler (#3355)
- Minimal interface - Supports some random operations (depolarize, probabilistic gates) - This is a potential replacement for cirq.CliffordSimulator (which is in a bad state right now, tracking two redundant state objects in a fashion observable to callers)
1 parent b65e0b4 commit e852d58

File tree

11 files changed

+159
-3
lines changed

11 files changed

+159
-3
lines changed

cirq/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@
365365
SimulationTrialResult,
366366
Simulator,
367367
SparseSimulatorStep,
368+
StabilizerSampler,
368369
StateVectorMixin,
369370
StateVectorSimulatorState,
370371
StateVectorStepResult,

cirq/ops/common_channels.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,16 @@ def __str__(self) -> str:
308308
return f"depolarize(p={self._p})"
309309
return f"depolarize(p={self._p},n_qubits={self._n_qubits})"
310310

311+
def _act_on_(self, args: Any) -> bool:
312+
from cirq.sim import clifford
313+
if isinstance(args, clifford.ActOnCliffordTableauArgs):
314+
if args.prng.random() < self._p:
315+
gate = args.prng.choice(
316+
[pauli_gates.X, pauli_gates.Y, pauli_gates.Z])
317+
protocols.act_on(gate, args)
318+
return True
319+
return NotImplemented
320+
311321
def _circuit_diagram_info_(self,
312322
args: 'protocols.CircuitDiagramInfoArgs') -> str:
313323
if args.precision is not None:

cirq/ops/common_channels_test.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,6 @@ def test_bit_flip_channel_invalid_probability():
571571
cirq.bit_flip(1.1)
572572

573573

574-
575574
def test_bit_flip_channel_text_diagram():
576575
bf = cirq.bit_flip(0.1234567)
577576
assert (cirq.circuit_diagram_info(
@@ -582,6 +581,17 @@ def test_bit_flip_channel_text_diagram():
582581
wire_symbols=('BF(0.12)',)))
583582

584583

584+
def test_stabilizer_supports_depolarize():
585+
with pytest.raises(TypeError, match="act_on"):
586+
for _ in range(100):
587+
cirq.act_on(cirq.depolarize(3 / 4), object())
588+
589+
q = cirq.LineQubit(0)
590+
c = cirq.Circuit(cirq.depolarize(3 / 4).on(q), cirq.measure(q, key='m'))
591+
m = np.sum(cirq.StabilizerSampler().sample(c, repetitions=100)['m'])
592+
assert 5 < m < 95
593+
594+
585595
def test_default_asymmetric_depolarizing_channel():
586596
d = cirq.asymmetric_depolarize()
587597
assert d.p_x == 0.0

cirq/ops/random_gate_channel.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,19 @@ def _trace_distance_bound_(self) -> float:
109109
result *= float(self.probability)
110110
return result
111111

112+
def _act_on_(self, args):
113+
from cirq.sim import clifford
114+
if self._is_parameterized_():
115+
return NotImplemented
116+
if isinstance(args, clifford.ActOnCliffordTableauArgs):
117+
if args.prng.random() < self.probability:
118+
# Note: because we're doing this probabilistically, it's not
119+
# safe to fallback to other strategies if act_on fails. Those
120+
# strategies could double-count the probability.
121+
protocols.act_on(self.sub_gate, args)
122+
return True
123+
return NotImplemented
124+
112125
def _json_dict_(self) -> Dict[str, Any]:
113126
return protocols.obj_to_dict_helper(self, ['sub_gate', 'probability'])
114127

cirq/ops/random_gate_channel_test.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,3 +240,24 @@ def test_trace_distance():
240240

241241
def test_str():
242242
assert str(cirq.X.with_probability(0.5)) == 'X[prob=0.5]'
243+
244+
245+
def test_stabilizer_supports_probability():
246+
q = cirq.LineQubit(0)
247+
c = cirq.Circuit(cirq.X(q).with_probability(0.5), cirq.measure(q, key='m'))
248+
m = np.sum(cirq.StabilizerSampler().sample(c, repetitions=100)['m'])
249+
assert 5 < m < 95
250+
251+
252+
def test_unsupported_stabilizer_safety():
253+
with pytest.raises(TypeError, match="act_on"):
254+
for _ in range(100):
255+
cirq.act_on(cirq.X.with_probability(0.5), object())
256+
with pytest.raises(TypeError, match="act_on"):
257+
cirq.act_on(cirq.X.with_probability(sympy.Symbol('x')), object())
258+
259+
q = cirq.LineQubit(0)
260+
c = cirq.Circuit((cirq.X(q)**0.25).with_probability(0.5),
261+
cirq.measure(q, key='m'))
262+
with pytest.raises(TypeError, match='Failed to act'):
263+
cirq.StabilizerSampler().sample(c, repetitions=100)

cirq/protocols/json_serialization_test.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,11 +197,14 @@ def test_fail_to_resolve():
197197

198198
# utility:
199199
'AnnealSequenceSearchStrategy',
200+
'CliffordSimulator',
200201
'DeserializingArg',
201202
'GateOpDeserializer',
202203
'GateOpSerializer',
203204
'GreedySequenceSearchStrategy',
204205
'SerializingArg',
206+
'Simulator',
207+
'StabilizerSampler',
205208
'Unique',
206209
'DEFAULT_RESOLVERS',
207210

@@ -278,7 +281,6 @@ def test_mutually_exclusive_blacklist():
278281
'CircuitDiagramInfo',
279282
'CircuitDiagramInfoArgs',
280283
'CircuitSampleJob',
281-
'CliffordSimulator',
282284
'CliffordSimulatorStepResult',
283285
'CliffordState',
284286
'CliffordTrialResult',
@@ -317,7 +319,6 @@ def test_mutually_exclusive_blacklist():
317319
'SerializableDevice',
318320
'SerializableGateSet',
319321
'SimulationTrialResult',
320-
'Simulator',
321322
'SingleQubitCliffordGate',
322323
'SparseSimulatorStep',
323324
'SQRT_ISWAP_GATESET',

cirq/sim/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
from cirq.sim.clifford import (
8080
ActOnCliffordTableauArgs,
8181
ActOnStabilizerCHFormArgs,
82+
StabilizerSampler,
8283
StabilizerStateChForm,
8384
CliffordSimulator,
8485
CliffordState,

cirq/sim/clifford/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,8 @@
1414
from cirq.sim.clifford.act_on_stabilizer_ch_form_args import (
1515
ActOnStabilizerCHFormArgs,)
1616

17+
from cirq.sim.clifford.stabilizer_sampler import (
18+
StabilizerSampler,)
19+
1720
from cirq.sim.clifford.stabilizer_state_ch_form import (
1821
StabilizerStateChForm,)
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Copyright 2020 The Cirq Developers
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from typing import Dict, List
16+
17+
import numpy as np
18+
19+
import cirq
20+
from cirq import circuits, protocols, value
21+
from cirq.sim.clifford.act_on_clifford_tableau_args import \
22+
ActOnCliffordTableauArgs
23+
from cirq.sim.clifford.clifford_tableau import CliffordTableau
24+
from cirq.work import sampler
25+
26+
27+
class StabilizerSampler(sampler.Sampler):
28+
"""An efficient sampler for stabilizer circuits."""
29+
30+
def __init__(self, *, seed: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None):
31+
"""
32+
Args:
33+
seed: The random seed or generator to use when sampling.
34+
"""
35+
self.init = True
36+
self._prng = value.parse_random_state(seed)
37+
38+
def run_sweep(
39+
self,
40+
program: 'cirq.Circuit',
41+
params: 'cirq.Sweepable',
42+
repetitions: int = 1,
43+
) -> List['cirq.Result']:
44+
results: List[cirq.Result] = []
45+
for param_resolver in cirq.to_resolvers(params):
46+
resolved_circuit = cirq.resolve_parameters(program, param_resolver)
47+
measurements = self._run(
48+
resolved_circuit,
49+
repetitions=repetitions,
50+
)
51+
results.append(
52+
cirq.Result(params=param_resolver, measurements=measurements))
53+
return results
54+
55+
def _run(self, circuit: circuits.Circuit,
56+
repetitions: int) -> Dict[str, np.ndarray]:
57+
58+
measurements: Dict[str, List[int]] = {
59+
key: [] for key in protocols.measurement_keys(circuit)
60+
}
61+
axes_map = {q: i for i, q in enumerate(circuit.all_qubits())}
62+
63+
for _ in range(repetitions):
64+
state = ActOnCliffordTableauArgs(
65+
CliffordTableau(num_qubits=len(axes_map)),
66+
axes=(),
67+
prng=self._prng,
68+
log_of_measurement_results={},
69+
)
70+
for op in circuit.all_operations():
71+
state.axes = tuple(axes_map[q] for q in op.qubits)
72+
protocols.act_on(op, state)
73+
74+
for k, v in state.log_of_measurement_results.items():
75+
measurements[k].append(v)
76+
77+
return {k: np.array(v) for k, v in measurements.items()}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import cirq
2+
3+
import numpy as np
4+
5+
6+
def test_produces_samples():
7+
a, b = cirq.LineQubit.range(2)
8+
c = cirq.Circuit(
9+
cirq.H(a),
10+
cirq.CNOT(a, b),
11+
cirq.measure(a, key='a'),
12+
cirq.measure(b, key='b'),
13+
)
14+
15+
result = cirq.StabilizerSampler().sample(c, repetitions=100)
16+
assert 5 < sum(result['a']) < 95
17+
assert np.all(result['a'] ^ result['b'] == 0)

0 commit comments

Comments
 (0)