Source code for qibosoq.programs.flux

"""Flux program used by qibosoq to execute sequences and sweeps."""

import logging
from typing import Dict, List, Tuple

import numpy as np
from qick import QickSoc
from qick.asm_v1 import QickRegister

import qibosoq.configuration as qibosoq_cfg
from qibosoq.components.base import Config, Qubit
from qibosoq.components.pulses import (
    Arbitrary,
    Element,
    FluxExponential,
    Pulse,
    Rectangular,
)
from qibosoq.programs.base import BaseProgram

logger = logging.getLogger(qibosoq_cfg.MAIN_LOGGER_NAME)


[docs] class FluxProgram(BaseProgram): """Abstract class for flux-tunable qubits programs.""" def __init__( self, soc: QickSoc, qpcfg: Config, sequence: List[Element], qubits: List[Qubit] ): """Define an empty dictionary for bias sweepers and call super().__init__.""" self.bias_sweep_registers: Dict[int, Tuple[QickRegister, QickRegister]] = {} super().__init__(soc, qpcfg, sequence, qubits)
[docs] def set_bias(self, mode: str = "sweetspot"): """Set qubits flux lines to a bias level. Note that this fuction acts only on the qubits used in self.sequence. Args: mode (str): can be 'sweetspot' or 'zero' """ duration = 48 # minimum len for qubit in self.qubits: # if bias is zero, just skip the qubit if qubit.bias is None or qubit.dac is None or qubit.bias == 0: continue flux_ch = qubit.dac max_gain = int(self.soccfg["gens"][flux_ch]["maxv"]) if mode == "sweetspot": value = max_gain elif mode == "zero": value = 0 else: raise NotImplementedError(f"Mode {mode} not supported") i_wf = np.full(duration, value) q_wf = np.zeros(len(i_wf)) self.add_pulse(flux_ch, f"const_{value}_{flux_ch}", i_wf, q_wf) self.set_pulse_registers( ch=flux_ch, waveform=f"const_{value}_{flux_ch}", style="arb", outsel="input", stdysel="last", freq=0, phase=0, gain=np.trunc(max_gain * qubit.bias).astype(int), ) if flux_ch in self.bias_sweep_registers: swept_reg, non_swept_reg = self.bias_sweep_registers[flux_ch] if mode == "sweetspot": non_swept_reg.set_to(swept_reg) elif mode == "zero": non_swept_reg.set_to(0) self.pulse(ch=flux_ch) self.sync_all(50) # wait all pulses are fired + 50 clks
[docs] def find_qubit_sweetspot(self, pulse: Pulse) -> float: """Return bias of a qubit from flux pulse.""" for qubit in self.qubits: if pulse.dac == qubit.dac: return qubit.bias if qubit.bias else 0 return 0.0
[docs] def execute_flux_pulse(self, pulse: Pulse): """Fire a fast flux pulse the starts and ends in sweetspot.""" gen_ch = pulse.dac max_gain = int(self.soccfg["gens"][gen_ch]["maxv"]) bias = self.find_qubit_sweetspot(pulse) sweetspot = np.trunc(bias * max_gain).astype(int) duration = self.soc.us2cycles(pulse.duration, gen_ch=gen_ch) samples_per_clk = self._gen_mgrs[gen_ch].samps_per_clk duration *= samples_per_clk # the duration here is expressed in samples if isinstance(pulse, Rectangular): amp = np.trunc(pulse.amplitude * max_gain).astype(int) i_vals = np.full(duration, amp) elif isinstance(pulse, FluxExponential): i_vals = pulse.i_values(duration, max_gain) elif isinstance(pulse, Arbitrary): i_vals = np.array(pulse.i_values) logger.info("Arbitrary shaped flux pulse. q_vals will be ignored.") else: raise NotImplementedError( "Only Rectangular, FluxExponential and Arbitrary are supported for flux pulses" ) # add a clock cycle of sweetspot values i_vals = np.append(i_vals + sweetspot, np.full(samples_per_clk, sweetspot)) q_vals = np.zeros(len(i_vals)) if (abs(i_vals) > max_gain).any(): raise ValueError("Flux pulse got amplitude > 1") self.add_pulse(gen_ch, pulse.name, i_vals, q_vals) self.set_pulse_registers( ch=gen_ch, waveform=pulse.name, style="arb", outsel="input", stdysel="last", freq=0, phase=0, gain=max_gain, ) self.pulse(ch=gen_ch)
[docs] def declare_nqz_flux(self): """Declare nqz = 1 for used flux channel.""" for qubit in self.qubits: if qubit.dac is not None: flux_ch = qubit.dac self.declare_gen(flux_ch, nqz=1)
[docs] def body(self): """Execute sequence of pulses. For each pulses calls the add_pulse_to_register function (if not already registered) before firing it. If the pulse is a readout, it does a measurement and does not wait for the end of it. At the end of the sequence wait for meas and clock. """ # in the form of {dac_number_0: last_pulse_of_dac_0, ...} last_pulse_registered = {} muxed_pulses_executed = [] muxed_ro_executed_indexes = [] self.set_bias("sweetspot") for elem in self.sequence: # wait the needed wait time so that the start is timed correctly if isinstance(elem.start_delay, QickRegister): self.sync(elem.start_delay.page, elem.start_delay.addr) else: # assume is number delay_start = self.soc.us2cycles(elem.start_delay) if delay_start != 0: self.synci(delay_start) if elem.type == "readout": self.execute_readout_pulse( elem, muxed_pulses_executed, muxed_ro_executed_indexes ) elif elem.type == "drive": assert isinstance(elem, Pulse) self.execute_drive_pulse(elem, last_pulse_registered) elif elem.type == "flux": assert isinstance(elem, Pulse) self.execute_flux_pulse(elem) self.wait_all() self.set_bias("zero") self.sync_all(self.relax_delay)
[docs] def declare_zones_and_ro(self, sequence: List[Pulse]): """Declare all nqz zones and readout frequencies. Declares drives, fluxes and readout (mux or not) and readout freq. """ self.declare_nqz_zones([pulse for pulse in sequence if pulse.type == "drive"]) self.declare_nqz_flux() if self.is_mux: self.declare_gen_mux_ro() else: self.declare_nqz_zones( [pulse for pulse in sequence if pulse.type == "readout"] ) self.declare_readout_freq()