def __init__(self, program: Loop, channels: Sequence[ChannelID], markers: Sequence[Tuple[ChannelID, ChannelID]], sample_rate: TimeType, amplitudes: Sequence[float], voltage_transformations: Sequence[Callable], offsets: Sequence[float] = None): assert len(channels) == len(markers) and all(len(marker) == 2 for marker in markers),\ "Driver can currently only handle awgs wth two markers per channel" assert len(channels) == len(amplitudes) self._program = program.copy_tree_structure() self._sample_rate = sample_rate self._amplitudes = tuple(amplitudes) self._offsets = tuple(offsets) if offsets is not None else None self._channels = tuple(channels) self._markers = tuple(markers) self._voltage_transformations = tuple(voltage_transformations) self._sequencing_elements = None self._waveforms = None make_compatible(self._program, 250, 1, sample_rate / 10**9) self._program.flatten_and_balance(1) self._sequencing_elements, self._waveforms = parse_program(program=self._program, channels=self.channels, markers=self.markers, sample_rate=self._sample_rate, amplitudes=self._amplitudes, voltage_transformations=self._voltage_transformations, offsets=self._offsets)
def test_make_compatible(self): program = Loop() pub_kwargs = dict(minimal_waveform_length=5, waveform_quantum=10, sample_rate=time_from_float(1.)) priv_kwargs = dict(min_len=5, quantum=10, sample_rate=time_from_float(1.)) with mock.patch( 'qupulse._program._loop._is_compatible', return_value=_CompatibilityLevel.incompatible) as mocked: with self.assertRaisesRegex(ValueError, 'cannot be made compatible'): make_compatible(program, **pub_kwargs) mocked.assert_called_once_with(program, **priv_kwargs) with mock.patch( 'qupulse._program._loop._is_compatible', return_value=_CompatibilityLevel.action_required) as is_compat: with mock.patch( 'qupulse._program._loop._make_compatible') as make_compat: make_compatible(program, **pub_kwargs) is_compat.assert_called_once_with(program, **priv_kwargs) make_compat.assert_called_once_with(program, **priv_kwargs)
def test_regression_duration_conversion(self): old_value = qupulse._program.waveforms.PULSE_TO_WAVEFORM_ERROR try: qupulse._program.waveforms.PULSE_TO_WAVEFORM_ERROR = 1e-6 for duration_in_samples in [64, 936320, 24615392]: p = ConstantPulseTemplate(duration_in_samples / 2.4, {'a': 0}) number_of_samples = p.create_program().duration * 2.4 make_compatible(p.create_program(), 8, 8, 2.4) self.assertEqual(number_of_samples.denominator, 1) p2 = ConstantPulseTemplate((duration_in_samples + 1) / 2.4, {'a': 0}) self.assertNotEqual(p.create_program().duration, p2.create_program().duration) finally: qupulse._program.waveforms.PULSE_TO_WAVEFORM_ERROR = old_value
def test_make_compatible_repetition_count(self): wf1 = DummyWaveform(duration=1.5) wf2 = DummyWaveform(duration=2.0) program = Loop(children=[ Loop(waveform=wf1, repetition_count=2), Loop(waveform=wf2) ]) duration = program.duration _make_compatible(program, min_len=1, quantum=1, sample_rate=time_from_float(1.)) self.assertEqual(program.duration, duration) wf2 = DummyWaveform(duration=2.5) program = Loop(children=[ Loop(waveform=wf1, repetition_count=3), Loop(waveform=wf2) ]) duration = program.duration with self.assertWarns(MakeCompatibleWarning): make_compatible(program, minimal_waveform_length=1, waveform_quantum=1, sample_rate=time_from_float(1.)) self.assertEqual(program.duration, duration) program = Loop(children=[ Loop(waveform=wf1, repetition_count=3), Loop(waveform=wf2) ], repetition_count=3) duration = program.duration _make_compatible(program, min_len=1, quantum=3, sample_rate=time_from_float(1.)) self.assertEqual(program.duration, duration)
def upload(self, name: str, program: Loop, channels: Tuple[Optional[ChannelID], ...], markers: Tuple[Optional[ChannelID], ...], voltage_transformation: Tuple[Callable, ...], force: bool = False) -> None: """Upload a program to the AWG. Physically uploads all waveforms required by the program - excluding those already present - to the device and sets up playback sequences accordingly. This method should be cheap for program already on the device and can therefore be used for syncing. Programs that are uploaded should be fast(~1 sec) to arm. Args: name: A name for the program on the AWG. program: The program (a sequence of instructions) to upload. channels: Tuple of length num_channels that ChannelIDs of in the program to use. Position in the list corresponds to the AWG channel markers: List of channels in the program to use. Position in the List in the list corresponds to the AWG channel voltage_transformation: transformations applied to the waveforms extracted rom the program. Position in the list corresponds to the AWG channel force: If a different sequence is already present with the same name, it is overwritten if force is set to True. (default = False) Known programs are handled in host memory most of the time. Only when uploading the device memory is touched at all. Returning from setting user register in seqc can take from 50ms to 60 ms. Fluctuates heavily. Not a good way to have deterministic behaviour "setUserReg(PROG_SEL, PROG_IDLE);". """ if len(channels) != self.num_channels: raise HDAWGValueError('Channel ID not specified') if len(markers) != self.num_markers: raise HDAWGValueError('Markers not specified') if len(voltage_transformation) != self.num_channels: raise HDAWGValueError('Wrong number of voltage transformations') if name in self.programs and not force: raise HDAWGValueError('{} is already known on {}'.format(name, self.identifier)) # Go to qupulse nanoseconds time base. q_sample_rate = self.sample_rate / 10**9 # Adjust program to fit criteria. make_compatible(program, minimal_waveform_length=self.MIN_WAVEFORM_LEN, waveform_quantum=self.WAVEFORM_LEN_QUANTUM, sample_rate=q_sample_rate) if self._amplitude_offset_handling == AWGAmplitudeOffsetHandling.IGNORE_OFFSET: voltage_offsets = (0.,) * self.num_channels elif self._amplitude_offset_handling == AWGAmplitudeOffsetHandling.CONSIDER_OFFSET: voltage_offsets = self.offsets() else: raise ValueError('{} is invalid as AWGAmplitudeOffsetHandling'.format(self._amplitude_offset_handling)) amplitudes = self.amplitudes() if name in self._program_manager.programs: self._program_manager.remove(name) self._program_manager.add_program(name, program, channels=channels, markers=markers, voltage_transformations=voltage_transformation, sample_rate=q_sample_rate, amplitudes=amplitudes, offsets=voltage_offsets) self._required_seqc_source = self._program_manager.to_seqc_program() self._program_manager.waveform_memory.sync_to_file_system(self.device.waveform_file_system) # start compiling the source (non-blocking) self._start_compile_and_upload()
def upload(self, name: str, program: Loop, channels: Tuple[Optional[ChannelID], Optional[ChannelID]], markers: Tuple[Optional[ChannelID], Optional[ChannelID]], voltage_transformation: Tuple[Callable, Callable], force: bool = False) -> None: """Upload a program to the AWG. The policy is to prefer amending the unknown waveforms to overwriting old ones.""" if len(channels) != self.num_channels: raise ValueError('Channel ID not specified') if len(markers) != self.num_markers: raise ValueError('Markers not specified') if len(voltage_transformation) != self.num_channels: raise ValueError('Wrong number of voltage transformations') # adjust program to fit criteria sample_rate = self.device.sample_rate(self._channels[0]) make_compatible(program, minimal_waveform_length=192, waveform_quantum=16, sample_rate=fractions.Fraction(sample_rate, 10**9)) if name in self._known_programs: if force: self.free_program(name) else: raise ValueError('{} is already known on {}'.format(name, self.identifier)) # They call the peak to peak range amplitude ranges = (self.device.amplitude(self._channels[0]), self.device.amplitude(self._channels[1])) voltage_amplitudes = (ranges[0]/2, ranges[1]/2) if self._amplitude_offset_handling == AWGAmplitudeOffsetHandling.IGNORE_OFFSET: voltage_offsets = (0, 0) elif self._amplitude_offset_handling == AWGAmplitudeOffsetHandling.CONSIDER_OFFSET: voltage_offsets = (self.device.offset(self._channels[0]), self.device.offset(self._channels[1])) else: raise ValueError('{} is invalid as AWGAmplitudeOffsetHandling'.format(self._amplitude_offset_handling)) # parse to tabor program tabor_program = TaborProgram(program, channels=tuple(channels), markers=markers, device_properties=self.device.dev_properties, sample_rate=sample_rate / 10**9, amplitudes=voltage_amplitudes, offsets=voltage_offsets, voltage_transformations=voltage_transformation) segments, segment_lengths = tabor_program.get_sampled_segments() waveform_to_segment, to_amend, to_insert = self._find_place_for_segments_in_memory(segments, segment_lengths) self._segment_references[waveform_to_segment[waveform_to_segment >= 0]] += 1 for wf_index in np.flatnonzero(to_insert > 0): segment_index = to_insert[wf_index] self._upload_segment(to_insert[wf_index], segments[wf_index]) waveform_to_segment[wf_index] = segment_index if np.any(to_amend): segments_to_amend = [segments[idx] for idx in np.flatnonzero(to_amend)] waveform_to_segment[to_amend] = self._amend_segments(segments_to_amend) self._known_programs[name] = TaborProgramMemory(waveform_to_segment=waveform_to_segment, program=tabor_program)