Skip to content

Commit

Permalink
Add Jobs directory and depolarizing channel (#166)
Browse files Browse the repository at this point in the history
Adds a jobs directory for a Job object combining a Circuit and a ParameterSweep. Provides an initial implementation of a depolarizing channel simulator using this framework.
  • Loading branch information
dstrain115 authored and Strilanc committed Mar 2, 2018
1 parent 93db9eb commit 35af7f2
Show file tree
Hide file tree
Showing 7 changed files with 421 additions and 3 deletions.
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ information on using pull requests.
Our code reviews also (currently) require the reviewer to run tests for
your pull request. To insure that these tests pass, you should run
these tests locally. To do this, you must first install the protobuf
compiler:
compiler and the virtual environment:
```bash
sudo apt-get install protobuf-compiler
sudo apt-get install protobuf-compiler virtualenv
```
Then you can run the following, which assumes you are in the directory
where your changes are made:
```bash
./continuous_integration/test-pull-request
./continuous_integration/test-pull-request.sh
```
Reviewers will run these tests before your code is submitted to ensure
that the tests are not broken. This ad hoc system is in place until
Expand Down
21 changes: 21 additions & 0 deletions cirq/contrib/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2018 Google LLC
#
# 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.

"""Package for contributions.
Any contributions not ready for full production can be put in a subdirectory in
this package.
"""

from cirq.contrib import jobs
27 changes: 27 additions & 0 deletions cirq/contrib/jobs/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copyright 2018 Google LLC
#
# 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.

"""Package for handling a full quantum job.
Types and methods related to transforming circuits in preparation of sending
them to Quantum Engine. Contains classes to help with adding parameter
sweeps and error simulation.
"""

from cirq.contrib.jobs.job import (
Job,
)
from cirq.contrib.jobs.depolarizer_channel import (
DepolarizerChannel,
)
165 changes: 165 additions & 0 deletions cirq/contrib/jobs/depolarizer_channel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# Copyright 2017 Google LLC
#
# 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.

"""Error simulator that adds randomly activated error gates after every moment.
"""
import random
from cirq.api.google.v1.params_pb2 import ParameterSweep
from cirq.api.google.v1.params_pb2 import SingleParameterSweep
from cirq.circuits.circuit import Circuit
from cirq.circuits.circuit import Moment
from cirq.contrib.jobs import Job
from cirq.google import xmon_gates
from cirq.study.parameterized_value import ParameterizedValue


class DepolarizerChannel(object):
"""Depolarizing Channel Transformer for error simulation.
This class simulates errors in a quantum circuit by randomly
introducing error gates in between each moment. This simulates
noise by decohering random qubits at each step. This transform
is intended only for classical simulations only.
This class currently only supports adding a Pauli-Z gate at
each step.
Attributes:
probability: Probability of a qubit being affected in a given moment
repetitions: Number of simulations to create.
"""

# Prefix for ParameterizedValues related to error simulation
_parameter_name = 'error_parameter'

def __init__(self, probability=0.001, repetitions=1):
self.p = probability
self.repetitions = repetitions

def transform_job(self, job):
"""Creates a new job object with depolarizing channel.
This job will contain the existing Job's circuit with an error gate per
qubit at every moment. Creates the parameter sweep for each gate and
populates with random values as per the specifications of the
depolarizer channel.
Does not yet support augmenting jobs that already have parameter sweeps.
Args:
job: Job object to transform
Returns:
A new Job object that contains a circuit with up to double the
moments as the original job, with every other moment being a
moment containing error gates. It will also contain a
ParameterSweep containing values for each error gate. Note that
moments that contain no error gates for any repetition will be
automatically omitted.
"""
# A set for quick lookup of pre-existing qubits
qubit_set = set()
# A list with deterministic qubit order
qubit_list = []
circuit = job.circuit

# Retrieve the set of qubits used in the circuit
for moment in circuit.moments:
for op in moment.operations:
for qubit in op.qubits:
if qubit not in qubit_set:
qubit_set.add(qubit)
qubit_list.append(qubit)

# Add error circuits
moments = []
parameter_number = 0

sweep = ParameterSweep()
sweep.repetitions = 1
sweep.sweep.factors.add()
add_gate = False
for moment in circuit.moments:
moments.append(moment)

points = {}
gate_needed = {}
for q in qubit_list:
points[q] = []
for _ in range(self.repetitions):
if random.random() < self.p:
add_gate = True
gate_needed[q] = True
points[q].append(1.0)
else:
points[q].append(0.0)

if add_gate:
moments.append(self._error_moment(parameter_number,
qubit_list, gate_needed))

for q in qubit_list:
if gate_needed[q]:
sps = self._single_parameter_sweep(parameter_number,
points[q])
sweep.sweep.factors[0].sweeps.extend([sps])
parameter_number += 1

if add_gate:
new_job = Job(Circuit(moments), sweep)
else:
new_job = Job(Circuit(moments))

return new_job

def _error_moment(self, current_parameter_number, qubit_list, gate_needed):
"""Creates a moment of error gates.
Args:
current_parameter_number: The current number of error parameters
already existing. Used for naming purposes.
qubit_list: List of qubits to add error gates for.
gate_needed: dictionary of qubits. Value is true if an error
gate should be added for the qubit.
Returns:
a moment that includes parameterized errors gates. The
parameters will be named with monotonically increasing error
parameterized values.
"""
error_gates = []
for q in qubit_list:
if gate_needed[q]:
new_key = self._parameter_name + str(current_parameter_number)
current_parameter_number += 1
new_parameter = ParameterizedValue(key=new_key)
error_gates.append(xmon_gates.ExpZGate(
half_turns=new_parameter).on(q))
return Moment(error_gates)

def _single_parameter_sweep(self, current_parameter_number, point_list):
"""Creates a single parameter sweep.
Args:
current_parameter_number: The current number of error parameters
already existing. Used for naming parameterized values.
point_list: list of floats to convert to SweepPoints
Returns:
a SingleParameterSweep for a given qubit based on randomized results.
"""
key = self._parameter_name + str(current_parameter_number)
sps = SingleParameterSweep()
sps.parameter_name = key
for point in point_list:
sps.sweep_points.points.append(point)
return sps
92 changes: 92 additions & 0 deletions cirq/contrib/jobs/depolarizer_channel_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Copyright 2017 Google LLC
#
# 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 circuits
from cirq import ops
from cirq.api.google.v1.params_pb2 import ParameterSweep
from cirq.contrib.jobs import DepolarizerChannel
from cirq.contrib.jobs import Job
from cirq.google import xmon_gates
from cirq.study.parameterized_value import ParameterizedValue


def test_depolarizer_no_errors():
q1 = ops.QubitId()
q2 = ops.QubitId()
cnot = Job(circuits.Circuit([
circuits.Moment([ops.CNOT(q1, q2)]),
]))
noerrors = DepolarizerChannel(probability=0.0)

assert noerrors.transform_job(cnot) == cnot


def test_depolarizer_all_errors():
q1 = ops.QubitId()
q2 = ops.QubitId()
cnot = Job(circuits.Circuit([
circuits.Moment([ops.CNOT(q1, q2)]),
]))
allerrors = DepolarizerChannel(probability=1.0)
p0 = ParameterizedValue(DepolarizerChannel._parameter_name + '0')
p1 = ParameterizedValue(DepolarizerChannel._parameter_name + '1')

sweep = ParameterSweep()
sweep.repetitions = 1
sweep.sweep.factors.add()
sweep.sweep.factors[0].sweeps.add()
sweep.sweep.factors[0].sweeps.add()
sweep.sweep.factors[0].sweeps[0].parameter_name = p0.key
sweep.sweep.factors[0].sweeps[0].sweep_points.points.append(1.0)
sweep.sweep.factors[0].sweeps[1].parameter_name = p1.key
sweep.sweep.factors[0].sweeps[1].sweep_points.points.append(1.0)

cnot_then_z = Job(circuits.Circuit([
circuits.Moment([ops.CNOT(q1, q2)]),
circuits.Moment([xmon_gates.ExpZGate(half_turns=p0).on(q1),
xmon_gates.ExpZGate(half_turns=p1).on(q2)])]),
sweep)

assert allerrors.transform_job(cnot) == cnot_then_z


def test_depolarizer_multiple_repetitions():
q1 = ops.QubitId()
q2 = ops.QubitId()
cnot = Job(circuits.Circuit([
circuits.Moment([ops.CNOT(q1, q2)]),
]))
allerrors3 = DepolarizerChannel(probability=1.0, repetitions=3)
p0 = ParameterizedValue(DepolarizerChannel._parameter_name + '0')
p1 = ParameterizedValue(DepolarizerChannel._parameter_name + '1')

sweep = ParameterSweep()
sweep.repetitions = 1
sweep.sweep.factors.add()
sweep.sweep.factors[0].sweeps.add()
sweep.sweep.factors[0].sweeps.add()
sweep.sweep.factors[0].sweeps[0].parameter_name = p0.key
sweep.sweep.factors[0].sweeps[0].sweep_points.points.append(1.0)
sweep.sweep.factors[0].sweeps[1].parameter_name = p1.key
sweep.sweep.factors[0].sweeps[1].sweep_points.points.append(1.0)
sweep.sweep.factors[0].sweeps[0].sweep_points.points.append(1.0)
sweep.sweep.factors[0].sweeps[0].sweep_points.points.append(1.0)
sweep.sweep.factors[0].sweeps[1].sweep_points.points.append(1.0)
sweep.sweep.factors[0].sweeps[1].sweep_points.points.append(1.0)
cnot_then_z3 = Job(circuits.Circuit([
circuits.Moment([ops.CNOT(q1, q2)]),
circuits.Moment([xmon_gates.ExpZGate(half_turns=p0).on(q1),
xmon_gates.ExpZGate(half_turns=p1).on(q2)])]),
sweep)
assert allerrors3.transform_job(cnot) == cnot_then_z3
50 changes: 50 additions & 0 deletions cirq/contrib/jobs/job.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Copyright 2018 Google LLC
#
# 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.

"""The data structure representing a quantum job.
Job data contains, at minimum, contain a circuit and a parameter sweep of any
parameters contained in the circuit.
"""

from cirq.api.google.v1.params_pb2 import ParameterSweep
from cirq.circuits import Circuit


class Job(object):
"""A circuit coupled with any parameter sweeps and meta-data.
This class should contain all information needed to submit to Quantum
Engine.
"""

def __init__(self, circuit=Circuit(), sweep=ParameterSweep()):
self.circuit = circuit
self.sweep = sweep

def __eq__(self, other):
if not isinstance(other, type(self)):
return NotImplemented
return self.circuit == other.circuit and self.sweep == other.sweep

def __ne__(self, other):
return not self == other

__hash__ = None

def __repr__(self):
return "Job(%s,%s)" % (self.circuit, self.sweep)

def __str__(self):
return "Job(%s,%s)" % (self.circuit, self.sweep)
Loading

0 comments on commit 35af7f2

Please sign in to comment.