Skip to content

Commit

Permalink
Remove use of .device in cirq-google. (quantumlib#4820)
Browse files Browse the repository at this point in the history
Next step in quantumlib#4744 .  Removes use of device attachment ability in cirq-google. This is a breaking change in the sense that some methods become "more forgiving" which I've highlighted below. Happy to add the tag if people think it's appropriate to label it.
  • Loading branch information
MichaelBroughton committed Jan 22, 2022
1 parent 4c978f1 commit 7829d67
Show file tree
Hide file tree
Showing 14 changed files with 117 additions and 106 deletions.
2 changes: 2 additions & 0 deletions cirq-core/cirq/circuits/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@
CIRCUIT_TYPE = TypeVar('CIRCUIT_TYPE', bound='AbstractCircuit')
INT_TYPE = Union[int, np.integer]

_DEVICE_DEP_MESSAGE = 'Attaching devices to circuits will no longer be supported.'


class Alignment(enum.Enum):
# Stop when left ends are lined up.
Expand Down
24 changes: 17 additions & 7 deletions cirq-google/cirq_google/api/v1/programs.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from typing import Any, cast, Dict, Iterable, Optional, Sequence, Tuple, TYPE_CHECKING, Iterator
from typing import Any, cast, Dict, Optional, Sequence, Tuple, TYPE_CHECKING, Iterator
import numpy as np
import sympy

Expand Down Expand Up @@ -166,16 +166,26 @@ def circuit_as_schedule_to_protos(circuit: cirq.Circuit) -> Iterator[operations_
yield op_proto


def circuit_from_schedule_from_protos(
device: 'cirq_google.XmonDevice',
ops: Iterable[operations_pb2.Operation],
) -> cirq.Circuit:
"""Convert protos into a Circuit for the given device."""
@cirq._compat.deprecated_parameter(
deadline='v0.15',
fix=cirq.circuits.circuit._DEVICE_DEP_MESSAGE,
parameter_desc='device',
match=lambda args, kwargs: 'device' in kwargs or len(args) > 1,
)
def circuit_from_schedule_from_protos(*args) -> cirq.Circuit:
"""Convert protos into a Circuit."""
if len(args) == 2:
device, ops = args[0], args[1]
else:
ops = args[0]
result = []
for op in ops:
xmon_op = xmon_op_from_proto(op)
result.append(xmon_op)
return cirq.Circuit(result, device=device)
ret = cirq.Circuit(result)
if len(args) == 2:
ret._device = device
return ret


def pack_results(measurements: Sequence[Tuple[str, np.ndarray]]) -> bytes:
Expand Down
20 changes: 17 additions & 3 deletions cirq-google/cirq_google/api/v1/programs_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
# 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.

import numpy as np
import pytest
import sympy
Expand All @@ -32,14 +31,29 @@ def test_protobuf_round_trip():
circuit = cirq.Circuit(
[cirq.X(q) ** 0.5 for q in device.qubits],
[cirq.CZ(q, q2) for q in [cirq.GridQubit(0, 0)] for q2 in device.neighbors_of(q)],
device=device,
)

protos = list(programs.circuit_as_schedule_to_protos(circuit))
s2 = programs.circuit_from_schedule_from_protos(device, protos)
s2 = programs.circuit_from_schedule_from_protos(protos)
assert s2 == circuit


def test_protobuf_round_trip_device_deprecated():
device = cg.Foxtail
circuit = cirq.Circuit(
[cirq.X(q) ** 0.5 for q in device.qubits],
[cirq.CZ(q, q2) for q in [cirq.GridQubit(0, 0)] for q2 in device.neighbors_of(q)],
)
circuit._device = device

protos = list(programs.circuit_as_schedule_to_protos(circuit))
with cirq.testing.assert_deprecated(
cirq.circuits.circuit._DEVICE_DEP_MESSAGE, deadline='v0.15'
):
s2 = programs.circuit_from_schedule_from_protos(device, protos)
assert s2 == circuit


def make_bytes(s: str) -> bytes:
"""Helper function to convert a string of digits into packed bytes.
Expand Down
1 change: 0 additions & 1 deletion cirq-google/cirq_google/engine/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,6 @@ def _serialize_program(
) -> any_pb2.Any:
if not isinstance(program, cirq.AbstractCircuit):
raise TypeError(f'Unrecognized program type: {type(program)}')
program.device.validate_circuit(program)

if self.context.proto_version == ProtoVersion.V2:
program = gate_set.serialize(program)
Expand Down
15 changes: 1 addition & 14 deletions cirq-google/cirq_google/engine/engine_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,21 +392,8 @@ def test_run_circuit(client):
client.get_job_result.called_once_with()


def test_circuit_device_validation_fails():
circuit = cirq.Circuit(device=cg.Foxtail)

# Purposefully create an invalid Circuit by fiddling with internal bits.
# This simulates a failure in the incremental checks.
circuit._moments.append(cirq.Moment([cirq.Z(cirq.NamedQubit("dorothy"))]))
engine = cg.Engine(project_id='project-id')
with pytest.raises(ValueError, match='Unsupported qubit type'):
engine.run_sweep(program=circuit, gate_set=cg.XMON)
with pytest.raises(ValueError, match='Unsupported qubit type'):
engine.create_program(circuit, gate_set=cg.XMON)


def test_no_gate_set():
circuit = cirq.Circuit(device=cg.Sycamore)
circuit = cirq.Circuit()
engine = cg.Engine(project_id='project-id')
with pytest.raises(ValueError, match='No gate set'):
engine.run(program=circuit)
Expand Down
11 changes: 9 additions & 2 deletions cirq-google/cirq_google/optimizers/optimize_for_sycamore.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ def _gate_product_tabulation_cached(
raise NotImplementedError(f"Two qubit gate tabulation not supported for {optimizer_type}")


@cirq._compat.deprecated_parameter(
deadline='v0.15',
fix=cirq.circuits.circuit._DEVICE_DEP_MESSAGE,
parameter_desc='new_device',
match=lambda args, kwargs: 'new_device' in kwargs,
)
def optimized_for_sycamore(
circuit: cirq.Circuit,
*,
Expand Down Expand Up @@ -161,8 +167,9 @@ def optimized_for_sycamore(
for optimizer in opts:
optimizer(copy)

return cirq.Circuit(
ret = cirq.Circuit(
(op.transform_qubits(qubit_map) for op in copy.all_operations()),
strategy=cirq.InsertStrategy.EARLIEST,
device=new_device or copy.device,
)
ret._device = new_device or copy._device
return ret
11 changes: 11 additions & 0 deletions cirq-google/cirq_google/optimizers/optimize_for_sycamore_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,14 @@ def test_one_q_matrix_gate():
assert cg.SYC_GATESET.is_supported_operation(op)
# single qubit gates shared between gatesets, so:
assert cg.SQRT_ISWAP_GATESET.is_supported_operation(op)


def test_assert_new_device_deprecated():
u = cirq.testing.random_special_unitary(2)
q = cirq.LineQubit(0)
circuit0 = cirq.Circuit(cirq.MatrixGate(u).on(q))
_ = cg.optimized_for_sycamore(circuit0, optimizer_type='sqrt_iswap')
with cirq.testing.assert_deprecated(
cirq.circuits.circuit._DEVICE_DEP_MESSAGE, deadline='v0.15'
):
_ = cg.optimized_for_sycamore(circuit0, optimizer_type='sqrt_iswap', new_device=cg.Foxtail)
18 changes: 10 additions & 8 deletions cirq-google/cirq_google/optimizers/optimize_for_xmon.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,19 @@
import cirq_google


@cirq._compat.deprecated_parameter(
deadline='v0.15',
fix=cirq.circuits.circuit._DEVICE_DEP_MESSAGE,
parameter_desc='new_device',
match=lambda args, kwargs: 'new_device' in kwargs,
)
def optimized_for_xmon(
circuit: cirq.Circuit,
new_device: Optional['cirq_google.XmonDevice'] = None,
qubit_map: Callable[[cirq.Qid], cirq.GridQubit] = lambda e: cast(cirq.GridQubit, e),
allow_partial_czs: bool = False,
) -> cirq.Circuit:
if allow_partial_czs:
return optimized_for_sycamore(
circuit, new_device=new_device, qubit_map=qubit_map, optimizer_type='xmon_partial_cz'
)
else:
return optimized_for_sycamore(
circuit, new_device=new_device, qubit_map=qubit_map, optimizer_type='xmon'
)
optimizer_type = 'xmon_partial_cz' if allow_partial_czs else 'xmon'
ret = optimized_for_sycamore(circuit, qubit_map=qubit_map, optimizer_type=optimizer_type)
ret._device = new_device or circuit._device
return ret
30 changes: 2 additions & 28 deletions cirq-google/cirq_google/optimizers/optimize_for_xmon_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
# 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.

import pytest

import cirq
Expand Down Expand Up @@ -57,38 +56,13 @@ def test_ccz():
assert_circuits_with_terminal_measurements_are_equivalent(before, after, atol=1e-4)


def test_adjacent_cz_get_split_apart():
before = cirq.Circuit(
[
cirq.Moment(
[
cirq.CZ(cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)),
cirq.CZ(cirq.GridQubit(1, 0), cirq.GridQubit(1, 1)),
]
)
]
)

after = cg.optimized_for_xmon(before, new_device=cg.Foxtail)

assert after == cirq.Circuit(
[
cirq.Moment([cirq.CZ(cirq.GridQubit(0, 0), cirq.GridQubit(0, 1))]),
cirq.Moment([cirq.CZ(cirq.GridQubit(1, 0), cirq.GridQubit(1, 1))]),
],
device=cg.Foxtail,
)


def test_remap_qubits():
before = cirq.Circuit([cirq.Moment([cirq.CZ(cirq.LineQubit(0), cirq.LineQubit(1))])])

after = cg.optimized_for_xmon(
before, new_device=cg.Foxtail, qubit_map=lambda q: cirq.GridQubit(q.x, 0)
)
after = cg.optimized_for_xmon(before, qubit_map=lambda q: cirq.GridQubit(q.x, 0))

assert after == cirq.Circuit(
[cirq.Moment([cirq.CZ(cirq.GridQubit(0, 0), cirq.GridQubit(1, 0))])], device=cg.Foxtail
[cirq.Moment([cirq.CZ(cirq.GridQubit(0, 0), cirq.GridQubit(1, 0))])]
)


Expand Down
33 changes: 31 additions & 2 deletions cirq-google/cirq_google/serialization/gate_sets_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
# 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.

import os
from typing import Dict
from unittest import mock
import numpy as np
import pytest
import sympy
Expand Down Expand Up @@ -355,7 +356,8 @@ def test_deserialize_circuit():
assert cg.XMON.deserialize(serialized) == circuit


def test_deserialize_schedule():
@mock.patch.dict(os.environ, clear='CIRQ_TESTING')
def test_deserialize_schedule_device_deprecated():
q0 = cirq.GridQubit(4, 4)
q1 = cirq.GridQubit(4, 5)
circuit = cirq.Circuit(
Expand Down Expand Up @@ -384,6 +386,33 @@ def test_deserialize_schedule():
assert cg.XMON.deserialize(serialized, cg.Bristlecone) == circuit


def test_deserialize_schedule():
q0 = cirq.GridQubit(4, 4)
q1 = cirq.GridQubit(4, 5)
circuit = cirq.Circuit(cirq.CZ(q0, q1), cirq.X(q0), cirq.Z(q1), cirq.measure(q0, key='a'))
serialized = v2.program_pb2.Program(
language=v2.program_pb2.Language(gate_set='xmon'),
schedule=v2.program_pb2.Schedule(
scheduled_operations=[
v2.program_pb2.ScheduledOperation(
operation=cg.XMON.serialize_op(cirq.CZ(q0, q1)), start_time_picos=0
),
v2.program_pb2.ScheduledOperation(
operation=cg.XMON.serialize_op(cirq.X(q0)), start_time_picos=200000
),
v2.program_pb2.ScheduledOperation(
operation=cg.XMON.serialize_op(cirq.Z(q1)), start_time_picos=200000
),
v2.program_pb2.ScheduledOperation(
operation=cg.XMON.serialize_op(cirq.measure(q0, key='a')),
start_time_picos=400000,
),
]
),
)
assert cg.XMON.deserialize(serialized) == circuit


def test_serialize_deserialize_syc():
proto = op_proto({'gate': {'id': 'syc'}, 'args': {}, 'qubits': [{'id': '1_2'}, {'id': '1_3'}]})

Expand Down
20 changes: 15 additions & 5 deletions cirq-google/cirq_google/serialization/serializable_gate_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,12 @@ def serialize_circuit_op(
return proto_msg
raise ValueError(f'Cannot serialize CircuitOperation {op!r}')

@cirq._compat.deprecated_parameter(
deadline='v0.15',
fix=cirq.circuits.circuit._DEVICE_DEP_MESSAGE,
parameter_desc='device',
match=lambda args, kwargs: 'device' in kwargs or len(args) > 2,
)
def deserialize(
self, proto: v2.program_pb2.Program, device: Optional[cirq.Device] = None
) -> cirq.Circuit:
Expand Down Expand Up @@ -322,10 +328,10 @@ def deserialize(
constants=proto.constants,
deserialized_constants=deserialized_constants,
)
return circuit if device is None else circuit.with_device(device)
if device is not None:
circuit._device = device # coverage: ignore
return circuit
if which == 'schedule':
if device is None:
raise ValueError('Deserializing schedule requires a device but None was given.')
return self._deserialize_schedule(
proto.schedule, device, arg_function_language=proto.language.arg_function_language
)
Expand Down Expand Up @@ -497,7 +503,7 @@ def _deserialize_circuit(
def _deserialize_schedule(
self,
schedule_proto: v2.program_pb2.Schedule,
device: cirq.Device,
device: Optional[cirq.Device],
*,
arg_function_language: str,
) -> cirq.Circuit:
Expand All @@ -510,4 +516,8 @@ def _deserialize_schedule(
scheduled_op_proto.operation, arg_function_language=arg_function_language
)
)
return cirq.Circuit(result, device=device)
ret = cirq.Circuit(result)
if device is None:
device = cirq.UNCONSTRAINED_DEVICE
ret._device = device
return ret
Original file line number Diff line number Diff line change
Expand Up @@ -387,29 +387,6 @@ def test_serialize_unrecognized():
MY_GATE_SET.serialize("not quite right")


def test_serialize_deserialize_schedule_no_device():
q0 = cirq.GridQubit(1, 1)
q1 = cirq.GridQubit(1, 2)
proto = v2.program_pb2.Program(
language=v2.program_pb2.Language(arg_function_language='', gate_set='my_gate_set'),
schedule=v2.program_pb2.Schedule(
scheduled_operations=[
v2.program_pb2.ScheduledOperation(
operation=X_SERIALIZER.to_proto(cirq.X(q0)), start_time_picos=0
),
v2.program_pb2.ScheduledOperation(
operation=X_SERIALIZER.to_proto(cirq.X(q1)), start_time_picos=200000
),
v2.program_pb2.ScheduledOperation(
operation=X_SERIALIZER.to_proto(cirq.X(q0)), start_time_picos=400000
),
]
),
)
with pytest.raises(ValueError):
MY_GATE_SET.deserialize(proto)


def test_serialize_deserialize_op():
q0 = cirq.GridQubit(1, 1)
proto = op_proto(
Expand Down Expand Up @@ -769,15 +746,6 @@ def test_deserialize_invalid_gate_set():
MY_GATE_SET.deserialize(proto)


def test_deserialize_schedule_missing_device():
proto = v2.program_pb2.Program(
language=v2.program_pb2.Language(gate_set='my_gate_set'),
schedule=v2.program_pb2.Schedule(scheduled_operations=[]),
)
with pytest.raises(ValueError, match='device'):
MY_GATE_SET.deserialize(proto)


def test_deserialize_no_operation():
proto = v2.program_pb2.Program(
language=v2.program_pb2.Language(gate_set='my_gate_set'),
Expand Down
2 changes: 1 addition & 1 deletion examples/advanced/quantum_volume.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def main(*, num_qubits: int, depth: int, num_circuits: int, seed: int, routes: i
Returns: Pass-through from calculate_quantum_volume.
"""
device = cirq_google.Bristlecone
compiler = lambda circuit: cirq_google.optimized_for_xmon(circuit=circuit, new_device=device)
compiler = lambda circuit: cirq_google.optimized_for_xmon(circuit=circuit)
noisy = cirq.DensityMatrixSimulator(
noise=cirq.ConstantQubitNoiseModel(qubit_noise_gate=cirq.DepolarizingChannel(p=0.005))
)
Expand Down
Loading

0 comments on commit 7829d67

Please sign in to comment.