def test_serialize_deserialize_pulse(self): with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=UserWarning, message="qupulse") period = 1e-6 amplitude = 1.5 sawtooth = Sequencer.make_sawtooth_wave(amplitude, period) serialized_pulse = sawtooth['wave'] serialized_data = Sequencer.serialize(sawtooth) self.assertTrue( "qupulse.pulses.sequence_pulse_template.SequencePulseTemplate" in serialized_data) self.assertTrue( "qupulse.pulses.mapping_pulse_template.MappingPulseTemplate" in serialized_data) self.assertTrue("\"amplitude\": 0.75" in serialized_data) self.assertTrue("\"period\": 1000.0" in serialized_data) self.assertTrue("\"width\": 0.95" in serialized_data) self.assertTrue( "qupulse.pulses.table_pulse_template.TablePulseTemplate" in serialized_data) self.assertTrue("sawtooth" in serialized_data) deserialized_pulse = Sequencer.deserialize(serialized_data) self.assertEqual( serialized_pulse.subtemplates[0].parameter_mapping['period'], deserialized_pulse.subtemplates[0].parameter_mapping['period']) self.assertEqual( serialized_pulse.subtemplates[0]. parameter_mapping['amplitude'], deserialized_pulse. subtemplates[0].parameter_mapping['amplitude']) self.assertEqual( serialized_pulse.subtemplates[0].parameter_mapping['width'], deserialized_pulse.subtemplates[0].parameter_mapping['width'])
def __make_markers(self, period): digitizer_marker = Sequencer.make_marker( period, self.digitizer_marker_uptime(), self.digitizer_marker_delay()) awg_marker = Sequencer.make_marker(period, self.awg_marker_uptime(), self.awg_marker_delay()) return dict({'m4i_mk': digitizer_marker, 'awg_mk': awg_marker})
def test_make_pulse_table(self): with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=UserWarning, message="qupulse") amplitudes = [1, 2, 3] waiting_times = [1e-4, 2e-5, 3e-3] sampling_rate = 1e9 pulse_data = Sequencer.make_pulse_table(amplitudes, waiting_times) raw_data = Sequencer.get_data(pulse_data, sampling_rate) self.assertTrue(raw_data[0] == amplitudes[0]) self.assertTrue(raw_data[-1] == amplitudes[-1])
def sweep_gates_2d(self, gates, sweep_ranges, period, resolution, width=0.9375, do_upload=True): """ Supplies sawtooth signals to a linear combination of gates, which effectively does a 2D scan. Arguments: gates (list[dict]): A list containing two dictionaries with both the the gate name keys and relative amplitude values. sweep_ranges (list): A list two overall amplitude of the sawtooth waves in millivolt in the x- and y-direction. period (float): The total period of the sawtooth signals in seconds. resolution (list): Two integer values with the number of sawtooth signal (pixels) in the x- and y-direction. width (float): Width of the rising sawtooth ramp as a proportion of the total cycle. Needs a value between 0 and 1. The value 1 producing a rising ramp, while 0 produces a falling ramp. do_upload (bool, Optional): Does not upload the waves to the AWG's when set to False. Returns: A dictionary with the properties of the sawtooth signals; the original sawtooth sequence, the sweep ranges, the marker properties and period of the sawtooth signals. Example: >> sec_period = 1e-6 >> resolution = [10, 10] >> mV_sweep_ranges = [100, 100] >> gates = [{'P4': 1}, {'P7': 0.1}] >> sweep_data = virtual_awg.sweep_gates_2d(gates, mV_sweep_ranges, period, resolution) """ sequences = {} base_period = period / np.prod(resolution) sequences.update(self.make_markers(period, repetitions=1)) period_x = resolution[0] * base_period for gate_name_x, rel_amplitude_x in gates[0].items(): amplitude_x = rel_amplitude_x * sweep_ranges[0] sweep_wave_x = Sequencer.make_sawtooth_wave(amplitude_x, period_x, width, resolution[1]) sequences.setdefault(gate_name_x, []).append(sweep_wave_x) period_y = resolution[0] * resolution[1] * base_period for gate_name_y, rel_amplitude_y in gates[1].items(): amplitude_y = rel_amplitude_y * sweep_ranges[1] sweep_wave_y = Sequencer.make_sawtooth_wave(amplitude_y, period_y, width) sequences.setdefault(gate_name_y, []).append(sweep_wave_y) sweep_data = self.sequence_gates(sequences, do_upload) sweep_data.update({'sweeprange_horz': sweep_ranges[0], 'sweeprange_vert': sweep_ranges[1], 'width_horz': width, 'width_vert': width, 'resolution': resolution, 'start_zero': True, 'period': period_y, 'period_horz': period_x, 'samplerate': self.awgs[0].retrieve_sampling_rate(), 'markerdelay': self.digitizer_marker_delay()}) return sweep_data
def test_qupulse_sawtooth_HasCorrectProperties(self): with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=UserWarning, message="qupulse") epsilon = 1e-14 period = 1e-3 amplitude = 1.5 sampling_rate = 1e9 sequence = Sequencer.make_sawtooth_wave(amplitude, period) raw_data = Sequencer.get_data(sequence, sampling_rate) self.assertTrue(len(raw_data) == sampling_rate * period + 1) self.assertTrue(np.abs(np.min(raw_data) + amplitude / 2) <= epsilon) self.assertTrue(np.abs(np.max(raw_data) - amplitude / 2) <= epsilon)
def test_offset_raises_errors(self): period = 10e-9 offset = -11e-9 uptime = 2e-9 with self.assertRaises(ValueError) as error: Sequencer.make_marker(period=period, offset=offset, uptime=uptime) self.assertIn('Invalid argument value for offset: |-1.1e-08| > 1e-08!', error.exception.args) offset = -offset with self.assertRaises(ValueError) as error: Sequencer.make_marker(period=period, offset=offset, uptime=uptime) self.assertIn('Invalid argument value for offset: |1.1e-08| > 1e-08!', error.exception.args)
def pulse_gates_2d(self, gates, sweep_ranges, period, resolution, do_upload=True): """ Supplies square signals to a linear combination of gates, which effectively does a 2D scan. Arguments: gates (list[dict]): A list containing two dictionaries with both the the gate name keys and relative amplitude values. sweep_ranges (list): A list two overall amplitude of the square signal in millivolt in the x- and y-direction. period (float): The period of the square signals in seconds. resolution (list): Two integer values with the number of square signal (pixels) in the x- and y-direction. do_upload (bool, Optional): Does not upload the waves to the AWG's when set to False. Returns: A dictionary with the properties of the square signals; the original square sequence, the sweep ranges, the marker properties and period of the square signals. Example: >> sec_period = 1e-6 >> resolution = [10, 10] >> mV_sweep_ranges = [100, 100] >> gates = [{'P4': 1}, {'P7': 0.1}] >> sweep_data = virtual_awg.pulse_gates_2d(gates, mV_sweep_ranges, period, resolution) """ sequences = {} sequences.update(self.make_markers(period)) period_x = resolution[0] * period for gate_name_x, rel_amplitude_x in gates[0].items(): amplitude_x = rel_amplitude_x * sweep_ranges[0] pulse_wave_x = Sequencer.make_square_wave(amplitude_x, period_x, resolution[1]) sequences.setdefault(gate_name_x, []).append(pulse_wave_x) period_y = resolution[0] * resolution[1] * period for gate_name_y, rel_amplitude_y in gates[1].items(): amplitude_y = rel_amplitude_y * sweep_ranges[1] pulse_wave_y = Sequencer.make_square_wave(amplitude_y, period_y) sequences.setdefault(gate_name_y, []).append(pulse_wave_y) sweep_data = self.sequence_gates(sequences, do_upload) sweep_data.update({'sweeprange_horz': sweep_ranges[0], 'sweeprange_vert': sweep_ranges[1], 'resolution': resolution, 'period': period_x, 'period_vert': period_y, 'samplerate': self.awgs[0].retrieve_setting('channel_sampling_rate'), 'markerdelay': self.awg_marker_delay()}) return sweep_data
def pulse_gates(self, gate_voltages, waiting_times, repetitions=1, do_upload=True): """ Supplies custom sequences to the gates. The supplied list of voltage setpoints with waiting times are converted into sequences for each gate and upload to the AWG. Arguments: gate_voltages (dict): Each gate name key contains a an array with millivolt setpoint level to be converted into a sequence. waiting_times (list[float]): The duration in seconds of each pulse in the sequence. repetitions (int): The number of times to repeat the sequence. do_upload (bool, Optional): Does not upload the waves to the AWG's when set to False. Returns: A dictionary with the properties of the pulse waves; the original pulse sequence, the sweep ranges and the marker properties and period of the pulse waves. Example: >>> gates_voltages = {'P4': [50, 0, -50], 'P7': [-25, 0, 25]} >>> waiting_times = [1e-4, 1e-4, 1e-4] >>> pulse_data = virtualawg.pulse_gates(gate_voltages, waiting_times) """ sequences = dict() period = sum(waiting_times) sequences.update(self.make_markers(period, repetitions)) for gate_name, amplitudes in gate_voltages.items(): sequences[gate_name] = Sequencer.make_pulse_table( amplitudes, waiting_times, repetitions, gate_name) sweep_data = self.sequence_gates(sequences, do_upload) sweep_data.update({'period': period, 'start_zero': True, 'width': 1.0}) if VirtualAwg.__digitizer_name in self._settings.awg_map: sweep_data.update({'markerdelay': self.digitizer_marker_delay()}) return sweep_data
def sequence_gates(self, gate_comb, do_upload=True): if do_upload: [awg.delete_waveforms() for awg in self.awgs] for number in self.__awg_range: sequence_channels = list() sequence_names = list() sequence_items = list() vpp_amplitude = self.awgs[number].retrieve_gain() sampling_rate = self.awgs[number].retrieve_sampling_rate() for gate_name, sequence in gate_comb.items(): (awg_number, channel_number, *marker_number) = self._gate_map[gate_name] if awg_number != number: continue awg_to_plunger = self.__hardware.parameters['awg_to_{}'.format( gate_name)].get() scaling_ratio = 2 * VirtualAwg.__volt_to_millivolt / awg_to_plunger / vpp_amplitude sample_data = Sequencer.get_data(sequence, sampling_rate) sequence_data = sample_data if marker_number else sample_data * scaling_ratio sequence_names.append('{}_{}'.format(gate_name, sequence['name'])) sequence_channels.append((channel_number, *marker_number)) sequence_items.append(sequence_data) if do_upload: self.awgs[number].upload_waveforms(sequence_names, sequence_channels, sequence_items) return {'gate_comb': gate_comb}
def sweep_gates(self, gates, sweep_range, period, width=0.95, do_upload=True): """ Sweep a set of gates with a sawtooth waveform. Example: >>> sweep_data = virtualawg.sweep_gates({'P4': 1, 'P7': 0.1}, 100, 1e-3) """ sequences = dict() sequences.update(self.__make_markers(period)) for gate_name, rel_amplitude in gates.items(): amplitude = rel_amplitude * sweep_range sequences[gate_name] = Sequencer.make_sawtooth_wave( amplitude, period, width) sweep_data = self.sequence_gates(sequences, do_upload) sweep_data.update({ 'sweeprange': sweep_range, 'period': period, 'width': width, 'markerdelay': self.digitizer_marker_delay() }) return sweep_data
def test_qupulse_template_to_array_new_style_vs_values_old_style(self): with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=UserWarning, message="qupulse") warnings.filterwarnings( "ignore", category=DeprecationWarning, message="InstructionBlock API is deprecated") period = 1e-3 amplitude = 1.5 sampling_rate = 1e9 sequence = Sequencer.make_sawtooth_wave(amplitude, period) template = sequence['wave'] channels = template.defined_channels loop = template.create_program( parameters=dict(), measurement_mapping={w: w for w in template.measurement_names}, channel_mapping={ch: ch for ch in channels}, global_transformation=None, to_single_waveform=set()) (_, voltages_new, _) = render(loop, sampling_rate / 1e9) # the value to compare to are calculated using qupulse 0.4 Sequencer class self.assertTrue(1000001 == len(voltages_new['sawtooth'])) self.assertAlmostEqual(-amplitude / 2, np.min(voltages_new['sawtooth']), 12) self.assertAlmostEqual(amplitude / 2, np.max(voltages_new['sawtooth']), 12)
def sequence_gates(self, sequences, do_upload=True): """ The base function for uploading sequences to the AWG's. The sequences must be constructed using the qtt.instrument_drivers.virtualAwg.sequencer.Sequencer class. Arguments: sequences (dict): A dictionary with names as keys and sequences as values. do_upload (bool, Optional): Does not upload the waves to the AWG's when set to False. Example: >> from qtt.instrument_drivers.virtualAwg.sequencer import Sequencer. >> amplitude = 1.5 >> period_in_seconds = 1e-6 >> sawtooth_signal = Sequencer.make_sawtooth_wave(amplitude, period_in_seconds) >> virtual_awg.sequence_gates(sawtooth_signal) """ upload_data = [] settings_data = {} if do_upload: _ = [awg.delete_waveforms() for awg in self.awgs] for number in self.__awg_range: sequence_channels = [] sequence_names = [] sequence_items = [] gain_factor = self.awgs[number].retrieve_gain() vpp_amplitude = 2 * gain_factor sampling_rate = self.awgs[number].retrieve_sampling_rate() settings_data[number] = {'vpp_amplitude': vpp_amplitude, 'sampling_rate': sampling_rate} for gate_name, sequence in sequences.items(): (awg_number, channel_number, *marker_number) = self._settings.awg_map[gate_name] if awg_number != number: continue waveforms = [Sequencer.get_data(waveform, sampling_rate) for waveform in sequence] sequence_data = np.sum(waveforms, 0) sequence_data = sequence_data[:-1] if not marker_number: awg_to_gate = self._settings.parameters[f'awg_to_{gate_name}'].get() scaling_ratio = 1 / (awg_to_gate * gain_factor) settings_data[number][gate_name] = {'scaling_ratio': scaling_ratio} sequence_data *= scaling_ratio sequence_name = sequence[0]['name'] sequence_names.append(f'{gate_name}_{sequence_name}') sequence_channels.append((channel_number, *marker_number)) sequence_items.append(sequence_data) upload_data.append((sequence_names, sequence_channels, sequence_items)) if do_upload and sequence_items: self.awgs[number].upload_waveforms(sequence_names, sequence_channels, sequence_items) sequence_data = {'gate_comb': sequences, 'upload_data': upload_data, 'settings': settings_data} if self.enable_debug: self._latest_sequence_data = sequence_data return sequence_data
def sweep_gates_2d(self, gates, sweep_ranges, period, resolution, width=0.95, do_upload=True): sequences = dict() sequences.update(self.__make_markers(period)) period_x = resolution[0] * period for gate_name_x, rel_amplitude_x in gates[0].items(): amplitude_x = rel_amplitude_x * sweep_ranges[0] sequences[gate_name_x] = Sequencer.make_sawtooth_wave( amplitude_x, period_x, width) period_y = resolution[0] * resolution[1] * period for gate_name_y, rel_amplitude_y in gates[1].items(): amplitude_y = rel_amplitude_y * sweep_ranges[1] sequences[gate_name_y] = Sequencer.make_sawtooth_wave( amplitude_y, period_y, width) sweep_data = self.sequence_gates(sequences, do_upload) sweep_data.update({ 'sweeprange_horz': sweep_ranges[0], 'sweeprange_vert': sweep_ranges[1], 'width_horz': 0.95, 'width_vert': 0.95, 'resolution': resolution, 'period': period_y, 'period_horz': period_x, 'samplerate': self.awgs[0].retrieve_setting('sampling_rate'), 'markerdelay': self.awg_marker_delay() }) return sweep_data
def test_raw_wave_HasCorrectProperties(self): with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=UserWarning, message="qupulse") period = 1e-3 sampling_rate = 1e9 name = 'test_raw_data' sequence = {'name': name, 'wave': [0] * int(period * sampling_rate + 1), 'type': DataTypes.RAW_DATA} raw_data = Sequencer.get_data(sequence, sampling_rate) self.assertTrue(len(raw_data) == sampling_rate * period + 1) self.assertTrue(np.min(raw_data) == 0)
def test_make_marker_no_regression(self): period = 10e-9 offset = 0 uptime = 4e-9 marker = Sequencer.make_marker(period=period, offset=offset, uptime=uptime) self.assertEqual(offset, marker['offset']) self.assertEqual(uptime, marker['uptime']) parameters = marker['wave'].subtemplates[0].parameter_mapping self.assertEqual(10, parameters['period']) self.assertEqual(0, parameters['offset']) self.assertEqual(4, parameters['uptime'])
def test_make_marker_negative_offset(self): period = 10e-9 offset = -3e-9 uptime = 2e-9 marker = Sequencer.make_marker(period=period, offset=offset, uptime=uptime) self.assertEqual(offset, marker['offset']) self.assertEqual(uptime, marker['uptime']) parameters = marker['wave'].subtemplates[0].parameter_mapping self.assertEqual(7, parameters['offset']) self.assertEqual(2, parameters['uptime']) self.assertEqual(10, parameters['period'])
def make_markers(self, period, repetitions=1): """ Constructs the markers for triggering the digitizer readout and the slave AWG start sequence. The sequence length equals the period x repetitions. Arguments: period (float): The period of the markers in seconds. repetitions (int): The number of markers in the sequence. """ marker_properties = {} uptime = self.digitizer_marker_uptime() delay = self.digitizer_marker_delay() if VirtualAwg.__digitizer_name in self._settings.awg_map: digitizer_marker = Sequencer.make_marker(period, uptime, delay, repetitions) marker_properties[VirtualAwg.__digitizer_name] = [digitizer_marker] if VirtualAwg.__awg_slave_name in self._settings.awg_map: awg_marker = Sequencer.make_marker(period, uptime, delay, repetitions) marker_properties[VirtualAwg.__awg_slave_name] = [awg_marker] return marker_properties
def pulse_gates(self, gates, sweep_range, period, do_upload=True): sequences = dict() sequences.update(self.__make_markers(period)) for gate_name, rel_amplitude in gates.items(): amplitude = rel_amplitude * sweep_range sequences[gate_name] = Sequencer.make_square_wave( amplitude, period) sweep_data = self.sequence_gates(sequences, do_upload) return sweep_data.update({ 'sweeprange': sweep_range, 'period': period, 'markerdelay': self.digitizer_marker_delay() })
def test_make_marker_negative_offset_rollover(self): period = 10e-9 offset = -2e-9 uptime = 4e-9 with patch('warnings.warn') as warn: marker = Sequencer.make_marker(period=period, offset=offset, uptime=uptime) warn.assert_called_once_with('Marker rolls over to subsequent period.') self.assertEqual(offset, marker['offset']) self.assertEqual(uptime, marker['uptime']) parameters = marker['wave'].subtemplates[0].parameter_mapping self.assertEqual(8, parameters['offset']) self.assertEqual(4, parameters['uptime']) self.assertEqual(10, parameters['period'])
def sweep_gates(self, gates, sweep_range, period, width=0.9375, do_upload=True): """ Supplies a sawtooth wave to the given gates and returns the settings required for processing and constructing the readout times for the digitizer. Arguments: gates (dict): Contains the gate name keys with relative amplitude values. sweep_range (float): The peak-to-peak amplitude of the sawtooth waves in millivolt. period (float): The period of the pulse waves in seconds. width (float): Width of the rising sawtooth ramp as a proportion of the total cycle. Needs a value between 0 and 1. The value 1 producing a rising ramp, while 0 produces a falling ramp. do_upload (bool, Optional): Does not upload the waves to the AWG's when set to False. Returns: A dictionary with the properties of the pulse waves; the original sawtooth sequence, the sweep ranges and the marker properties and period of the sawtooth waves. Example: >> sec_period = 1e-6 >> mV_sweep_range = 100 >> gates = {'P4': 1, 'P7': 0.1} >> sweep_data = virtual_awg.sweep_gates(gates, 100, 1e-3) """ sequences = dict() sequences.update(self.make_markers(period)) for gate_name, rel_amplitude in gates.items(): amplitude = rel_amplitude * sweep_range sweep_wave = Sequencer.make_sawtooth_wave(amplitude, period, width) sequences.setdefault(gate_name, []).append(sweep_wave) sweep_data = self.sequence_gates(sequences, do_upload) sweep_data.update({ 'sweeprange': sweep_range, 'period': period, 'width': width, 'start_zero': True, '_gates': gates }) if VirtualAwg.__digitizer_name in self._settings.awg_map: sweep_data.update({'markerdelay': self.digitizer_marker_delay()}) return sweep_data
def test_qupulse_template_to_array_new_style_vs_old_style(self): with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=UserWarning, message="qupulse") warnings.filterwarnings( "ignore", category=DeprecationWarning, message="InstructionBlock API is deprecated") period = 1e-3 amplitude = 1.5 sampling_rate = 1e9 sequence = Sequencer.make_sawtooth_wave(amplitude, period) template = sequence['wave'] channels = template.defined_channels sequencer = Sequencing() sequencer.push( template, dict(), channel_mapping={ch: ch for ch in channels}, window_mapping={w: w for w in template.measurement_names}) instructions = sequencer.build() if not sequencer.has_finished(): raise PlottingNotPossibleException(template) (_, voltages_old, _) = render(instructions, sampling_rate / 1e9) loop = template.create_program( parameters=dict(), measurement_mapping={w: w for w in template.measurement_names}, channel_mapping={ch: ch for ch in channels}, global_transformation=None, to_single_waveform=set()) (_, voltages_new, _) = render(loop, sampling_rate / 1e9) self.assertTrue(len(voltages_old) == len(voltages_new)) self.assertTrue(np.min(voltages_old) == np.min(voltages_old)) self.assertTrue(np.max(voltages_old) == np.max(voltages_old))
def test_uptime_raises_errors(self): period = 10e-9 offset = 0 uptime = 0 with self.assertRaises(ValueError) as error: Sequencer.make_marker(period=period, offset=offset, uptime=uptime) self.assertIn("Invalid argument value for uptime '0'!", error.exception.args) uptime = 11e-9 with self.assertRaises(ValueError) as error: Sequencer.make_marker(period=period, offset=offset, uptime=uptime) self.assertIn("Invalid argument value for uptime '1.1e-08'!", error.exception.args) uptime = -1e-9 with self.assertRaises(ValueError) as error: Sequencer.make_marker(period=period, offset=offset, uptime=uptime) self.assertIn("Invalid argument value for uptime '-1e-09'!", error.exception.args)