def test_route_circuit_via_unitaries(n_moments, algo, seed, make_bad): circuit = cirq.testing.random_circuit(4, n_moments, 0.5, random_state=seed) device_graph = ccr.get_grid_device_graph(3, 2) swap_network = ccr.route_circuit(circuit, device_graph, algo_name=algo, random_state=seed) logical_qubits = sorted(circuit.all_qubits()) if len(logical_qubits) < 2: return reverse_mapping = {l: p for p, l in swap_network.initial_mapping.items()} physical_qubits = [reverse_mapping[l] for l in logical_qubits] physical_qubits += list(set(device_graph).difference(physical_qubits)) n_unused_qubits = len(physical_qubits) - len(logical_qubits) if make_bad: swap_network.circuit += [cirq.CNOT(*physical_qubits[:2])] cca.return_to_initial_mapping(swap_network.circuit) logical_unitary = circuit.unitary(qubit_order=logical_qubits) logical_unitary = np.kron(logical_unitary, np.eye(1 << n_unused_qubits)) physical_unitary = swap_network.circuit.unitary( qubit_order=physical_qubits) assert ccr.is_valid_routing(circuit, swap_network) == (not make_bad) assert np.allclose(physical_unitary, logical_unitary) == (not make_bad)
def test_route_circuit_reproducible_between_runs(algo): seed = 23 circuit = cirq.testing.random_circuit(6, 5, 0.5, random_state=seed) device_graph = ccr.get_grid_device_graph(2, 3) swap_network = ccr.route_circuit(circuit, device_graph, algo_name=algo, random_state=seed) swap_network_str = str(swap_network).lstrip('\n').rstrip() expected_swap_network_str = """ ┌──┐ ┌────┐ ┌──────┐ (0, 0): ───4────Z─────4────@───────4──────────────4─── │ (0, 1): ───2────@─────2────┼1↦0────5────@─────────5─── │ ││ │ (0, 2): ───5────┼─────5────┼0↦1────2────┼iSwap────2─── │ │ ││ (1, 0): ───3────┼T────3────@───────3────┼┼────────3─── │ ││ (1, 1): ───1────@─────1────────────1────X┼────────1─── │ (1, 2): ───0────X─────0────────────0─────iSwap────0─── └──┘ └────┘ └──────┘ """.lstrip('\n').rstrip() assert swap_network_str == expected_swap_network_str
def test_route_circuit(circuit, device_graph, algo): swap_network = ccr.route_circuit(circuit, device_graph, algo_name=algo) assert set(swap_network.initial_mapping).issubset(device_graph) assert (sorted(swap_network.initial_mapping.values()) == sorted( circuit.all_qubits())) assert ccr.ops_are_consistent_with_device_graph( swap_network.circuit.all_operations(), device_graph) assert ccr.is_valid_routing(circuit, swap_network)
def compile_circuit( circuit: cirq.Circuit, *, device: cirq.google.XmonDevice, routing_attempts: int, compiler: Callable[[cirq.Circuit], cirq.Circuit] = None, routing_algo_name: Optional[str] = None, router: Optional[Callable[..., ccr.SwapNetwork]] = None, ) -> ccr.SwapNetwork: """Compile the given model circuit onto the given device. This uses a different compilation method than described in https://arxiv.org/pdf/1811.12926.pdf Appendix A. The latter goes through a 7-step process involving various decompositions, routing, and optimization steps. We route the model circuit and then run a series of optimizers on it (which can be passed into this function). Args: circuit: The model circuit to compile. device: The device to compile onto. routing_attempts: See doc for calculate_quantum_volume. compiler: An optional function to deconstruct the model circuit's gates down to the target devices gate set and then optimize it. Returns: A tuple where the first value is the compiled circuit and the second value is the final mapping from the model circuit to the compiled circuit. The latter is necessary in order to preserve the measurement order. """ compiled_circuit = circuit.copy() # Swap Mapping (Routing). Ensure the gates can actually operate on the # target qubits given our topology. if router is None and routing_algo_name is None: routing_algo_name = 'greedy' best_swap_network: Union[ccr.SwapNetwork, None] = None best_score = None for _ in range(routing_attempts): swap_network = ccr.route_circuit(compiled_circuit, ccr.xmon_device_to_graph(device), router=router, algo_name=routing_algo_name) score = len(swap_network.circuit) if best_score is None or score < best_score: best_swap_network = swap_network best_score = score if best_swap_network is None: raise AssertionError('Unable to get routing for circuit') # 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. if compiler: best_swap_network.circuit = compiler(best_swap_network.circuit) return best_swap_network
def test_route_circuit_reproducible_with_seed(algo, seed): circuit = cirq.testing.random_circuit(8, 20, 0.5, random_state=seed) device_graph = ccr.get_grid_device_graph(4, 3) wrappers = (lambda s: s, np.random.RandomState) swap_networks = [] for wrapper, _ in itertools.product(wrappers, range(3)): swap_network = ccr.route_circuit(circuit, device_graph, algo_name=algo, random_state=wrapper(seed)) swap_networks.append(swap_network) eq = cirq.testing.equals_tester.EqualsTester() eq.add_equality_group(*swap_networks)
def test_route_circuit(n_moments, algo, circuit_seed, routing_seed): circuit = cirq.testing.random_circuit(10, n_moments, 0.5, random_state=circuit_seed) device_graph = ccr.get_grid_device_graph(4, 3) swap_network = ccr.route_circuit(circuit, device_graph, algo_name=algo, random_state=routing_seed) assert set(swap_network.initial_mapping).issubset(device_graph) assert sorted(swap_network.initial_mapping.values()) == sorted( circuit.all_qubits()) assert ccr.ops_are_consistent_with_device_graph( swap_network.circuit.all_operations(), device_graph) assert ccr.is_valid_routing(circuit, swap_network)
def run(self, target, run_analyser=True): with tempfile.TemporaryDirectory() as tmpdirname: fname = os.path.join(tmpdirname, "target.qasm") target.save_to_qasm(fname, qreg_name="q") with open(fname) as file: qasm_data = file.read() original_circuit = circuit_from_qasm(qasm_data) start_time = timer() # only 'greedy' routing is implemented in Cirq swap_networks: List[ccr.SwapNetwork] = [] routing_attempts = 1 with suppress(KeyError): routing_attempts = self._cfg["routing_attempts"] for _ in range(routing_attempts): swap_network = ccr.route_circuit(original_circuit, self.cirq_hardware, router=None, algo_name="greedy") swap_networks.append(swap_network) assert len(swap_networks) > 0, "Unable to get routing for 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 qubit_order = { LineQubit(n): NamedQubit(f"q_{n}") for n in range(self.quantum_hardware.num_qubits) } # decompose composite gates no_decomp = lambda op: isinstance(op.gate, CNotPowGate) opt = ExpandComposite(no_decomp=no_decomp) opt.optimize_circuit(routed_circuit) self.execution_time = timer() - start_time qasm_data = routed_circuit.to_qasm(qubit_order=qubit_order) lines = qasm_data.split("\n") gate_chain = GateChain.from_qasm_list_of_lines(lines, quantum_hardware=None) if run_analyser: self.analyse(target, gate_chain) self.analyser_report["Execution Time"] = self.execution_time return gate_chain
def test_router_bad_args(): circuit = cirq.Circuit() device_graph = ccr.get_linear_device_graph(5) with pytest.raises(ValueError): ccr.route_circuit(circuit, device_graph) algo_name = 'greedy' with pytest.raises(ValueError): ccr.route_circuit(circuit, device_graph, algo_name=algo_name, router=ccr.ROUTERS[algo_name]) circuit = cirq.Circuit(cirq.CCZ(*cirq.LineQubit.range(3))) with pytest.raises(ValueError): ccr.route_circuit(circuit, device_graph, algo_name=algo_name) circuit = cirq.Circuit( cirq.CZ(cirq.LineQubit(i), cirq.LineQubit(i + 1)) for i in range(5)) with pytest.raises(ValueError): ccr.route_circuit(circuit, device_graph, algo_name=algo_name)
def place_circuit(circuit, device, exclude_always): if exclude_always is None: exclude_always = set() else: exclude_always = set(exclude_always) try: return cirq.google.optimized_for_sycamore(circuit=circuit, new_device=device, optimizer_type='sycamore') except ValueError as e: pass # Workaround to work with route_circuit, which unnecessarily doesn't support multi-qubit measures def split_measure( measure_gate: 'cirq.GateOperation') -> 'cirq.GateOperation': if not cirq.protocols.is_measurement(measure_gate): yield measure_gate return key = cirq.protocols.measurement_key(measure_gate) yield cirq.Moment([ cirq.measure(qubit, key=key + '.' + str(qubit)) for qubit in measure_gate.qubits ]) circuit = cirq.Circuit(*map(split_measure, circuit.all_operations())) available_qubits = device.qubit_set() - exclude_always graph = naive_connectivity(available_qubits) circuit = route_circuit(circuit=circuit, device_graph=graph, algo_name='greedy').circuit circuit = cirq.google.optimized_for_sycamore(circuit=circuit, new_device=device, optimizer_type='sycamore') # Workaround because SerializableDevice is not json-able circuit = cirq.Circuit() + circuit device.validate_circuit(circuit) return circuit
def compile_circuit( circuit: cirq.Circuit, *, device: cirq.google.XmonDevice, routing_attempts: int, compiler: Callable[[cirq.Circuit], cirq.Circuit] = None, routing_algo_name: Optional[str] = None, router: Optional[Callable[..., ccr.SwapNetwork]] = None, add_readout_error_correction=False, ) -> CompilationResult: """Compile the given model circuit onto the given device. This uses a different compilation method than described in https://arxiv.org/pdf/1811.12926.pdf Appendix A. The latter goes through a 7-step process involving various decompositions, routing, and optimization steps. We route the model circuit and then run a series of optimizers on it (which can be passed into this function). Args: circuit: The model circuit to compile. device: The device to compile onto. routing_attempts: See doc for calculate_quantum_volume. compiler: An optional function to deconstruct the model circuit's gates down to the target devices gate set and then optimize it. add_readout_error_correction: If true, add some parity bits that will later be used to detect readout error. Returns: A tuple where the first value is the compiled circuit and the second value is the final mapping from the model circuit to the compiled circuit. The latter is necessary in order to preserve the measurement order. """ compiled_circuit = circuit.copy() # Optionally add some the parity check bits. parity_map: Dict[cirq.Qid, cirq.Qid] = {} # original -> parity if add_readout_error_correction: num_qubits = len(compiled_circuit.all_qubits()) # 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. qubit_num = idx + num_qubits parity_qubit = cirq.LineQubit(qubit_num) compiled_circuit.append(cirq.X(parity_qubit)) compiled_circuit.append(cirq.CNOT(qubit, parity_qubit)) parity_map[qubit] = parity_qubit # Swap Mapping (Routing). Ensure the gates can actually operate on the # target qubits given our topology. if router is None and routing_algo_name is None: # TODO: The routing algorithm sometimes does a poor job with the parity # qubits, adding SWAP gates that are unnecessary. This should be fixed, # or we can add the parity qubits manually after routing. routing_algo_name = 'greedy' swap_networks: List[ccr.SwapNetwork] = [] for _ in range(routing_attempts): swap_network = ccr.route_circuit(compiled_circuit, ccr.xmon_device_to_graph(device), router=router, algo_name=routing_algo_name) 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)) if not compiler: return CompilationResult(swap_network=swap_networks[0], 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], parity_map=parity_map)
def compile_circuit( circuit: cirq.Circuit, *, device_graph: nx.Graph, routing_attempts: int, compiler: Callable[[cirq.Circuit], cirq.Circuit] = None, routing_algo_name: Optional[str] = None, router: Optional[Callable[..., ccr.SwapNetwork]] = None, add_readout_error_correction=False, ) -> CompilationResult: """Compile the given model circuit onto the given device graph. This uses a different compilation method than described in https://arxiv.org/pdf/1811.12926.pdf Appendix A. The latter goes through a 7-step process involving various decompositions, routing, and optimization steps. We route the model circuit and then run a series of optimizers on it (which can be passed into this function). Args: circuit: The model circuit to compile. device_graph: The device graph to compile onto. routing_attempts: See doc for calculate_quantum_volume. compiler: An optional function to deconstruct the model circuit's gates down to the target devices gate set and then optimize it. routing_algo_name: The name of the routing algorithm, see ROUTING in `route_circuit.py`. router: The function that actually does the routing. add_readout_error_correction: If true, add some parity bits that will later be used to detect readout error. Returns: A tuple where the first value is the compiled circuit and the second value is the final mapping from the model circuit to the compiled circuit. The latter is necessary in order to preserve the measurement order. """ compiled_circuit = circuit.copy() # Optionally add some the parity check bits. parity_map: Dict[cirq.Qid, cirq.Qid] = {} # original -> parity if add_readout_error_correction: num_qubits = len(compiled_circuit.all_qubits()) # 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. 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(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 # target qubits given our topology. if router is None and routing_algo_name is None: # TODO: The routing algorithm sometimes does a poor job with the parity # qubits, adding SWAP gates that are unnecessary. This should be fixed, # or we can add the parity qubits manually after routing. # Github issue: https://github.com/quantumlib/Cirq/issues/2967 routing_algo_name = 'greedy' swap_networks: List[ccr.SwapNetwork] = [] for _ in range(routing_attempts): swap_network = ccr.route_circuit(compiled_circuit, device_graph, router=router, algo_name=routing_algo_name) swap_networks.append(swap_network) assert len(swap_networks) > 0, 'Unable to get routing for 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(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. return CompilationResult(circuit=compiler(swap_networks[0].circuit), mapping=mapping, parity_map=parity_map)