diff --git a/pulse_lib/keysight/M3202A_uploader.py b/pulse_lib/keysight/M3202A_uploader.py index af7e463ff29a3261ba26e4b2e14df89911c37dfe..b8df556e7878a805f1601068ab96fa26ae7b7996 100644 --- a/pulse_lib/keysight/M3202A_uploader.py +++ b/pulse_lib/keysight/M3202A_uploader.py @@ -6,7 +6,7 @@ import math from collections import defaultdict from dataclasses import dataclass, field from typing import List, Dict, Optional -from pulse_lib.uploader.uploader_funcs import merge_markers +from pulse_lib.uploader.uploader_funcs import merge_markers, get_sample_rate logger = logging.getLogger(__name__) @@ -587,7 +587,7 @@ class UploadAggregator: return for iseg,seg in enumerate(job.sequence): - sample_rate = seg.sample_rate if seg.sample_rate is not None else job.default_sample_rate + sample_rate = get_sample_rate(job, seg) for channel_name, channel_info in self.channels.items(): if iseg == 0: @@ -608,7 +608,7 @@ class UploadAggregator: 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 + sample_rate = get_sample_rate(job, seg) * 1e-9 duration = seg.get_total_time(job.index) logger.debug(f'Seg duration:{duration:9.3f}') npt = iround(duration * sample_rate) diff --git a/pulse_lib/keysight/qs_uploader.py b/pulse_lib/keysight/qs_uploader.py index 9a44ac20ac963c46e6c72e00b261ae4a24f08229..d5c8b510c27c253324295a68a4237f9dfc0087b5 100644 --- a/pulse_lib/keysight/qs_uploader.py +++ b/pulse_lib/keysight/qs_uploader.py @@ -16,7 +16,8 @@ from pulse_lib.segments.conditional_segment import conditional_segment from pulse_lib.tests.mock_m3202a_qs import AwgInstruction, AwgConditionalInstruction from pulse_lib.tests.mock_m3102a_qs import DigitizerInstruction from pulse_lib.segments.utility.rounding import iround -from pulse_lib.uploader.uploader_funcs import get_iq_nco_idle_frequency, merge_markers +from pulse_lib.uploader.uploader_funcs import ( + get_iq_nco_idle_frequency, merge_markers, get_sample_rate) logger = logging.getLogger(__name__) @@ -696,7 +697,7 @@ class UploadAggregator: return for iseg,seg in enumerate(job.sequence): - sample_rate = seg.sample_rate if seg.sample_rate is not None else job.default_sample_rate + sample_rate = get_sample_rate(job, seg) for channel_name, channel_info in self.channels.items(): if iseg == 0: @@ -720,7 +721,7 @@ class UploadAggregator: 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 + sample_rate = get_sample_rate(job, seg) * 1e-9 duration = seg.get_total_time(job.index) npt = iround(duration * sample_rate) info = SegmentRenderInfo(sample_rate, t_start, npt) diff --git a/pulse_lib/sequencer.py b/pulse_lib/sequencer.py index e0f9bfe93e8e04ca168e53062e11ca5e67c28c61..a289b7478b77c2f7ed8d09b07ac9bf31ebc12eb3 100644 --- a/pulse_lib/sequencer.py +++ b/pulse_lib/sequencer.py @@ -177,11 +177,11 @@ class sequencer(): raise ValueError('The provided element in the sequence seems to be of the wrong data type.' f'{type(entry)} provided, segment_container expected') - for seg_container in self.sequence: - if seg_container.sample_rate is not None: - effective_rate = self.uploader.get_effective_sample_rate(seg_container.sample_rate) - msg = f"effective sampling rate for {seg_container.name} is set to {si_format(effective_rate, precision=1)}Sa/s" - logger.info(msg) +# for seg_container in self.sequence: +# if seg_container.sample_rate is not None: +# effective_rate = self.uploader.get_effective_sample_rate(seg_container.sample_rate) +# msg = f"effective sampling rate for {seg_container.name} is set to {si_format(effective_rate, precision=1)}Sa/s" +# logger.info(msg) # update dimensionality of all sequence objects start = time.perf_counter() @@ -197,7 +197,7 @@ class sequencer(): n_samples = 0 for seg_container in self.sequence: sr = seg_container.sample_rate if seg_container.sample_rate else 1e9 - n_samples = max(n_samples, np.max(seg_container.total_time) * 1e9 / sr) + n_samples = max(n_samples, np.max(seg_container.total_time) * 1e9 / np.max(sr)) if not isinstance(seg_container, conditional_segment): for channel_name in seg_container.channels: shape = seg_container[channel_name].data.shape diff --git a/pulse_lib/tests/looping/test_segment_sample_rate.py b/pulse_lib/tests/looping/test_segment_sample_rate.py new file mode 100644 index 0000000000000000000000000000000000000000..0f1547f7c7ebe5886fa44e940c78612624622a1d --- /dev/null +++ b/pulse_lib/tests/looping/test_segment_sample_rate.py @@ -0,0 +1,44 @@ + +from pulse_lib.tests.configurations.test_configuration import context + +#%% +import pulse_lib.segments.utility.looping as lp +import numpy as np + + +def get_min_sample_rate(duration): + # Keysight minimum 2000 samples in segment, + # but due to segment 'welding' the segment should be a bit longer + # at least 2000 ns at start and 10 samples at end. + for sr in [1e6, 2e6, 5e6, 1e7, 2e7, 5e7, 1e8]: + if (duration-2000) * 1e-9 * sr > 2000 + 10: + return sr + return 1e9 + + +def test(): + pulse = context.init_pulselib(n_gates=2) + + t_wait = lp.geomspace(1000, 100000, 5, 't_wait', unit='ns', axis=0) + + s = pulse.mk_segment() + + calc_sr = np.frompyfunc(get_min_sample_rate, 1, 1) + sr = calc_sr(t_wait) + # print(sr) + s.sample_rate = sr + + s.P1.add_block(0, 100, 80.0) + s.wait(t_wait, reset_time=True) + s.P1.add_block(0, 100, 80.0) + + sequence = pulse.mk_sequence([s]) + context.add_hw_schedule(sequence) + for t in sequence.t_wait.values: + sequence.t_wait(t) + context.plot_awgs(sequence, ylim=(-0.100,0.100)) + + return None + +if __name__ == '__main__': + ds = test() diff --git a/pulse_lib/uploader/uploader_funcs.py b/pulse_lib/uploader/uploader_funcs.py index 312aeeb2b8daee124b8b82ce00611974317080b1..b44d149de66314d32f1e1e2174f2012188c4cb5b 100644 --- a/pulse_lib/uploader/uploader_funcs.py +++ b/pulse_lib/uploader/uploader_funcs.py @@ -1,4 +1,5 @@ from typing import List, Tuple +from numbers import Number import logging import numpy as np from pulse_lib.segments.utility.looping import loop_obj @@ -6,6 +7,7 @@ from pulse_lib.configuration.iq_channels import FrequencyUndefined logger = logging.getLogger(__name__) + def get_iq_nco_idle_frequency(job, qubit_channel, index): ''' Returns IQ / NCO frequency to use between pulses. @@ -29,6 +31,7 @@ def get_iq_nco_idle_frequency(job, qubit_channel, index): return None return frequency - qubit_channel.iq_channel.LO + def merge_markers(marker_name, marker_deltas, marker_value=1, min_off_ns=10) -> List[Tuple[int,int]]: ''' Merge overlapping markers. @@ -62,3 +65,15 @@ def merge_markers(marker_name, marker_deltas, marker_value=1, min_off_ns=10) -> return res + +def get_sample_rate(job, segment): + sample_rate = (segment.sample_rate + if segment.sample_rate is not None + else job.default_sample_rate) + + if not isinstance(sample_rate, Number): + # assume looping object. + lp_sample_rate = sample_rate + index = tuple(job.index[axis] for axis in lp_sample_rate.axis) + sample_rate = sample_rate[index] + return sample_rate