Esempio n. 1
0
    def _gen_rfcs_block():

        optimize_pb = pb.PulseBlock()
        optimize_pb.insert(p_obj=po.PTrue(ch='aom', dur=1e-6))
        optimize_pb.insert(p_obj=po.PTrue(ch='mw_gate', dur=1e-6))
        optimize_pb.dflt_dict = dict(aom=po.DTrue(), mw_gate=po.DTrue())
        return optimize_pb
Esempio n. 2
0
    def _gen_p_block(self, safety_window=200e-9):

        # ========== Prepare individual blocks =========

        # ------- pre_init_pb --------
        # Long optical pumping before first pulse

        pre_init_pb = pb.PulseBlock(p_obj_list=[po.PTrue(ch='aom', dur=3e-6)])

        # ------- all_off_pb --------
        # Switch all channels off after the full pulse sequence

        all_off_pb = pb.PulseBlock(p_obj_list=[
            po.PFalse(ch='aom', dur=1e-6),
            po.PFalse(ch='mw_gate', dur=1e-6),
            po.PFalse(ch='ctr_gate', dur=1e-6)
        ])

        # ========== Construct full rabi_pb =========

        rabi_pb = pb.PulseBlock(name='RabiPB')

        # Pre-init block
        rabi_pb.insert_pb(pb_obj=pre_init_pb)

        # rabi_element ** n_pts
        for tau in self.tau_ar:
            rabi_pb.append_pb(pb_obj=self._gen_rabi_elem(
                tau=tau, safety_window=safety_window))

        # All-off block
        rabi_pb.append_pb(pb_obj=all_off_pb)

        # Default values
        rabi_pb.dflt_dict = dict(aom=po.DFalse(),
                                 ctr_gate=po.DFalse(),
                                 mw_gate=po.DFalse())

        # Correct for aom delay
        rabi_pb.add_offset(offset_dict=dict(aom=-self._aom_delay))

        # Duration of full RabiPB
        # [for gated_ctr.get_count_ar() timeout]
        self._rabi_pb_dur = rabi_pb.dur

        return rabi_pb
Esempio n. 3
0
def pb_expand_test(res_dict, indicate_bounds=True):
    """ Helper method to test pb_zip()

    This method takes direct output res_dict of pb_zip() call and reconstructs
    the original pulse block according to the sequence.
    To visualize boundaries between individual sequence elements, set optional
    indicate_bounds parameter to True. This will add zero-length divider pulses
    on one of the channels.

    :param res_dict: (dict) return of pb_zip() call to be reconstructed back
    into a plain waveform.
    :param indicate_bounds: (bool) if True, divider pulses will be added to one
    of the channels after each individual waveform.
    :return: (PulseBlock) reconstructed plain waveform
    """

    import pulseblock.pulse as po

    new_pb = pb.PulseBlock()

    seq_list = res_dict['seq_list']
    snip_list = res_dict['snip_list']

    for snip_idx in range(len(seq_list)):
        snip_name, rep_num = seq_list[snip_idx]

        for rep_idx in range(rep_num):

            # Find wfm_snip corresponding to snip_name
            wfm_snip = None
            for wfm_snip in snip_list:
                if wfm_snip.name == snip_name:
                    break

            new_pb.append_pb(pb_obj=wfm_snip)

            # Add a marker to indicate the block boundary
            if indicate_bounds:
                mrk_ch = list(wfm_snip.dflt_dict.keys())[0]

                new_pb.append(p_obj=po.PTrue(ch=mrk_ch, dur=0), cflct_er=False)

    return new_pb
Esempio n. 4
0
    def _gen_rabi_elem(tau=0, safety_window=200e-9):
        rabi_elem = pb.PulseBlock()

        # Init green_aom pulse
        rabi_elem.insert(p_obj=po.PTrue(ch='aom', dur=2e-6 +
                                        2 * safety_window))

        # Normalization pulse
        rabi_elem.insert(p_obj=po.PTrue(ch='ctr_gate', t0=1.5e-6, dur=0.5e-6))

        # mw pulse
        rabi_elem.append(
            p_obj=po.PTrue(ch='mw_gate', dur=tau, t0=4 * safety_window))

        tmp_dur = rabi_elem.dur + 4 * safety_window

        # Readout AOM pulse
        rabi_elem.insert(p_obj=po.PTrue(ch='aom', dur=1e-6, t0=tmp_dur))

        # readout ctr_gate pulse
        rabi_elem.insert(p_obj=po.PTrue(ch='ctr_gate', dur=0.5e-6, t0=tmp_dur))

        return rabi_elem
Esempio n. 5
0
    def _gen_p_block(self, debug=False):

        # Technical params

        # safety window between mw_src makes a step and
        # before counter starts accumulation of counts
        safety_window = 0.1 * self._accum_dur

        # Duration of sweep-step pulse
        step_pulse_dur = 0.1 * self._accum_dur  # 200e-6

        # ========== Prepare individual blocks =========

        # ------- first_pb --------
        # First point in the sweep:
        #   mw_src should reset sweep position to start_f,
        #   so there is no need to send step_trig pulse

        first_pb = pb.PulseBlock()

        # long safety window in the beginning
        first_pb.insert(p_obj=po.PFalse(ch='ctr_gate', dur=self._accum_dur))

        # first ctr_gate pulse
        first_pb.append(p_obj=po.PTrue(ch='ctr_gate', dur=self._accum_dur))

        # ------- step_pb --------
        # This pb is repeated for every subsequent sweep step:
        #   send ctr_trig pulse, wait for a safety window and
        #   start accumulating pulses

        step_pb = pb.PulseBlock()

        # step pulse to mw_src
        step_pb.insert(p_obj=po.PTrue(ch='mw_step', dur=step_pulse_dur))

        # ctr_gate pulse
        step_pb.append(p_obj=po.PTrue(
            ch='ctr_gate', dur=self._accum_dur, t0=safety_window))

        # ------- off_pb --------
        # Once sweep is complete, set all outputs to low

        off_pb = pb.PulseBlock()

        off_pb.insert(p_obj=po.PFalse(ch='aom', dur=safety_window))
        off_pb.insert(p_obj=po.PFalse(ch='mw_gate', dur=safety_window))
        off_pb.insert(p_obj=po.PFalse(ch='mw_step', dur=safety_window))
        off_pb.insert(p_obj=po.PFalse(ch='ctr_gate', dur=safety_window))

        # ========== Construct full odmr_pb =========

        odmr_pb = pb.PulseBlock(name='ODMRPulseBlock')

        # Default values
        odmr_pb.dflt_dict = dict(
            aom=po.DFalse(),
            ctr_gate=po.DFalse(),
            mw_gate=po.DFalse(),
            mw_step=po.DFalse(),
        )

        # first_bp
        odmr_pb.insert_pb(pb_obj=first_pb)

        # append step_bp ** (_n_pts-1)
        for _ in np.arange(start=1, stop=self._n_pts):
            odmr_pb.append_pb(pb_obj=step_pb)

        # aom and mw_gate are High during the whole block
        tmp_dur = odmr_pb.dur
        odmr_pb.insert(p_obj=po.PTrue(ch='aom', dur=tmp_dur))
        odmr_pb.insert(p_obj=po.PTrue(ch='mw_gate', dur=tmp_dur))

        # append off_pb
        odmr_pb.append_pb(pb_obj=off_pb)

        if debug:
            return dict(first_pb=first_pb,
                        step_pb=step_pb,
                        off_pb=off_pb,
                        odmr_pb=odmr_pb)
        else:
            return odmr_pb
Esempio n. 6
0
    def compile_pulseblock(self):
        """ Compiles the list of pulse_specifiers and var dists into valid
        Pulseblock.
        """

        pulseblock = pb.PulseBlock(name=self.name)

        for i, pb_spec in enumerate(self.pulse_specifiers):

            var_dict = pb_spec.pulsevar_dict
            arg_dict = {}

            # Extract parameters from the pulsevar dict
            offset = self.resolve_value(pb_spec.offset) * 1e-6
            arg_dict["ch"] = pb_spec.channel
            arg_dict["dur"] = self.resolve_value(pb_spec.dur) * 1e-6

            self.append_value_to_dict(var_dict, "val", arg_dict)
            self.append_value_to_dict(var_dict, "amp", arg_dict)
            self.append_value_to_dict(var_dict, "freq", arg_dict)
            self.append_value_to_dict(var_dict, "ph", arg_dict)
            self.append_value_to_dict(var_dict,
                                      "stdev",
                                      arg_dict,
                                      fn=lambda x: 1e-6 * x)
            self.append_value_to_dict(var_dict, "iq", arg_dict)
            self.append_value_to_dict(var_dict, "mod", arg_dict)
            self.append_value_to_dict(var_dict, "mod_freq", arg_dict)
            self.append_value_to_dict(var_dict, "mod_ph", arg_dict)

            supported_pulses = {
                "PTrue": po.PTrue,
                "PSin": po.PSin,
                "PGaussian": po.PGaussian,
                "PConst": po.PConst
            }

            # Handle IQ mixing case
            if "iq" in arg_dict and arg_dict["iq"]:

                iq_calibration = IQ_Calibration()
                iq_calibration.load_calibration(self.config["iq_cal_path"])
                # Set arbitrarily so that neither channel will overflow 1
                iq_calibration.IF_volt = 0.8

                (if_freq, lo_freq, phase_opt, amp_i_opt, amp_q_opt, dc_i_opt,
                 dc_q_opt) = iq_calibration.get_optimal_hdawg_and_LO_values(
                     arg_dict["mod_freq"])

                # Store the optimal IQ parameters as 2 separate dictionaries
                arg_dict_i = copy.deepcopy(arg_dict)
                arg_dict_q = copy.deepcopy(arg_dict)

                # Modify the channel names
                arg_dict_i["ch"] = arg_dict["ch"] + "_i"
                arg_dict_q["ch"] = arg_dict["ch"] + "_q"

                # Modulation frequency changed to IF
                arg_dict_i["mod_freq"] = if_freq
                arg_dict_q["mod_freq"] = if_freq

                # Relative phase
                arg_dict_i["mod_ph"] = arg_dict["mod_ph"] + phase_opt[0]

                # The amplitude is the amplitude of the Sin genarator and is
                # indepenent of ["amp"], the signal amplitude.
                arg_dict_i["iq_params"] = {
                    "amp_iq": amp_i_opt[0],
                    "dc_iq": dc_i_opt[0],
                    "lo_freq": lo_freq
                }
                arg_dict_q["iq_params"] = {
                    "amp_iq": amp_q_opt[0],
                    "dc_iq": dc_q_opt[0],
                    "lo_freq": lo_freq
                }

                arg_dict_list = [arg_dict_i, arg_dict_q]

            else:
                arg_dict_list = [arg_dict]

            # Construct a pulse and add it to the pulseblock
            # The iteration over arg_dict takes care of the IQ mixing case
            for idx, arg_dict in enumerate(arg_dict_list):

                # Construct single pulse.
                if pb_spec.pulsetype in supported_pulses:
                    pulse = supported_pulses[pb_spec.pulsetype](**arg_dict)
                else:
                    pulse = None
                    self.log.warn(
                        f"Found an unsupported pulse type {pb_spec.pulsetype}")

                # Store the duration of the first pulse (for IQ mixing) as the
                # pb duration is modified for the second pulse.
                if idx == 0:
                    first_dur = pulse.dur
                pb_dur = pulseblock.dur

                # Insert pulse to correct position in pulseblock.
                if pb_spec.tref == "Absolute":
                    pulseblock.append_po_as_pb(p_obj=pulse,
                                               offset=offset - pb_dur)
                elif pb_spec.tref == "After Last Pulse":
                    if idx == 0:
                        pulseblock.append_po_as_pb(p_obj=pulse, offset=offset)
                    # Force the 2nd pulse to start at same time as the first
                    # pulse in an IQ mix pulse.
                    else:
                        pulseblock.append_po_as_pb(p_obj=pulse,
                                                   offset=-first_dur)
                elif pb_spec.tref == "After Last Pulse On Channel":
                    # Get the end time of the last pulse on the ch
                    ch = pb.Channel(name=arg_dict["ch"],
                                    is_analog=pulse.is_analog)
                    if ch in pulseblock.p_dict.keys():
                        last_pulse = pulseblock.p_dict[ch][-1]
                        last_pulsetime = last_pulse.t0 + last_pulse.dur
                    else:
                        last_pulsetime = 0
                    pulseblock.append_po_as_pb(p_obj=pulse,
                                               offset=last_pulsetime + offset -
                                               pb_dur)
                elif pb_spec.tref == "With Last Pulse":
                    # Retrieve previous pulseblock:
                    if i != 0:
                        previous_pb_spec = self.pulse_specifiers[i - 1]
                    else:
                        raise ValueError(
                            "Cannot chose timing reference 'With Last Pulse' for first pulse in pulse-sequence."
                        )
                    # Retrieve duration of previous pulseblock.
                    prev_dur = self.resolve_value(previous_pb_spec.dur) * 1e-6
                    if idx == 0:
                        pulseblock.append_po_as_pb(p_obj=pulse,
                                                   offset=-prev_dur + offset)
                    # Force the 2nd pulse to start at same time as the first
                    # pulse in an IQ mix pulse.
                    else:
                        pulseblock.append_po_as_pb(p_obj=pulse,
                                                   offset=-first_dur)

        self.pulseblock = pulseblock
Esempio n. 7
0
def pb_zip(pb_obj, dur_quant):
    """ Collapse default (wait) periods into repetitions of a single wait block.

    This method takes a plain PulseBlock and returns a sequence table and a list
    of individual waveforms (snippets).
    Corresponding pulse sequence reproduces the original waveform but with long
    wait periods replaced by multiple repetitions of the same short wait element
    for saving hardware memory (collapsing or zipping of a waveform).

    :param pb_obj: PulseObject instance - original pulse block to be collapsed

    :param dur_quant: (float) minimal waveform duration allowed by the hardware
    memory (in the same units as used in pb_obj).
    This value is normally determined as
        min_wfm_size / sampling_rate,
    where min_wfm_size is the minimal size of a sample array in hardware memory.
    The repeated wait waveform will have this size.

    :return: (dict)
    {
        'seq_list': list of tuples ('wfm_name', repetition_number) - chronological
        sequence of (name of the wfm snippet, how many times it should be repeated)

        'snip_list': list of PulseBlock objects - 'snippets' of the original pb_obj,
        which form the sequence according to seq_list
        Naming convention:
            pb_obj.name + '_wait' for the repeated wait wfm
            pb_obj.name + '_' + 'snip number {0, 1, ...}' for non-repeated snippets
        Order: wait is the first element, non-repeated snippets go next in
            chronological order.
    }
    """

    # Non-default intervals ---------------------------------------------------

    # find all time intervals covered by non-default pulses
    p_interval_list = []
    for ch in pb_obj.p_dict.keys():
        for pulse in pb_obj.p_dict[ch]:
            p_interval_list.append((pulse.t0, pulse.t0 + pulse.dur))

    # merge all overlapping pulse intervals
    p_interval_list = merge_intervals(i_list=p_interval_list)

    # Step usage dictionary ---------------------------------------------------

    # Now one knows all the wait periods which can be collapsed
    # (full duration of pb_obj minus all pulse intervals)
    #
    # But both the wait waveform and non-trivial pulse waveforms have to
    # meet the length constraint: dur_quant + n*dur_step. That is why it is
    # not trivial to split full duration into repetitions of wait element and
    # non-default waveforms.
    #
    # Here one uses the easiest (perhaps not the most efficient) way to meet
    # these restrictions:
    # - divide the entire duration of pb_obj into discrete steps of dur_quant
    # - construct dur_quant-long wait waveform
    # - if a given step does not contain any pulses (or any parts of pulses),
    # it will be replaced by a repetiotion of wait wfm
    # - if one or several adjacent steps contain non-default pulses, they are
    # merged and output as a single pulse block
    # - last integer dur_quant is merged with the reminder (shorter than dur_quant)
    # and is returned as a single pulse block (because the reminder is shorter
    # than minimal waveform length and cannot be sampled alone)

    # Number of whole dur_quant, fitting into pb_obj.dur
    quant_num = int(pb_obj.dur // dur_quant)

    # Use array:
    #   False - this dur_quant is a wait period
    #   True - this dur_quant is a part of pulse interval
    use_ar = np.full(shape=quant_num, fill_value=False)

    # Determine which steps contain non-default pulses
    for p_interval in p_interval_list:
        left_idx = int(p_interval[0] // dur_quant)
        right_idx = int(p_interval[1] // dur_quant)

        # Handle left-most interval
        #   If p_interval[0] == 0.0, integer division by dur_quant
        #   may accidentally evaluate to -1.0
        if left_idx < 0:
            left_idx = 0

        # Handle right-most intervals
        if left_idx == quant_num - 1 or left_idx == quant_num:
            # The entire interval fits into the 'default-True' area
            # [(quant_num - 1)-st element and reminder], so no change
            # has to be made in use_ar
            continue
        elif right_idx == quant_num:
            # The left edge of the interval is outside of the 'default-True'
            # area [(quant_num - 1)-st elem and reminder], but the right edge
            # is in the reminder. The pulse might introduce non-trivial change
            # into use_ar, but right_idx is currently out of range.
            #
            # Reduce it to the max available index to avoid out-of-range and
            # broadcasting errors:
            right_idx = quant_num - 1

        use_ar[left_idx:right_idx + 1] = np.full(shape=right_idx - left_idx +
                                                 1,
                                                 fill_value=True)
    # Last integer step of dur_quant must be considered as non-wait
    # (to be merged and sampled with the reminder, which is shorter
    # than the hardware minimum)
    use_ar[-1] = True

    # Find periods of pulses / repeating wait (perform 'run-length encoding'):

    # The return is a period dictionary:
    # {
    #   'val_ar': True or False - this run of steps is non-default or wait
    #   'len_ar': number of dur_quant steps within each run
    #   'start_ar': starting index of each run
    # }
    period_dict = run_len_encode(in_ar=use_ar)

    # Final sequence table and PulseBlock dictionary --------------------------

    final_snip_list = []
    final_seq_list = []

    # Construct 'wait' PulseBlock
    wait_pb_name = pb_obj.name + '_wait'
    wait_pb = pb.PulseBlock(name=wait_pb_name)
    wait_pb.dur = dur_quant
    wait_pb.dflt_dict = copy.deepcopy(pb_obj.dflt_dict)
    final_snip_list.append(wait_pb)

    # Iterate through all pulse runs, take 'snippet pulse blocks',
    # and construct sequence table

    # The last run (must be non-wait) will be extended to include the reminder
    last_period_idx = len(period_dict['val_ar']) - 1
    # This counter is used to keep track of PulseBlock names
    pb_name_idx = 0

    for period_idx, period_val in enumerate(period_dict['val_ar']):

        # Pulse period
        if period_val:

            # Take a snippet of this pulse period
            tmp_pb_name = pb_obj.name + '_{}'.format(pb_name_idx)

            # Snippet edges
            start_t = period_dict['start_ar'][period_idx] * dur_quant
            if period_idx < last_period_idx:
                # pulse period in the bulk - take the snippet by the period edges
                stop_t = (period_dict['start_ar'][period_idx] +
                          period_dict['len_ar'][period_idx]) * dur_quant
            else:
                # right-most pulse period must be extended to include the reminder
                stop_t = pb_obj.dur

            tmp_pb = pb_snip(pb_obj=pb_obj,
                             start_t=start_t,
                             stop_t=stop_t,
                             snip_name=tmp_pb_name,
                             use_centers=True)

            # Add the snippet to the PulseBlock list
            final_snip_list.append(tmp_pb)
            # Add corresponding entry to the sequence table:
            #   output tmp_pb_name once
            final_seq_list.append((tmp_pb_name, 1))
            # Increment name index for the next PulseBlock
            pb_name_idx += 1

        # Wait period
        else:
            # Only add an entry to the sequence table:
            #   output 'wait waveform n times'
            final_seq_list.append(
                (wait_pb_name, period_dict['len_ar'][period_idx]))

    return {'seq_list': final_seq_list, 'snip_list': final_snip_list}
Esempio n. 8
0
def pb_snip(pb_obj, start_t, stop_t, snip_name=None, use_centers=False):
    """ PulseBlock snipping tool

    Copies all default values and all pulse objects between the snippet edges
    with preserved order and spacing between them.
    The start_t and stop_t edges are the origin and the end of the returned
    PulseBlock object.

    Note that snippet edge must not cross any pulse object (but can touch it).
    If any edge crosses any pulse object, ValueError exception is produced.

    Assumptions:
    - pulse objects are ascending-time-ordered within pb_obj.p_dict[ch_name]
    - all pulse objects have non-zero duration. The algorithm will still work
     for a zero-duration pulse, but it might be ether left out or included
     into the snippet unpredictably, if it touches the edges star_t or stop_t.

    :param pb_obj: original PulseBlock
    :param start_t: (float) left snippet edge
    :param stop_t: (float) right snippet edge
    :param snip_name: (str, optional) name for the new PulseBlock object
                      If not given, pb_obj.name + '_snip' is used.
    :param use_centers: (bool) if False, belonging of a pulse to the snippet
    is determined by edges. If True - by position of the pulse central point

    :return: new PulseBlock object -
             snippet of pb_obj between start_t and stop_t
             Exception is produced in the case of error.
    """

    # New PulseBlock to be filled and returned
    new_pb = pb.PulseBlock()
    new_pb.dur = stop_t - start_t
    if snip_name is not None:
        new_pb.name = snip_name
    else:
        new_pb.name = pb_obj.name + '_snip'

    # Copy all default pulses
    new_pb.dflt_dict = copy.deepcopy(pb_obj.dflt_dict)

    # Copy all pulses falling in between start_t and stop_t
    for ch in pb_obj.p_dict.keys():
        for pulse in pb_obj.p_dict[ch]:

            # Assumption: pulses in pb_obj.p_dict[ch] are time-ordered
            # Assumption: all pulses have dur > 0.
            #   The algorithm will also work for a pulse of dur = 0,
            #   but such pulse might be left out or included unpredictably,
            #   if it touches the window edges star_t or stop_t.

            # Two ways to determine belonging of a block to the snippet:
            #
            # 1) whether the center of a pulse falls into [start_t, stop_t]
            # interval.
            #   Pros: robust against float-comparison errors if the pulse
            #     touches snippet edge.
            #   Cons: This method does not detect the cases when snippet
            #     edges go across a pulse block.
            #
            # 2) whether both edges of a pulse fall into [start_t, stop_t]
            #   Pros: checks for snippet edges crossing the pulse
            #   Cons: may unpredictably produce exceptions if one of the pulses
            #       touches snippet edge (due to unpredictable comparison result
            #       for two nominally identical floats)

            # Method 1: use pulse center
            if use_centers:
                pulse_center = pulse.t0 + pulse.dur / 2
                if pulse_center <= start_t:
                    # pulse is fully to the left from snip window
                    continue

                elif start_t <= pulse_center <= stop_t:
                    # pulse is fully inside the snip window

                    if ch not in new_pb.p_dict.keys():
                        # the first time the pulse is added to this channel
                        new_pb.p_dict[ch] = []

                    pulse_copy = copy.deepcopy(pulse)
                    pulse_copy.t0 -= start_t
                    new_pb.p_dict[ch].append(pulse_copy)

                else:
                    # This pulse and all subsequent ones lie to the right
                    # from the snip window
                    break

            # Method 2: use pulse edges
            else:
                if pulse.t0 + pulse.dur <= start_t:
                    # pulse is fully to the left from snip window
                    continue

                elif start_t <= pulse.t0 and pulse.t0 + pulse.dur <= stop_t:
                    # pulse is fully inside the snip window

                    if ch not in new_pb.p_dict.keys():
                        # the first time the pulse is added to this channel
                        new_pb.p_dict[ch] = []

                    pulse_copy = copy.deepcopy(pulse)
                    pulse_copy.t0 -= start_t
                    new_pb.p_dict[ch].append(pulse_copy)

                elif stop_t <= pulse.t0:
                    # This pulse and all subsequent ones lie to the right
                    # from the snip window
                    break

                # The window edge goes across one of the pulse objects.
                # Since it is not clear what to do with such a pulse,
                # one has to raise an exception.
                # Alternatively, it might be some unexpected comparison error.
                else:
                    # Determine the conflicting edge
                    if pulse.t0 < start_t < pulse.t0 + pulse.dur:
                        edge_type = 'start_t'
                        edge_t = start_t

                    elif pulse.t0 < stop_t < pulse.t0 + pulse.dur:
                        edge_type = 'stop_t'
                        edge_t = stop_t

                    # Something completely unexpected:
                    # if it were just a crossing with an edge,
                    # one of the above conditions should have been satisfied.
                    # Hence, this part is reached only in the case of some
                    # unexpected error.
                    #
                    # Just raise an exception and provide all the information
                    else:
                        raise ValueError(
                            'pb_snip(): condition check failed: \n'
                            '   pulse_obj = {} \n'
                            '   t0 = {} \n'
                            '   dur = {} \n'
                            '   start_t = {} \n'
                            '   stop_t = {}'
                            ''.format(pulse, pulse.t0, pulse.dur, start_t,
                                      stop_t))

                    raise ValueError(
                        'pb_snip(): snip edge goes across a pulse object \n'
                        '   channel = {} \n'
                        '   t0 = {} \n'
                        '   t0+dur = {} \n'
                        '   pulse object = {} \n'
                        '   conflicting edge = {} \n'
                        '   edge position = {}'
                        ''.format(ch, pulse.t0, pulse.t0 + pulse.dur,
                                  str(pulse), edge_type, edge_t))

    return new_pb
Esempio n. 9
0
    def compile_pulseblock(self):
        """ Compiles the list of pulse_specifiers and var dists into valid
        Pulseblock.
        """

        pulseblock = pb.PulseBlock(name=self.name)

        for i, pb_spec in enumerate(self.pulse_specifiers):

            var_dict = pb_spec.pulsevar_dict
            arg_dict = {}

            # Extract parameters from the pulsevar dict
            offset = self.resolve_value(pb_spec.offset) * 1e-6
            arg_dict["ch"] = pb_spec.channel
            arg_dict["dur"] = self.resolve_value(pb_spec.dur) * 1e-6

            self.append_value_to_dict(var_dict, "val", arg_dict)
            self.append_value_to_dict(var_dict, "amp", arg_dict)
            self.append_value_to_dict(var_dict, "freq", arg_dict)
            self.append_value_to_dict(var_dict, "ph", arg_dict)
            self.append_value_to_dict(var_dict,
                                      "stdev",
                                      arg_dict,
                                      fn=lambda x: 1e-6 * x)
            self.append_value_to_dict(var_dict, "iq", arg_dict)
            self.append_value_to_dict(var_dict, "mod", arg_dict)
            self.append_value_to_dict(var_dict, "mod_freq", arg_dict)
            self.append_value_to_dict(var_dict, "mod_ph", arg_dict)

            supported_pulses = {
                "PTrue": po.PTrue,
                "PSin": po.PSin,
                "PGaussian": po.PGaussian,
                "PConst": po.PConst
            }

            # Handle IQ mixing case
            if "iq" in arg_dict and arg_dict["iq"]:

                (if_freq, lo_freq, phase_opt, amp_i_opt, amp_q_opt, dc_i_opt,
                 dc_q_opt
                 ) = self.iq_calibration.get_optimal_hdawg_and_LO_values(
                     arg_dict["mod_freq"])

                self.log.info(f"if={if_freq}, lo={lo_freq}, phase={phase_opt}")

                # Store the optimal IQ parameters as 2 separate dictionaries
                arg_dict_i = copy.deepcopy(arg_dict)
                arg_dict_q = copy.deepcopy(arg_dict)

                # Modify the channel names
                arg_dict_i["ch"] = arg_dict["ch"] + "_i"
                arg_dict_q["ch"] = arg_dict["ch"] + "_q"

                # Modulation frequency changed to IF
                arg_dict_i["mod_freq"] = if_freq
                arg_dict_q["mod_freq"] = if_freq

                # Relative phase
                arg_dict_i["mod_ph"] = arg_dict["mod_ph"] + phase_opt[0]

                # The amplitude is the amplitude of the Sin genarator and is
                # indepenent of ["amp"], the signal amplitude.
                arg_dict_i["iq_params"] = {
                    "amp_iq": amp_i_opt[0],
                    "dc_iq": dc_i_opt[0],
                    "lo_freq": lo_freq
                }
                arg_dict_q["iq_params"] = {
                    "amp_iq": amp_q_opt[0],
                    "dc_iq": dc_q_opt[0],
                    "lo_freq": lo_freq
                }

                arg_dict_list = [arg_dict_i, arg_dict_q]

            else:
                arg_dict_list = [arg_dict]

            # Construct a pulse and add it to the pulseblock
            # The iteration over arg_dict takes care of the IQ mixing case
            # idx = 0 is the I portion, idx = 1 is the Q portion.
            for idx, arg_dict in enumerate(arg_dict_list):

                # Construct single pulse.
                if pb_spec.pulsetype in supported_pulses:
                    pulse = supported_pulses[pb_spec.pulsetype](**arg_dict)
                else:
                    pulse = None
                    self.log.warn(
                        f"Found an unsupported pulse type {pb_spec.pulsetype}")

                pb_dur = pulseblock.dur
                prev_t0 = pulseblock.latest_t0
                prev_dur = pulseblock.latest_dur

                # idx = 0 refers to the I pulse (or a normal non-IQ pulse)
                if idx == 0:
                    # CASE 1
                    if pb_spec.tref == "Absolute":
                        pulseblock.append_po_as_pb(p_obj=pulse,
                                                   offset=-pb_dur + offset)

                    # CASE 2
                    elif pb_spec.tref in (
                            "After Last Pulse", "At End of Sequence"
                    ):  # For compatbility with previous naming
                        pulseblock.append_po_as_pb(p_obj=pulse, offset=offset)

                    # CASE 3
                    elif pb_spec.tref in (
                            "With Last Pulse", "With Previous Pulse"
                    ):  # For compatbility with previous naming
                        # Take timing reference based on the last pulse's t0
                        pulseblock.append_po_as_pb(p_obj=pulse,
                                                   offset=-pb_dur + prev_t0 +
                                                   offset)

                    # CASE 4
                    elif pb_spec.tref == "After Previous Pulse":
                        # Take timing reference based on the last pulse's t0 and duration
                        pulseblock.append_po_as_pb(p_obj=pulse,
                                                   offset=-pb_dur + prev_t0 +
                                                   prev_dur + offset)

                    # CASE 5
                    elif pb_spec.tref == "After Last Pulse On Channel":
                        # Get the end time of the last pulse on the ch
                        ch = pb.Channel(name=arg_dict["ch"],
                                        is_analog=pulse.is_analog)
                        if ch in pulseblock.p_dict.keys():
                            last_pulse = pulseblock.p_dict[ch][-1]
                            last_pulsetime = last_pulse.t0 + last_pulse.dur
                        else:
                            last_pulsetime = 0

                        pulseblock.append_po_as_pb(p_obj=pulse,
                                                   offset=-pb_dur +
                                                   last_pulsetime + offset)

                else:
                    # idx = 1 here (Q pulse)
                    # Force the 2nd pulse to start at same time as the first
                    # pulse in an IQ mix pulse. Note that prev_t0 is the t0 of
                    # the I pulse since this is executed right after the I pulse.
                    pulseblock.append_po_as_pb(p_obj=pulse,
                                               offset=-pb_dur + prev_t0)

        self.pulseblock = pulseblock
        self.clean_pulseblock_timings()