Пример #1
0
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)
Пример #2
0
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
Пример #3
0
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)
Пример #4
0
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
Пример #5
0
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)
Пример #6
0
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)
Пример #7
0
    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
Пример #8
0
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
Пример #10
0
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)
Пример #11
0
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)