Skip to content

Commit

Permalink
Add cirq.StabilizerSampler (#3355)
Browse files Browse the repository at this point in the history
- 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)
  • Loading branch information
Strilanc committed Sep 29, 2020
1 parent b65e0b4 commit e852d58
Show file tree
Hide file tree
Showing 11 changed files with 159 additions and 3 deletions.
1 change: 1 addition & 0 deletions cirq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@
SimulationTrialResult,
Simulator,
SparseSimulatorStep,
StabilizerSampler,
StateVectorMixin,
StateVectorSimulatorState,
StateVectorStepResult,
Expand Down
10 changes: 10 additions & 0 deletions cirq/ops/common_channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,16 @@ def __str__(self) -> str:
return f"depolarize(p={self._p})"
return f"depolarize(p={self._p},n_qubits={self._n_qubits})"

def _act_on_(self, args: Any) -> bool:
from cirq.sim import clifford
if isinstance(args, clifford.ActOnCliffordTableauArgs):
if args.prng.random() < self._p:
gate = args.prng.choice(
[pauli_gates.X, pauli_gates.Y, pauli_gates.Z])
protocols.act_on(gate, args)
return True
return NotImplemented

def _circuit_diagram_info_(self,
args: 'protocols.CircuitDiagramInfoArgs') -> str:
if args.precision is not None:
Expand Down
12 changes: 11 additions & 1 deletion cirq/ops/common_channels_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,6 @@ def test_bit_flip_channel_invalid_probability():
cirq.bit_flip(1.1)



def test_bit_flip_channel_text_diagram():
bf = cirq.bit_flip(0.1234567)
assert (cirq.circuit_diagram_info(
Expand All @@ -582,6 +581,17 @@ def test_bit_flip_channel_text_diagram():
wire_symbols=('BF(0.12)',)))


def test_stabilizer_supports_depolarize():
with pytest.raises(TypeError, match="act_on"):
for _ in range(100):
cirq.act_on(cirq.depolarize(3 / 4), object())

q = cirq.LineQubit(0)
c = cirq.Circuit(cirq.depolarize(3 / 4).on(q), cirq.measure(q, key='m'))
m = np.sum(cirq.StabilizerSampler().sample(c, repetitions=100)['m'])
assert 5 < m < 95


def test_default_asymmetric_depolarizing_channel():
d = cirq.asymmetric_depolarize()
assert d.p_x == 0.0
Expand Down
13 changes: 13 additions & 0 deletions cirq/ops/random_gate_channel.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,19 @@ def _trace_distance_bound_(self) -> float:
result *= float(self.probability)
return result

def _act_on_(self, args):
from cirq.sim import clifford
if self._is_parameterized_():
return NotImplemented
if isinstance(args, clifford.ActOnCliffordTableauArgs):
if args.prng.random() < self.probability:
# Note: because we're doing this probabilistically, it's not
# safe to fallback to other strategies if act_on fails. Those
# strategies could double-count the probability.
protocols.act_on(self.sub_gate, args)
return True
return NotImplemented

def _json_dict_(self) -> Dict[str, Any]:
return protocols.obj_to_dict_helper(self, ['sub_gate', 'probability'])

Expand Down
21 changes: 21 additions & 0 deletions cirq/ops/random_gate_channel_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,24 @@ def test_trace_distance():

def test_str():
assert str(cirq.X.with_probability(0.5)) == 'X[prob=0.5]'


def test_stabilizer_supports_probability():
q = cirq.LineQubit(0)
c = cirq.Circuit(cirq.X(q).with_probability(0.5), cirq.measure(q, key='m'))
m = np.sum(cirq.StabilizerSampler().sample(c, repetitions=100)['m'])
assert 5 < m < 95


def test_unsupported_stabilizer_safety():
with pytest.raises(TypeError, match="act_on"):
for _ in range(100):
cirq.act_on(cirq.X.with_probability(0.5), object())
with pytest.raises(TypeError, match="act_on"):
cirq.act_on(cirq.X.with_probability(sympy.Symbol('x')), object())

q = cirq.LineQubit(0)
c = cirq.Circuit((cirq.X(q)**0.25).with_probability(0.5),
cirq.measure(q, key='m'))
with pytest.raises(TypeError, match='Failed to act'):
cirq.StabilizerSampler().sample(c, repetitions=100)
5 changes: 3 additions & 2 deletions cirq/protocols/json_serialization_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,11 +197,14 @@ def test_fail_to_resolve():

# utility:
'AnnealSequenceSearchStrategy',
'CliffordSimulator',
'DeserializingArg',
'GateOpDeserializer',
'GateOpSerializer',
'GreedySequenceSearchStrategy',
'SerializingArg',
'Simulator',
'StabilizerSampler',
'Unique',
'DEFAULT_RESOLVERS',

Expand Down Expand Up @@ -278,7 +281,6 @@ def test_mutually_exclusive_blacklist():
'CircuitDiagramInfo',
'CircuitDiagramInfoArgs',
'CircuitSampleJob',
'CliffordSimulator',
'CliffordSimulatorStepResult',
'CliffordState',
'CliffordTrialResult',
Expand Down Expand Up @@ -317,7 +319,6 @@ def test_mutually_exclusive_blacklist():
'SerializableDevice',
'SerializableGateSet',
'SimulationTrialResult',
'Simulator',
'SingleQubitCliffordGate',
'SparseSimulatorStep',
'SQRT_ISWAP_GATESET',
Expand Down
1 change: 1 addition & 0 deletions cirq/sim/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
from cirq.sim.clifford import (
ActOnCliffordTableauArgs,
ActOnStabilizerCHFormArgs,
StabilizerSampler,
StabilizerStateChForm,
CliffordSimulator,
CliffordState,
Expand Down
3 changes: 3 additions & 0 deletions cirq/sim/clifford/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,8 @@
from cirq.sim.clifford.act_on_stabilizer_ch_form_args import (
ActOnStabilizerCHFormArgs,)

from cirq.sim.clifford.stabilizer_sampler import (
StabilizerSampler,)

from cirq.sim.clifford.stabilizer_state_ch_form import (
StabilizerStateChForm,)
77 changes: 77 additions & 0 deletions cirq/sim/clifford/stabilizer_sampler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Copyright 2020 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

import numpy as np

import cirq
from cirq import circuits, protocols, value
from cirq.sim.clifford.act_on_clifford_tableau_args import \
ActOnCliffordTableauArgs
from cirq.sim.clifford.clifford_tableau import CliffordTableau
from cirq.work import sampler


class StabilizerSampler(sampler.Sampler):
"""An efficient sampler for stabilizer circuits."""

def __init__(self, *, seed: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None):
"""
Args:
seed: The random seed or generator to use when sampling.
"""
self.init = True
self._prng = value.parse_random_state(seed)

def run_sweep(
self,
program: 'cirq.Circuit',
params: 'cirq.Sweepable',
repetitions: int = 1,
) -> List['cirq.Result']:
results: List[cirq.Result] = []
for param_resolver in cirq.to_resolvers(params):
resolved_circuit = cirq.resolve_parameters(program, param_resolver)
measurements = self._run(
resolved_circuit,
repetitions=repetitions,
)
results.append(
cirq.Result(params=param_resolver, measurements=measurements))
return results

def _run(self, circuit: circuits.Circuit,
repetitions: int) -> Dict[str, np.ndarray]:

measurements: Dict[str, List[int]] = {
key: [] for key in protocols.measurement_keys(circuit)
}
axes_map = {q: i for i, q in enumerate(circuit.all_qubits())}

for _ in range(repetitions):
state = ActOnCliffordTableauArgs(
CliffordTableau(num_qubits=len(axes_map)),
axes=(),
prng=self._prng,
log_of_measurement_results={},
)
for op in circuit.all_operations():
state.axes = tuple(axes_map[q] for q in op.qubits)
protocols.act_on(op, state)

for k, v in state.log_of_measurement_results.items():
measurements[k].append(v)

return {k: np.array(v) for k, v in measurements.items()}
17 changes: 17 additions & 0 deletions cirq/sim/clifford/stabilizer_sampler_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import cirq

import numpy as np


def test_produces_samples():
a, b = cirq.LineQubit.range(2)
c = cirq.Circuit(
cirq.H(a),
cirq.CNOT(a, b),
cirq.measure(a, key='a'),
cirq.measure(b, key='b'),
)

result = cirq.StabilizerSampler().sample(c, repetitions=100)
assert 5 < sum(result['a']) < 95
assert np.all(result['a'] ^ result['b'] == 0)
2 changes: 2 additions & 0 deletions rtd_docs/api.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

API Reference
=============

Expand Down Expand Up @@ -280,6 +281,7 @@ results.
cirq.SimulationTrialResult
cirq.Simulator
cirq.SparseSimulatorStep
cirq.StabilizerSampler
cirq.StateVectorMixin
cirq.StateVectorSimulatorState
cirq.StateVectorStepResult
Expand Down

0 comments on commit e852d58

Please sign in to comment.