def _render(self, sample_rate, ref_channel_states): ''' make a full rendering of the waveform at a predermined sample rate. ''' # express in Gs/s sample_rate = sample_rate * 1e-9 t_tot = self.total_time # get number of points that need to be rendered t_tot_pt = iround(t_tot * sample_rate) + 1 my_sequence = np.zeros(t_tot_pt) for data_points in self.my_marker_data: start = iround(data_points.start * sample_rate) stop = iround(data_points.stop * sample_rate) my_sequence[start:stop] = 1 * self.pulse_amplitude return my_sequence[:-1]
def _generate_digitizer_sequences(self, job): for name, value in job.schedule_params.items(): if name.startswith('dig_trigger_') or name.startswith('dig_wait'): raise Exception('HVI triggers not supported with QS') pxi_triggers = {} for seg in job.sequence: if isinstance(seg, conditional_segment): acq_names = get_acquisition_names(seg) pxi = 6 for acq in acq_names: pxi_triggers[acq] = pxi pxi += 1 logging.debug(f'PXI triggers: {pxi_triggers}') offset = int(self.max_pre_start_ns) segments = self.segments for channel_name, channel in self.digitizer_channels.items(): t_start = -offset sequence = AcquisitionSequenceBuilder(channel_name, t_start) job.digitizer_sequences[channel_name] = sequence for iseg, (seg, seg_render) in enumerate(zip(job.sequence, segments)): if isinstance(seg, conditional_segment): logging.debug(f'conditional for {channel_name}') # TODO @@@@ lookup acquisitions and set pxi trigger. seg_ch = get_conditional_channel(seg, channel_name) else: seg_ch = seg[channel_name] acquisition_data = seg_ch._get_data_all_at(job.index).get_data() for acquisition in acquisition_data: t_acq = seg_render.t_start + acquisition.start if channel.downsample_rate is not None: period_ns = iround(1e8/channel.downsample_rate) * 10 n_cycles = int(acquisition.t_measure / period_ns) t_integrate = period_ns else: t_integrate = acquisition.t_measure n_cycles = 1 pxi_trigger = pxi_triggers.get(str(acquisition.ref), None) sequence.acquire(t_acq, t_integrate, n_cycles, threshold=acquisition.threshold, pxi_trigger=pxi_trigger) logging.debug(f'Acq: {acquisition.ref}: {pxi_trigger}') sequence.close()
def _set_channel_raw(self, data, index): digitizer_channels = self._description.digitizer_channels # TODO @@@ works only for 1 digitzer # FIX @@@ this numbering assumes that the channels in the pulselib configuration is equal to # the active channels of the digitizer. Unfortunately, this doesn't have to be true. # get digitizer parameter result numbering output_channels = [] for channel in digitizer_channels.values(): acquisitions = self._get_acquisitions(channel.name, index) if len(acquisitions) > 0: output_channels += channel.channel_numbers output_channels.sort() # set raw values self._channel_raw = {} for channel in digitizer_channels.values(): acquisitions = self._get_acquisitions(channel.name, index) if len(acquisitions) == 0: self._channel_raw[channel.name] = np.zeros( 0, dtype=np.complex if channel.iq_out else np.float) continue if isinstance(channel, digitizer_channel): # this can be complex valued output with LO modulation or phase shift in digitizer (FPGA) ch = output_channels.index(channel.channel_number) ch_raw = data[ch] elif isinstance(channel, digitizer_channel_iq): ch_I = output_channels.index(channel.channel_numbers[0]) ch_Q = output_channels.index(channel.channel_numbers[1]) ch_raw = (data[ch_I] + 1j * data[ch_Q]) * np.exp( 1j * channel.phase) else: raise NotImplementedError( f'Unknown channel type {type(channel)}') if not channel.iq_out: ch_raw = ch_raw.real if channel.downsample_rate is None: self._channel_raw[channel.name] = ch_raw.reshape( (-1, len(acquisitions))).T else: period_ns = iround(1e8 / channel.downsample_rate) * 10 n_samples = sum( int(acq.t_measure / period_ns) for acq in acquisitions) self._channel_raw[channel.name] = ch_raw.reshape( (-1, n_samples)).T
def render_MW_and_custom(self, sample_rate, ref_channel_states): ''' Render MW pulses and custom data in 'rendered_elements'. ''' elements = [] self._pre_process() # express in Gs/s sample_rate = sample_rate * 1e-9 # render MW pulses. # create list with phase shifts per ref_channel phase_shifts_channels = {} for ps in self.phase_shifts: ps_ch = phase_shifts_channels.setdefault(ps.channel_name, []) ps_ch.append(ps) for IQ_data_single_object in self.MW_pulse_data: # start stop time of MW pulse start_pulse = IQ_data_single_object.start stop_pulse = IQ_data_single_object.stop # max amp, freq and phase. amp = IQ_data_single_object.amplitude freq = IQ_data_single_object.frequency phase = IQ_data_single_object.start_phase if ref_channel_states and IQ_data_single_object.ref_channel in ref_channel_states.start_phase: ref_start_time = ref_channel_states.start_time ref_start_phase = ref_channel_states.start_phase[ IQ_data_single_object.ref_channel] phase_shift = 0 if IQ_data_single_object.ref_channel in phase_shifts_channels: for ps in phase_shifts_channels[ IQ_data_single_object.ref_channel]: if ps.time <= start_pulse: phase_shift += ps.phase_shift else: ref_start_time = 0 ref_start_phase = 0 phase_shift = 0 # envelope data of the pulse if IQ_data_single_object.envelope is None: IQ_data_single_object.envelope = envelope_generator() amp_envelope = IQ_data_single_object.envelope.get_AM_envelope( (stop_pulse - start_pulse), sample_rate) phase_envelope = IQ_data_single_object.envelope.get_PM_envelope( (stop_pulse - start_pulse), sample_rate) #self.baseband_pulse_data[-1,0] convert to point numbers n_pt = int((stop_pulse - start_pulse) * sample_rate) if isinstance( amp_envelope, float) else len(amp_envelope) start_pt = iround(start_pulse * sample_rate) stop_pt = start_pt + n_pt # add the sin pulse total_phase = phase_shift + phase + phase_envelope + ref_start_phase t = start_pt + ref_start_time / sample_rate + np.arange(n_pt) wvf = amp * amp_envelope * np.sin(2 * np.pi * freq / sample_rate * 1e-9 * t + total_phase) elements.append(rendered_element(start_pt, stop_pt, wvf)) for custom_pulse in self.custom_pulse_data: wvf = self._render_custom_pulse(custom_pulse, sample_rate * 1e9) start_pt = iround(custom_pulse.start * sample_rate) stop_pt = start_pt + len(wvf) elements.append(rendered_element(start_pt, stop_pt, wvf)) return self._merge_elements(elements)
def _render(self, sample_rate, ref_channel_states): ''' make a full rendering of the waveform at a predetermined sample rate. ''' self._pre_process() # express in Gs/s sample_rate = sample_rate * 1e-9 t_tot = self.total_time # get number of points that need to be rendered t_tot_pt = iround(t_tot * sample_rate) + 1 wvf = np.zeros([int(t_tot_pt)]) t_pt = iround(self._times * sample_rate) for i in range(len(t_pt) - 1): pt0 = t_pt[i] pt1 = t_pt[i + 1] if pt0 != pt1: if self._ramps[i] != 0: wvf[pt0:pt1] = np.linspace(self._amplitudes[i], self._amplitudes_end[i + 1], pt1 - pt0 + 1)[:-1] else: wvf[pt0:pt1] = self._amplitudes[i] # render MW pulses. # create list with phase shifts per ref_channel phase_shifts_channels = {} for ps in self.phase_shifts: ps_ch = phase_shifts_channels.setdefault(ps.channel_name, []) ps_ch.append(ps) for IQ_data_single_object in self.MW_pulse_data: # start stop time of MW pulse start_pulse = IQ_data_single_object.start stop_pulse = IQ_data_single_object.stop # max amp, freq and phase. amp = IQ_data_single_object.amplitude freq = IQ_data_single_object.frequency phase = IQ_data_single_object.start_phase if ref_channel_states and IQ_data_single_object.ref_channel in ref_channel_states.start_phase: ref_start_time = ref_channel_states.start_time ref_start_phase = ref_channel_states.start_phase[ IQ_data_single_object.ref_channel] if IQ_data_single_object.ref_channel in phase_shifts_channels: phase_shifts = [ ps.phase_shift for ps in phase_shifts_channels[ IQ_data_single_object.ref_channel] if ps.time <= start_pulse ] phase_shift = sum(phase_shifts) else: phase_shift = 0 else: ref_start_time = 0 ref_start_phase = 0 phase_shift = 0 # envelope data of the pulse if IQ_data_single_object.envelope is None: IQ_data_single_object.envelope = envelope_generator() amp_envelope = IQ_data_single_object.envelope.get_AM_envelope( (stop_pulse - start_pulse), sample_rate) phase_envelope = np.asarray( IQ_data_single_object.envelope.get_PM_envelope( (stop_pulse - start_pulse), sample_rate)) #self.baseband_pulse_data[-1,0] convert to point numbers n_pt = int((stop_pulse - start_pulse) * sample_rate) if isinstance( amp_envelope, float) else len(amp_envelope) start_pt = iround(start_pulse * sample_rate) stop_pt = start_pt + n_pt # add the sin pulse total_phase = phase_shift + phase + phase_envelope + ref_start_phase t = start_pt + ref_start_time / sample_rate + np.arange(n_pt) wvf[start_pt:stop_pt] += amp * amp_envelope * np.sin( 2 * np.pi * freq / sample_rate * 1e-9 * t + total_phase) for custom_pulse in self.custom_pulse_data: data = self._render_custom_pulse(custom_pulse, sample_rate * 1e9) start_pt = iround(custom_pulse.start * sample_rate) stop_pt = start_pt + len(data) wvf[start_pt:stop_pt] += data # remove last value. t_tot_pt = t_tot + 1. Last value is always 0. It is only needed in the loop on the pulses. return wvf[:-1]
def _generate_upload_wvf(self, job, awg_upload_func): segments = self.segments sections = job.upload_info.sections ref_channel_states = RefChannels(0) # loop over all qubit channels to accumulate total phase shift for i in range(len(job.sequence)): ref_channel_states.start_phases_all.append(dict()) for channel_name, qubit_channel in self.qubit_channels.items(): if (QsUploader.use_iq_sequencers and channel_name in self.sequencer_channels): # skip IQ sequencer channels continue phase = 0 for iseg,seg in enumerate(job.sequence): ref_channel_states.start_phases_all[iseg][channel_name] = phase seg_ch = seg[channel_name] phase += seg_ch.get_accumulated_phase(job.index) for channel_name, channel_info in self.channels.items(): if (QsUploader.use_iq_sequencers and channel_name in self.sequencer_out_channels): # skip IQ sequencer channels continue if (QsUploader.use_baseband_sequencers and channel_name in self.sequencer_channels): # skip baseband sequencer channels continue section = sections[0] buffer = np.zeros(section.npt) bias_T_compensation_mV = 0 for iseg,(seg,seg_render) in enumerate(zip(job.sequence,segments)): sample_rate = seg_render.sample_rate n_delay = iround(channel_info.delay_ns * sample_rate) if isinstance(seg, conditional_segment): logging.debug(f'conditional for {channel_name}') seg_ch = get_conditional_channel(seg, channel_name) else: seg_ch = seg[channel_name] ref_channel_states.start_time = seg_render.t_start ref_channel_states.start_phase = ref_channel_states.start_phases_all[iseg] start = time.perf_counter() #print(f'start: {channel_name}.{iseg}: {ref_channel_states.start_time}') wvf = seg_ch.get_segment(job.index, sample_rate*1e9, ref_channel_states) duration = time.perf_counter() - start logging.debug(f'generated [{job.index}]{iseg}:{channel_name} {len(wvf)} Sa, in {duration*1000:6.3f} ms') if len(wvf) != seg_render.npt: logging.warn(f'waveform {iseg}:{channel_name} {len(wvf)} Sa <> sequence length {seg_render.npt}') i_start = 0 if seg_render.start_section: if section != seg_render.start_section: logging.error(f'OOPS section mismatch {iseg}, {channel_name}') # add n_start_transition - n_delay to start_section # n_delay_welding = iround(channel_info.delay_ns * section.sample_rate) t_welding = (section.t_end - seg_render.t_start) i_start = iround(t_welding*sample_rate) - n_delay n_section = iround(t_welding*section.sample_rate) + iround(-channel_info.delay_ns * section.sample_rate) if n_section > 0: if iround(n_section*sample_rate/section.sample_rate) >= len(wvf): raise Exception(f'segment {iseg} too short for welding. (nwelding:{n_section}, len_wvf:{len(wvf)})') isub = [iround(i*sample_rate/section.sample_rate) for i in np.arange(n_section)] welding_samples = np.take(wvf, isub) buffer[-n_section:] = welding_samples bias_T_compensation_mV = self._add_bias_T_compensation(buffer, bias_T_compensation_mV, section.sample_rate, channel_info) self._upload_wvf(job, channel_name, buffer, channel_info.amplitude, channel_info.attenuation, section.sample_rate, awg_upload_func) section = seg_render.section buffer = np.zeros(section.npt) if seg_render.end_section: next_section = seg_render.end_section # add n_end_transition + n_delay to next section. First complete this section n_delay_welding = iround(channel_info.delay_ns * section.sample_rate) t_welding = (seg_render.t_end - next_section.t_start) i_end = len(wvf) - iround(t_welding*sample_rate) + n_delay_welding if i_start != i_end: buffer[-(i_end-i_start):] = wvf[i_start:i_end] bias_T_compensation_mV = self._add_bias_T_compensation(buffer, bias_T_compensation_mV, section.sample_rate, channel_info) self._upload_wvf(job, channel_name, buffer, channel_info.amplitude, channel_info.attenuation, section.sample_rate, awg_upload_func) section = next_section buffer = np.zeros(section.npt) n_section = iround(t_welding*section.sample_rate) + iround(channel_info.delay_ns * section.sample_rate) if iround(n_section*sample_rate/section.sample_rate) >= len(wvf): raise Exception(f'segment {iseg} too short for welding. (nwelding:{n_section}, len_wvf:{len(wvf)})') isub = [min(len(wvf)-1, i_end + iround(i*sample_rate/section.sample_rate)) for i in np.arange(n_section)] welding_samples = np.take(wvf, isub) buffer[:n_section] = welding_samples else: if section != seg_render.section: logging.error(f'OOPS-2 section mismatch {iseg}, {channel_name}') offset = seg_render.offset + n_delay buffer[offset+i_start:offset + len(wvf)] = wvf[i_start:] if job.neutralize: if section != sections[-1]: # DC compensation is in a separate section bias_T_compensation_mV = self._add_bias_T_compensation(buffer, bias_T_compensation_mV, section.sample_rate, channel_info) self._upload_wvf(job, channel_name, buffer, channel_info.amplitude, channel_info.attenuation, section.sample_rate, awg_upload_func) section = sections[-1] buffer = np.zeros(section.npt) logging.info(f'DC compensation section with {section.npt} Sa') compensation_npt = iround(job.upload_info.dc_compensation_duration * section.sample_rate) if compensation_npt > 0 and channel_info.dc_compensation: compensation_voltage = -channel_info.integral * section.sample_rate / compensation_npt * 1e9 job.upload_info.dc_compensation_voltages[channel_name] = compensation_voltage buffer[-(compensation_npt+1):-1] = compensation_voltage logging.debug(f'DC compensation {channel_name}: {compensation_voltage:6.1f} mV {compensation_npt} Sa') else: job.upload_info.dc_compensation_voltages[channel_name] = 0 bias_T_compensation_mV = self._add_bias_T_compensation(buffer, bias_T_compensation_mV, section.sample_rate, channel_info) self._upload_wvf(job, channel_name, buffer, channel_info.amplitude, channel_info.attenuation, section.sample_rate, awg_upload_func)
def _generate_sections(self, job): max_pre_start_ns = self.max_pre_start_ns max_post_end_ns = self.max_post_end_ns self.segments = [] segments = self.segments t_start = 0 for seg in job.sequence: # work with sample rate in GSa/s sample_rate = (seg.sample_rate if seg.sample_rate is not None else job.default_sample_rate) * 1e-9 duration = seg.get_total_time(job.index) npt = iround(duration * sample_rate) info = SegmentRenderInfo(sample_rate, t_start, npt) segments.append(info) t_start = info.t_end # sections sections = job.upload_info.sections t_start = -max_pre_start_ns nseg = len(segments) section = RenderSection(segments[0].sample_rate, t_start) sections.append(section) section.npt += iround(max_pre_start_ns * section.sample_rate) for iseg,seg in enumerate(segments): sample_rate = seg.sample_rate if iseg < nseg-1: sample_rate_next = segments[iseg+1].sample_rate else: sample_rate_next = 0 # create welding region if sample_rate decreases if sample_rate < section.sample_rate: # welding region is length of padding for alignment + post_stop region n_post = iround(((seg.t_start + max_post_end_ns) - section.t_end) * section.sample_rate) section.npt += n_post section.align(extend=True) # number of points of segment to be rendered to previous section n_start_transition = iround((section.t_end - seg.t_start)*sample_rate) seg.n_start_transition = n_start_transition seg.start_section = section # start new section section = RenderSection(sample_rate, section.t_end) sections.append(section) section.npt -= n_start_transition seg.section = section seg.offset = section.npt section.npt += seg.npt # create welding region if sample rate increases if sample_rate_next != 0 and sample_rate_next > sample_rate: # The current section should end before the next segment starts: # - subtract any extension into the next segment # - align boundary with truncation n_pre = int(np.ceil((section.t_end - (seg.t_end - max_pre_start_ns)) * section.sample_rate)) section.npt -= n_pre section.align(extend=False) # start new section section = RenderSection(sample_rate_next, section.t_end) sections.append(section) # number of points of segment to be rendered to next section n_end_transition = iround((seg.t_end - section.t_start)*sample_rate_next) section.npt += n_end_transition seg.n_end_transition = n_end_transition seg.end_section = section # add post stop samples; seg = last segment, section is last section n_post = iround(((seg.t_end + max_post_end_ns) - section.t_end) * section.sample_rate) section.npt += n_post # add DC compensation compensation_time = self.get_max_compensation_time() logging.debug(f'DC compensation time: {compensation_time*1e9} ns') compensation_npt = int(np.ceil(compensation_time * section.sample_rate * 1e9)) if compensation_npt > 50_000: # more than 50_000 samples? Use new segment with lower sample rate for compensation sample_rate = 1e9 * section.sample_rate * 5_000 / compensation_npt # find an existing sample rate nice_sample_rates = [1e6, 2e6, 5e6, 1e7, 2e7, 5e7, 1e8, 2e8, 1e9] for sr in nice_sample_rates: if sample_rate <= sr: sample_rate = sr * 1e-9 break # create new section section.align(extend=True) section = RenderSection(sample_rate, section.t_end) sections.append(section) # calculate npt compensation_npt = int(np.ceil(compensation_time * section.sample_rate * 1e9)) logging.info(f'Added new segment for DC compensation: {int(compensation_time*1e9)} ns, ' f'sample_rate: {sr/1e6} MHz, {compensation_npt} Sa') job.upload_info.dc_compensation_duration = compensation_npt/section.sample_rate section.npt += compensation_npt # add at least 1 zero section.npt += 1 section.align(extend=True) job.playback_time = section.t_end - sections[0].t_start job.n_waveforms = len(sections) logging.debug(f'Playback time: {job.playback_time} ns') if UploadAggregator.verbose: for segment in segments: logging.info(f'segment: {segment}') for section in sections: logging.info(f'section: {section}')