Skip to content

Commit

Permalink
Add optional tabulation to ConvertToSycamoreGates (#2615)
Browse files Browse the repository at this point in the history
- Moves @dkafri's tabulation code from common to google/optimizers (not sure if that's OK or not - alternatively, I can make another optimizer that is called before ConvertToSycamoreGates).
- Add SwapPermutationGate to contrib JSON resolvers
- Quick fix to Quantum Volume:
  - After routing, save the qubit mapping and remove the SwapPermutationGates. This makes it so that optimizers don't need to know about or preserve SwapPermutationGates, which is a routing implementation detail.

@mpharrigan This should give us much better Quantum Volume numbers.
  • Loading branch information
KevinVillela authored and CirqBot committed Dec 4, 2019
1 parent 18a677d commit 2b88d34
Show file tree
Hide file tree
Showing 13 changed files with 291 additions and 124 deletions.
2 changes: 2 additions & 0 deletions cirq/contrib/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
def contrib_class_resolver(cirq_type: str):
"""Extend cirq's JSON API with resolvers for cirq contrib classes."""
from cirq.contrib.quantum_volume import QuantumVolumeResult
from cirq.contrib.acquaintance import SwapPermutationGate
classes = [
QuantumVolumeResult,
SwapPermutationGate,
]
d = {cls.__name__: cls for cls in classes}
return d.get(cirq_type, None)
Expand Down
6 changes: 6 additions & 0 deletions cirq/contrib/json_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from cirq.contrib.quantum_volume import QuantumVolumeResult
from cirq.testing import assert_json_roundtrip_works
from cirq.contrib.json import DEFAULT_CONTRIB_RESOLVERS
from cirq.contrib.acquaintance import SwapPermutationGate


def test_quantum_volume():
Expand All @@ -12,3 +13,8 @@ def test_quantum_volume():
compiled_circuit=cirq.Circuit(cirq.H.on_each(qubits)),
sampler_result=.1)
assert_json_roundtrip_works(qvr, resolvers=DEFAULT_CONTRIB_RESOLVERS)


def test_swap_permutation_gate():
gate = SwapPermutationGate(swap_gate=cirq.SWAP)
assert_json_roundtrip_works(gate, resolvers=DEFAULT_CONTRIB_RESOLVERS)
61 changes: 45 additions & 16 deletions cirq/contrib/quantum_volume/quantum_volume.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ def compute_heavy_set(circuit: cirq.Circuit) -> List[int]:

@dataclass
class CompilationResult:
swap_network: ccr.SwapNetwork
circuit: cirq.Circuit
mapping: Dict[cirq.Qid, cirq.Qid]
parity_map: Dict[cirq.Qid, cirq.Qid]


Expand All @@ -117,8 +118,8 @@ def sample_heavy_set(compilation_result: CompilationResult,
output bit-strings were in the heavy set.
"""
mapping = compilation_result.swap_network.final_mapping()
circuit = compilation_result.swap_network.circuit
mapping = compilation_result.mapping
circuit = compilation_result.circuit

# Add measure gates to the end of (a copy of) the circuit. Ensure that those
# gates measure those in the given mapping, preserving this order.
Expand Down Expand Up @@ -197,6 +198,24 @@ def process_results(mapping: Dict[cirq.Qid, cirq.Qid],
return data


class SwapPermutationReplacer(cirq.PointOptimizer):
"""Replaces SwapPermutationGates with their underlying implementation
gate."""

def __init__(self):
super().__init__()

def optimization_at(self, circuit: cirq.Circuit, index: int,
op: cirq.Operation
) -> Optional[cirq.PointOptimizationSummary]:
if isinstance(op.gate, cirq.contrib.acquaintance.SwapPermutationGate):
new_ops = op.gate.swap_gate.on(*op.qubits)
return cirq.PointOptimizationSummary(clear_span=1,
clear_qubits=op.qubits,
new_operations=new_ops)
return None # Don't make changes to other gates.


def compile_circuit(
circuit: cirq.Circuit,
*,
Expand Down Expand Up @@ -238,13 +257,14 @@ def compile_circuit(
# Sort just to make it deterministic.
for idx, qubit in enumerate(sorted(compiled_circuit.all_qubits())):
# For each qubit, create a new qubit that will serve as its parity
# check. This parity bit is initialized to 0 and then CNOTed with
# the original qubit. Later, these two qubits will be checked for
# equality - if they don't match, there was likely a readout error.
# check. An inverse-controlled-NOT is performed on the qubit and its
# parity bit. Later, these two qubits will be checked for parity -
# if they are equal, there was likely a readout error.
qubit_num = idx + num_qubits
parity_qubit = cirq.LineQubit(qubit_num)
compiled_circuit.append(cirq.X(parity_qubit))
compiled_circuit.append(cirq.X(qubit))
compiled_circuit.append(cirq.CNOT(qubit, parity_qubit))
compiled_circuit.append(cirq.X(qubit))
parity_map[qubit] = parity_qubit

# Swap Mapping (Routing). Ensure the gates can actually operate on the
Expand All @@ -264,19 +284,29 @@ def compile_circuit(
swap_networks.append(swap_network)
assert len(swap_networks) > 0, 'Unable to get routing for circuit'

swap_networks.sort(key=lambda swap_network: len(swap_network.circuit))
# Sort by the least number of qubits first (as routing sometimes adds extra
# ancilla qubits), and then the length of the circuit second.
swap_networks.sort(key=lambda swap_network: (len(
swap_network.circuit.all_qubits()), len(swap_network.circuit)))

routed_circuit = swap_networks[0].circuit
mapping = swap_networks[0].final_mapping()
# Replace the PermutationGates with regular gates, so we don't proliferate
# the routing implementation details to the compiler and the device itself.
SwapPermutationReplacer().optimize_circuit(routed_circuit)

if not compiler:
return CompilationResult(swap_network=swap_networks[0],
return CompilationResult(circuit=routed_circuit,
mapping=mapping,
parity_map=parity_map)

# Compile. This should decompose the routed circuit down to a gate set that
# our device supports, and then optimize. The paper uses various
# compiling techniques - because Quantum Volume is intended to test those
# as well, we allow this to be passed in. This compiler is not allowed to
# change the order of the qubits.
swap_networks[0].circuit = compiler(swap_networks[0].circuit)
return CompilationResult(swap_network=swap_networks[0],
return CompilationResult(circuit=compiler(swap_networks[0].circuit),
mapping=mapping,
parity_map=parity_map)


Expand Down Expand Up @@ -386,11 +416,10 @@ def execute_circuits(
sampler=sampler)
print(f" Compiled HOG probability #{circuit_i + 1}: {prob}")
results.append(
QuantumVolumeResult(
model_circuit=model_circuit,
heavy_set=heavy_set,
compiled_circuit=compilation_result.swap_network.circuit,
sampler_result=prob))
QuantumVolumeResult(model_circuit=model_circuit,
heavy_set=heavy_set,
compiled_circuit=compilation_result.circuit,
sampler_result=prob))
return results


Expand Down
86 changes: 61 additions & 25 deletions cirq/contrib/quantum_volume/quantum_volume_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ def test_sample_heavy_set():
measurements={'mock': np.array([[0, 1], [1, 0], [1, 1], [0, 0]])})
sampler.run = MagicMock(return_value=result)
circuit = cirq.Circuit(cirq.measure(*cirq.LineQubit.range(2)))
compilation_result = CompilationResult(swap_network=ccr.SwapNetwork(
circuit, {}),
compilation_result = CompilationResult(circuit=circuit,
mapping={},
parity_map={})
probability = cirq.contrib.quantum_volume.sample_heavy_set(
compilation_result, [1, 2, 3], sampler=sampler, repetitions=10)
Expand Down Expand Up @@ -100,8 +100,8 @@ def test_sample_heavy_set_with_parity():
sampler.run = MagicMock(return_value=result)
circuit = cirq.Circuit(cirq.measure(*cirq.LineQubit.range(4)))
compilation_result = CompilationResult(
swap_network=ccr.SwapNetwork(circuit,
{q: q for q in cirq.LineQubit.range(4)}),
circuit=circuit,
mapping={q: q for q in cirq.LineQubit.range(4)},
parity_map={
cirq.LineQubit(0): cirq.LineQubit(1),
cirq.LineQubit(2): cirq.LineQubit(3)
Expand Down Expand Up @@ -136,54 +136,90 @@ def test_compile_circuit():
compiler=compiler_mock,
routing_attempts=1)

assert len(compilation_result.swap_network.final_mapping()) == 3
assert len(compilation_result.mapping) == 3
assert cirq.contrib.routing.ops_are_consistent_with_device_graph(
compilation_result.swap_network.circuit.all_operations(),
compilation_result.circuit.all_operations(),
cirq.contrib.routing.xmon_device_to_graph(cirq.google.Bristlecone))
compiler_mock.assert_called_with(compilation_result.swap_network.circuit)
compiler_mock.assert_called_with(compilation_result.circuit)


def test_compile_circuit_replaces_swaps():
"""Tests that the compiler never sees the SwapPermutationGates from the
router."""
compiler_mock = MagicMock(side_effect=lambda circuit: circuit)
a, b, c = cirq.LineQubit.range(3)
# Create a circuit that will require some swaps.
model_circuit = cirq.Circuit([
cirq.Moment([cirq.CNOT(a, b)]),
cirq.Moment([cirq.CNOT(a, c)]),
cirq.Moment([cirq.CNOT(b, c)]),
])
compilation_result = cirq.contrib.quantum_volume.compile_circuit(
model_circuit,
device=cirq.google.Bristlecone,
compiler=compiler_mock,
routing_attempts=1)

# Assert that there were some swaps in the result
compiler_mock.assert_called_with(compilation_result.circuit)
assert len(
list(
compilation_result.circuit.findall_operations_with_gate_type(
cirq.ops.SwapPowGate))) > 0
# Assert that there were not SwapPermutations in the result.
assert len(
list(
compilation_result.circuit.findall_operations_with_gate_type(
cirq.contrib.acquaintance.SwapPermutationGate))) == 0


def test_compile_circuit_with_readout_correction():
"""Tests that we are able to compile a model circuit."""
"""Tests that we are able to compile a model circuit with readout error
correction."""
compiler_mock = MagicMock(side_effect=lambda circuit: circuit)
router_mock = MagicMock(
side_effect=lambda circuit, network: ccr.SwapNetwork(circuit, {}))
a, b, c = cirq.LineQubit.range(3)
ap, bp, cp = cirq.LineQubit.range(3, 6)
model_circuit = cirq.Circuit([
cirq.Moment([cirq.X(a), cirq.Y(b), cirq.Z(c)]),
])
compilation_result = cirq.contrib.quantum_volume.compile_circuit(
model_circuit,
device=cirq.google.Bristlecone,
compiler=compiler_mock,
router=router_mock,
routing_attempts=1,
add_readout_error_correction=True)

assert len(compilation_result.swap_network.final_mapping()) == 6
assert compilation_result.parity_map == {
cirq.LineQubit(0): cirq.LineQubit(3),
cirq.LineQubit(1): cirq.LineQubit(4),
cirq.LineQubit(2): cirq.LineQubit(5)
}
assert cirq.contrib.routing.ops_are_consistent_with_device_graph(
compilation_result.swap_network.circuit.all_operations(),
cirq.contrib.routing.xmon_device_to_graph(cirq.google.Bristlecone))
compiler_mock.assert_called_with(compilation_result.swap_network.circuit)
assert compilation_result.circuit == cirq.Circuit([
cirq.Moment([cirq.X(a), cirq.Y(b), cirq.Z(c)]),
cirq.Moment([cirq.X(a), cirq.X(b), cirq.X(c)]),
cirq.Moment([cirq.CNOT(a, ap),
cirq.CNOT(b, bp),
cirq.CNOT(c, cp)]),
cirq.Moment([cirq.X(a), cirq.X(b), cirq.X(c)]),
])


def test_compile_circuit_multiple_routing_attempts():
"""Tests that we make multiple attempts at r
outing and keep the best one."""
"""Tests that we make multiple attempts at routing and keep the best one."""
qubits = cirq.LineQubit.range(3)
initial_mapping = dict(zip(qubits, qubits))
badly_routed = cirq.Circuit([
more_operations = cirq.Circuit([
cirq.X.on_each(qubits),
cirq.Y.on_each(qubits),
])
more_qubits = cirq.Circuit([
cirq.X.on_each(cirq.LineQubit.range(4)),
])
well_routed = cirq.Circuit([
cirq.X.on_each(qubits),
])
router_mock = MagicMock(side_effect=[
ccr.SwapNetwork(badly_routed, initial_mapping),
ccr.SwapNetwork(more_operations, initial_mapping),
ccr.SwapNetwork(well_routed, initial_mapping),
ccr.SwapNetwork(more_qubits, initial_mapping),
])
compiler_mock = MagicMock(side_effect=lambda circuit: circuit)
model_circuit = cirq.Circuit([cirq.X.on_each(qubits)])
Expand All @@ -193,10 +229,10 @@ def test_compile_circuit_multiple_routing_attempts():
device=cirq.google.Bristlecone,
compiler=compiler_mock,
router=router_mock,
routing_attempts=2)
routing_attempts=3)

assert compilation_result.swap_network.final_mapping() == initial_mapping
assert router_mock.call_count == 2
assert compilation_result.mapping == initial_mapping
assert router_mock.call_count == 3
compiler_mock.assert_called_with(well_routed)


Expand Down

0 comments on commit 2b88d34

Please sign in to comment.