Source code for qibocal.auto.transpile

from typing import Optional

from qibo import Circuit, gates
from qibo.backends import Backend
from qibo.transpiler.pipeline import Passes
from qibo.transpiler.unroller import NativeGates, Unroller
from qibolab import PulseSequence
from qibolab._core.compilers import Compiler
from qibolab._core.native import NativeContainer

from qibocal.auto.operation import QubitId

REPLACEMENTS = {
    "RX": "GPI2",
    "MZ": "M",
}


[docs]def transpile_circuits( circuits: list[Circuit], qubit_maps: list[list[QubitId]], backend: Backend, transpiler: Optional[Passes], ): """Transpile and pad `circuits` according to the platform. Apply the `transpiler` to `circuits` and pad them in circuits with the same number of qubits in the platform. Before manipulating the circuits, this function check that the `qubit_maps` contain string ids and in the positive case it remap them in integers, following the ids order provided by the platform. .. note:: In this function we are implicitly assume that the qubit ids are all string or all integers. """ transpiled_circuits = [] platform = backend.platform qubits = list(platform.qubits) if isinstance(qubit_maps[0][0], str): for i, qubit_map in enumerate(qubit_maps): qubit_map = map(lambda x: qubits.index(x), qubit_map) qubit_maps[i] = list(qubit_map) platform_nqubits = platform.nqubits for circuit, qubit_map in zip(circuits, qubit_maps): new_circuit = pad_circuit(platform_nqubits, circuit, qubit_map) transpiled_circ, _ = transpiler(new_circuit) transpiled_circuits.append(transpiled_circ) return transpiled_circuits
[docs]def execute_transpiled_circuits( circuits: list[Circuit], qubit_maps: list[list[QubitId]], backend: Backend, transpiler: Optional[Passes], initial_states=None, nshots=1000, ): """Transpile `circuits`. If the `qibolab` backend is used, this function pads the `circuits` in new ones with a number of qubits equal to the one provided by the platform. At the end, the circuits are transpiled, executed and the results returned. The input `transpiler` is optional, but it should be provided if the backend is `qibolab`. For the qubit map look :func:`dummy_transpiler`. This function returns the list of transpiled circuits and the execution results. """ transpiled_circuits = transpile_circuits( circuits, qubit_maps, backend, transpiler, ) return transpiled_circuits, backend.execute_circuits( transpiled_circuits, initial_states=initial_states, nshots=nshots )
[docs]def execute_transpiled_circuit( circuit: Circuit, qubit_map: list[QubitId], backend: Backend, transpiler: Optional[Passes], initial_state=None, nshots=1000, ): """Transpile `circuit`. If the `qibolab` backend is used, this function pads the `circuit` in new a one with a number of qubits equal to the one provided by the platform. At the end, the circuit is transpiled, executed and the results returned. The input `transpiler` is optional, but it should be provided if the backend is `qibolab`. For the qubit map look :func:`dummy_transpiler`. This function returns the transpiled circuit and the execution results. """ transpiled_circ = transpile_circuits( [circuit], [qubit_map], backend, transpiler, )[0] return transpiled_circ, backend.execute_circuit( transpiled_circ, initial_state=initial_state, nshots=nshots )
[docs]def natives(platform) -> dict[str, NativeContainer]: """ Return the dict of native gates name with the associated native container defined in the `platform`. This function assumes the native gates to be the same for each qubit and pair. """ qubit = next(iter(platform.qubits)) single_qubit_natives_container = platform.natives.single_qubit[qubit] single_qubit_natives = list(single_qubit_natives_container.model_fields) if len(platform.pairs) > 0: # add two qubit natives only if there are pairs pair = next(iter(platform.pairs)) two_qubit_natives_container = platform.natives.two_qubit[pair] two_qubit_natives = list(two_qubit_natives_container.model_fields) else: two_qubit_natives = [] # Solve Qibo-Qibolab mismatch single_qubit_natives.append("RZ") single_qubit_natives.append("Z") single_qubit_natives.remove("RX12") single_qubit_natives.remove("RX90") single_qubit_natives.remove("CP") single_qubit_natives = [REPLACEMENTS.get(x, x) for x in single_qubit_natives] return {i: platform.natives.single_qubit[qubit] for i in single_qubit_natives} | { i: platform.natives.two_qubit[pair] for i in two_qubit_natives }
[docs]def create_rule(name, natives): """Create rule for gate name given container natives.""" def rule(gate: gates.Gate, natives: NativeContainer) -> PulseSequence: return natives.ensure(name).create_sequence() return rule
[docs]def set_compiler(backend, natives_): """ Set the compiler to execute the native gates defined by the platform. """ compiler = backend.compiler rules = {} for name, natives_container in natives_.items(): gate = getattr(gates, name) if gate not in compiler.rules: rules[gate] = create_rule(name, natives_container) else: rules[gate] = compiler.rules[gate] rules[gates.I] = compiler.rules[gates.I] backend.compiler = Compiler(rules=rules)
[docs]def dummy_transpiler(backend: Backend) -> Passes: """ If the backend is `qibolab`, a transpiler with just an unroller is returned, otherwise `None`. This function overwrites the compiler defined in the backend, taking into account the native gates defined in the`platform` (see :func:`set_compiler`). """ platform = backend.platform native_gates = natives(platform) set_compiler(backend, native_gates) native_gates = [getattr(gates, x) for x in native_gates] unroller = Unroller(NativeGates.from_gatelist(native_gates)) return Passes(connectivity=platform.pairs, passes=[unroller])
[docs]def pad_circuit(nqubits, circuit: Circuit, qubit_map: list[int]) -> Circuit: """ Pad `circuit` in a new one with `nqubits` qubits, according to `qubit_map`. `qubit_map` is a list `[i, j, k, ...]`, where the i-th physical qubit is mapped into the 0th logical qubit and so on. """ new_circuit = Circuit(nqubits) new_circuit.add(circuit.on_qubits(*qubit_map)) return new_circuit