Source code for qibolab.compilers.compiler

from collections import defaultdict
from dataclasses import dataclass, field

from qibo import gates
from qibo.config import raise_error

from qibolab.compilers.default import (
    cnot_rule,
    cz_rule,
    gpi2_rule,
    gpi_rule,
    identity_rule,
    measurement_rule,
    rz_rule,
    u3_rule,
    z_rule,
)
from qibolab.pulses import PulseSequence, ReadoutPulse


[docs]@dataclass class Compiler: """Compiler that transforms a :class:`qibo.models.Circuit` to a :class:`qibolab.pulses.PulseSequence`. The transformation is done using a dictionary of rules which map each Qibo gate to a pulse sequence and some virtual Z-phases. A rule is a function that takes two argumens: - gate (:class:`qibo.gates.abstract.Gate`): Gate object to be compiled. - platform (:class:`qibolab.platforms.abstract.AbstractPlatform`): Platform object to read native gate pulses from. and returns: - sequence (:class:`qibolab.pulses.PulseSequence`): Sequence of pulses that implement the given gate. - virtual_z_phases (dict): Dictionary mapping qubits to virtual Z-phases induced by the gate. See :class:`qibolab.compilers.default` for an example of a compiler implementation. """ rules: dict = field(default_factory=dict) """Map from gates to compilation rules."""
[docs] @classmethod def default(cls): return cls( { gates.I: identity_rule, gates.Z: z_rule, gates.RZ: rz_rule, gates.U3: u3_rule, gates.CZ: cz_rule, gates.CNOT: cnot_rule, gates.GPI2: gpi2_rule, gates.GPI: gpi_rule, gates.M: measurement_rule, } )
def __setitem__(self, key, rule): """Sets a new rule to the compiler. If a rule already exists for the gate, it will be overwritten. """ self.rules[key] = rule def __getitem__(self, item): """Get an existing rule for a given gate.""" try: return self.rules[item] except KeyError: raise_error(KeyError, f"Compiler rule not available for {item}.") def __delitem__(self, item): """Remove rule for the given gate.""" try: del self.rules[item] except KeyError: raise_error( KeyError, f"Cannot remove {item} from compiler because it does not exist.", )
[docs] def register(self, gate_cls): """Decorator for registering a function as a rule in the compiler. Using this decorator is optional. Alternatively the user can set the rules directly via ``__setitem__``. Args: gate_cls: Qibo gate object that the rule will be assigned to. """ def inner(func): self[gate_cls] = func return func return inner
def _compile_gate( self, gate, platform, sequence, virtual_z_phases, moment_start, delays ): """Adds a single gate to the pulse sequence.""" rule = self[gate.__class__] # get local sequence and phases for the current gate gate_sequence, gate_phases = rule(gate, platform) # update global pulse sequence # determine the right start time based on the availability of the qubits involved all_qubits = {*gate_sequence.qubits, *gate.qubits} start = max( *[ sequence.get_qubit_pulses(qubit).finish + delays[qubit] for qubit in all_qubits ], moment_start, ) # shift start time and phase according to the global sequence for pulse in gate_sequence: pulse.start += start if not isinstance(pulse, ReadoutPulse): pulse.relative_phase += virtual_z_phases[pulse.qubit] sequence.add(pulse) return gate_sequence, gate_phases
[docs] def compile(self, circuit, platform): """Transforms a circuit to pulse sequence. Args: circuit (qibo.models.Circuit): Qibo circuit that respects the platform's connectivity and native gates. platform (qibolab.platforms.abstract.AbstractPlatform): Platform used to load the native pulse representations. Returns: sequence (qibolab.pulses.PulseSequence): Pulse sequence that implements the circuit. measurement_map (dict): Map from each measurement gate to the sequence of readout pulse implementing it. """ sequence = PulseSequence() # FIXME: This will not work with qubits that have string names # TODO: Implement a mapping between circuit qubit ids and platform ``Qubit``s virtual_z_phases = defaultdict(int) measurement_map = {} # process circuit gates delays = defaultdict(int) for moment in circuit.queue.moments: moment_start = sequence.finish for gate in set(filter(lambda x: x is not None, moment)): if isinstance(gate, gates.Align): for qubit in gate.qubits: delays[qubit] += gate.delay continue gate_sequence, gate_phases = self._compile_gate( gate, platform, sequence, virtual_z_phases, moment_start, delays ) for qubit in gate.qubits: delays[qubit] = 0 # update virtual Z phases for qubit, phase in gate_phases.items(): virtual_z_phases[qubit] += phase # register readout sequences to ``measurement_map`` so that we can # properly map acquisition results to measurement gates if isinstance(gate, gates.M): measurement_map[gate] = gate_sequence return sequence, measurement_map