From e07213be946d9972bc3f76a4c5d075ec88ea5894 Mon Sep 17 00:00:00 2001 From: Simon Humpohl Date: Wed, 24 Feb 2021 12:40:32 +0100 Subject: [PATCH 1/9] Make flatten and balance work on single waveform playbacks --- qupulse/_program/_loop.py | 66 +++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/qupulse/_program/_loop.py b/qupulse/_program/_loop.py index 9310cf5f7..3432d6dca 100644 --- a/qupulse/_program/_loop.py +++ b/qupulse/_program/_loop.py @@ -323,36 +323,42 @@ def flatten_and_balance(self, depth: int) -> None: Args: depth: Target depth of the program """ - i = 0 - while i < len(self): - # only used by type checker - sub_program = cast(Loop, self[i]) - - if sub_program.depth() < depth - 1: - # increase nesting because the subprogram is not deep enough - sub_program.encapsulate() - - elif not sub_program.is_balanced(): - # balance the sub program. We revisit it in the next iteration (no change of i ) - # because it might modify self. While writing this comment I am not sure this is true. 14.01.2020 Simon - sub_program.flatten_and_balance(depth - 1) - - elif sub_program.depth() == depth - 1: - # subprogram is balanced with the correct depth - i += 1 - - elif sub_program._has_single_child_that_can_be_merged(): - # subprogram is balanced but to deep and has no measurements -> we can "lift" the sub-sub-program - # TODO: There was a len(sub_sub_program) == 1 check here that I cannot explain - sub_program._merge_single_child() - - elif not sub_program.is_leaf(): - # subprogram is balanced but too deep - sub_program.unroll() - - else: - # we land in this case if the function gets called with depth == 0 and the current subprogram is a leaf - i += 1 + if self._waveform is None: + i = 0 + while i < len(self): + # only used by type checker + sub_program = cast(Loop, self[i]) + + if sub_program.depth() < depth - 1: + # increase nesting because the subprogram is not deep enough + sub_program.encapsulate() + + elif not sub_program.is_balanced(): + # balance the sub program. We revisit it in the next iteration (no change of i ) + # because it might modify self. While writing this comment I am not sure this is true. 14.01.2020 Simon + sub_program.flatten_and_balance(depth - 1) + + elif sub_program.depth() == depth - 1: + # subprogram is balanced with the correct depth + i += 1 + + elif sub_program._has_single_child_that_can_be_merged(): + # subprogram is balanced but to deep and has no measurements -> we can "lift" the sub-sub-program + # TODO: There was a len(sub_sub_program) == 1 check here that I cannot explain + sub_program._merge_single_child() + + elif not sub_program.is_leaf(): + # subprogram is balanced but too deep + sub_program.unroll() + + else: + # we land in this case if the function gets called with depth == 0 and the current subprogram is a leaf + i += 1 + + else: + assert len(self) == 0 + for _ in range(depth): + self.encapsulate() def _has_single_child_that_can_be_merged(self) -> bool: if len(self) == 1: From 2fd2014e42b2820b7fc84c38597b15d679789761 Mon Sep 17 00:00:00 2001 From: ag-bluhm-69 Date: Thu, 25 Feb 2021 11:54:00 +0100 Subject: [PATCH 2/9] Add a setup function for the Tektronix AWG5014 --- MATLAB/+qc/setup_tek_awg.m | 76 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 MATLAB/+qc/setup_tek_awg.m diff --git a/MATLAB/+qc/setup_tek_awg.m b/MATLAB/+qc/setup_tek_awg.m new file mode 100644 index 000000000..ebb94da00 --- /dev/null +++ b/MATLAB/+qc/setup_tek_awg.m @@ -0,0 +1,76 @@ +function setup_tek_awg(varargin) + +global smdata +global plsdata + +defaultArgs = struct( ... + 'sampleVoltPerAwgVolt', [util.db('dB2F',-48)*2 util.db('dB2F',-48)*2 util.db('dB2F',-44)*2 util.db('dB2F',-48)*2], ... % 10^(-dB/20)*ImpedanceMismatch + 'smChannels', {{'RFA', 'RFB', 'RFC', 'RFD'}}, ... + 'tekName', 'AWG5000', ... + 'globalTransformation', [], ... + 'ip', '169.254.40.80', ... %IP's: + 'dcMode', false, ... + 'maxPulseWait', 60 ... % Maximum waiting time in s in qc.awg_program before arming DAQ again + ); + +args = util.parse_varargin(varargin, defaultArgs); +plsdata.awg.sampleVoltPerAwgVolt = args.sampleVoltPerAwgVolt; +plsdata.awg.dcMode = args.dcMode; +plsdata.awg.triggerStartTime = 0; +plsdata.awg.maxPulseWait = args.maxPulseWait; +plsdata.awg.minSamples = 250; +plsdata.awg.sampleQuantum = 1; +plsdata.awg.globalTransformation = args.globalTransformation; + +for k = 1:numel(args.smChannels) + smChannel = args.smChannels(k); + if ~(smdata.channels(smchanlookup(smChannel)).instchan(1) == sminstlookup(args.tekName)) + error('Channel %s does not belong to %s\n', smChannel, args.tekName); + end + smdata.channels(smchanlookup(smChannel)).rangeramp(end) = 1/args.sampleVoltPerAwgVolt(k); +end + +% Reload qctoolkit TEK AWG integration +qctoolkit_tek = py.importlib.reload(py.importlib.import_module('qctoolkit.hardware.awgs.tektronix')); + +py.importlib.import_module('tek_awg'); + +awg = py.tek_awg.TekAwg.connect_to_ip('169.254.40.80'); +% Only real instrument +smdata.inst(sminstlookup(args.tekName)).data.tawg = qctoolkit_tek.TektronixAWG(awg, pyargs('synchronize','clear')); + + +plsdata.awg.inst = smdata.inst(sminstlookup(args.tekName)).data.tawg; +if exist('awgctrl_tek.m', 'file') + awgcntrl('off') +end + +% Create hardware setup for qctoolkit integration +plsdata.awg.hardwareSetup = py.qctoolkit.hardware.setup.HardwareSetup(); + +% Create python lambda function in Matlab +numpy = py.importlib.import_module('numpy'); +for k = 1:numel(args.sampleVoltPerAwgVolt) + multiply{k} = py.functools.partial(numpy.multiply, double(1./(args.sampleVoltPerAwgVolt(k)))); +end + +% PlaybackChannels can take more than two values (analog channels) +plsdata.awg.hardwareSetup.set_channel('TEK_A', ... + py.qctoolkit.hardware.setup.PlaybackChannel(plsdata.awg.inst, int64(0), multiply{1})); +plsdata.awg.hardwareSetup.set_channel('TEK_B', ... + py.qctoolkit.hardware.setup.PlaybackChannel(plsdata.awg.inst, int64(1), multiply{2})); +plsdata.awg.hardwareSetup.set_channel('TEK_C', ... + py.qctoolkit.hardware.setup.PlaybackChannel(plsdata.awg.inst, int64(2), multiply{3})); +plsdata.awg.hardwareSetup.set_channel('TEK_D', ... + py.qctoolkit.hardware.setup.PlaybackChannel(plsdata.awg.inst, int64(3), multiply{4})); + +% MarkerChannel can only take on two values (digital channels) +plsdata.awg.hardwareSetup.set_channel('TEK_A_MARKER', ... + py.qctoolkit.hardware.setup.MarkerChannel(plsdata.awg.inst, int64(0))); +plsdata.awg.hardwareSetup.set_channel('TEK_B_MARKER', ... + py.qctoolkit.hardware.setup.MarkerChannel(plsdata.awg.inst, int64(1))); +plsdata.awg.hardwareSetup.set_channel('TEK_C_MARKER', ... + py.qctoolkit.hardware.setup.MarkerChannel(plsdata.awg.inst, int64(2))); +plsdata.awg.hardwareSetup.set_channel('TEK_D_MARKER', ... + py.qctoolkit.hardware.setup.MarkerChannel(plsdata.awg.inst, int64(3))); +end \ No newline at end of file From 6e9209be45903b791e257a82cc8d2e6dc5400a9c Mon Sep 17 00:00:00 2001 From: ag-bluhm-69 Date: Fri, 26 Feb 2021 11:09:59 +0100 Subject: [PATCH 3/9] Modify conf_seq for HBT Sample --- MATLAB/+qc/conf_seq.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/MATLAB/+qc/conf_seq.m b/MATLAB/+qc/conf_seq.m index d785249a4..527a2e827 100644 --- a/MATLAB/+qc/conf_seq.m +++ b/MATLAB/+qc/conf_seq.m @@ -45,9 +45,9 @@ ... Saving variables 'save_custom_var_fn', @tune.get_global_opts,... % Can specify a function which returns data to be saved in the scan 'save_custom_var_args', {{'dnp', 'tune_gui'}}, ... - 'save_metadata_fns', {{@sm_scans.triton_200.metafn_get_configchanvals} ... % Can specify functions to log metadata during each loop - {@sm_scans.triton_200.metafn_get_rf_channels}}, ... - 'save_metadata_fields', {{'configchanvals'} {'rfChannels'}}, ... % Fieldnames of the metadata struct saved by smrun + 'save_metadata_fns', {{{@sm_scans.triton_200.metafn_get_configchanvals} ... % Can specify functions to log metadata during each loop + {@sm_scans.triton_200.metafn_get_rf_channels}}}, ... + 'save_metadata_fields', {{'configchanvals', 'rfChannels'}}, ... % Fieldnames of the metadata struct saved by smrun ... ... Measurements 'operations', {plsdata.daq.defaultOperations}, ... @@ -68,7 +68,7 @@ 'arm_global', false, ... % If true, set the program to be armed via tunedata.global_opts.conf_seq.arm_program_name. ... % If you use this, all programs need to be uploaded manually before the scan and need to ... % have the same Alazar configuration. - 'rf_sources', [true true], ... % turn RF sources on and off automatically + 'rf_sources', [false false], ... % turn RF sources on and off automatically 'buffer_strategy', {plsdata.daq.defaultBufferStrategy},... % call qc.set_alazar_buffer_strategy with these arguments before pulse 'verbosity', 10 ... % 0: display nothing, 10: display all except when arming program, 11: display all ); From b93262a4b9d8c544cae09630047f9934140940dc Mon Sep 17 00:00:00 2001 From: ag-bluhm-69 Date: Fri, 26 Feb 2021 11:11:22 +0100 Subject: [PATCH 4/9] Add query function for Tek amplitudes as needed for qc.awg_program --- qupulse/hardware/awgs/tektronix.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/qupulse/hardware/awgs/tektronix.py b/qupulse/hardware/awgs/tektronix.py index ab41e0a4f..5a86688b1 100644 --- a/qupulse/hardware/awgs/tektronix.py +++ b/qupulse/hardware/awgs/tektronix.py @@ -25,6 +25,8 @@ __all__ = ['TektronixAWG'] +class TektronixException(Exception): + pass class WaveformEntry: def __init__(self, name: str, length: int, waveform: tek_awg.Waveform, timestamp): @@ -810,3 +812,12 @@ def run_current_program(self, channel_states: Optional[Tuple[bool, bool, bool, b self.logger.info("Running program '%s'", program_name) self.device.jump_to_sequence_element(program_index) self.device.wait_until_commands_executed() + + def amplitude(self, channel) -> float: + if channel not in (1, 2, 3, 4): + raise TektronixException('Invalid channel: {}'.format(channel)) + + return float(self._device.query(f'SOUR{channel}:VOLT?')) + + + From f95487c03c31db0141a49c31222d56a59a9f6a63 Mon Sep 17 00:00:00 2001 From: Rene Otten Date: Fri, 26 Feb 2021 11:31:36 +0100 Subject: [PATCH 5/9] Use functions pprovided by the driver instead of direct queries --- qupulse/hardware/awgs/tektronix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qupulse/hardware/awgs/tektronix.py b/qupulse/hardware/awgs/tektronix.py index 5a86688b1..2b48adb67 100644 --- a/qupulse/hardware/awgs/tektronix.py +++ b/qupulse/hardware/awgs/tektronix.py @@ -817,7 +817,7 @@ def amplitude(self, channel) -> float: if channel not in (1, 2, 3, 4): raise TektronixException('Invalid channel: {}'.format(channel)) - return float(self._device.query(f'SOUR{channel}:VOLT?')) + return self._device.get_amplitude(channel) From 16e84cdd8d323a5e29e1ab87289358f49e03cdbf Mon Sep 17 00:00:00 2001 From: Rene Otten Date: Tue, 23 Mar 2021 14:49:47 +0100 Subject: [PATCH 6/9] Add toSingleWaveform functionality to qc --- MATLAB/+qc/awg_program.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MATLAB/+qc/awg_program.m b/MATLAB/+qc/awg_program.m index 91b06086c..2efd5baf1 100644 --- a/MATLAB/+qc/awg_program.m +++ b/MATLAB/+qc/awg_program.m @@ -18,6 +18,7 @@ 'window_mapping', plsdata.awg.defaultWindowMapping, ... 'global_transformation', plsdata.awg.globalTransformation, ... 'add_marker', {plsdata.awg.defaultAddMarker}, ... + 'to_single_waveform', plsdata.awg.toSingleWaveform,... 'force_update', false, ... 'verbosity', 10 ... ); @@ -55,7 +56,7 @@ fprintf('Program ''%s'' is now being instantiated...', a.program_name); tic; end - instantiated_pulse = qc.instantiate_pulse(a.pulse_template, 'parameters', qc.join_params_and_dicts(program.parameters_and_dicts), 'channel_mapping', program.channel_mapping, 'window_mapping', program.window_mapping, 'global_transformation', program.global_transformation); + instantiated_pulse = qc.instantiate_pulse(a.pulse_template, 'parameters', qc.join_params_and_dicts(program.parameters_and_dicts), 'channel_mapping', program.channel_mapping, 'window_mapping', program.window_mapping, 'global_transformation', program.global_transformation,'to_single_waveform',py.set(a.to_single_waveform)); if a.verbosity > 9 fprintf('took %.0fs\n', toc); From 2c60e59eb67646d51900780fec99baf3c3b6e488 Mon Sep 17 00:00:00 2001 From: Rene Otten Date: Tue, 23 Mar 2021 14:50:26 +0100 Subject: [PATCH 7/9] Fix small errors in setup_tek_awg --- MATLAB/+qc/setup_tek_awg.m | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/MATLAB/+qc/setup_tek_awg.m b/MATLAB/+qc/setup_tek_awg.m index ebb94da00..4c6e6a930 100644 --- a/MATLAB/+qc/setup_tek_awg.m +++ b/MATLAB/+qc/setup_tek_awg.m @@ -4,7 +4,7 @@ function setup_tek_awg(varargin) global plsdata defaultArgs = struct( ... - 'sampleVoltPerAwgVolt', [util.db('dB2F',-48)*2 util.db('dB2F',-48)*2 util.db('dB2F',-44)*2 util.db('dB2F',-48)*2], ... % 10^(-dB/20)*ImpedanceMismatch + 'sampleVoltPerAwgVolt', [util.db('dB2F',-45)*2 util.db('dB2F',-45)*2 util.db('dB2F',-45)*2 util.db('dB2F',-45)*2], ... % 10^(-dB/20)*ImpedanceMismatch 'smChannels', {{'RFA', 'RFB', 'RFC', 'RFD'}}, ... 'tekName', 'AWG5000', ... 'globalTransformation', [], ... @@ -36,13 +36,16 @@ function setup_tek_awg(varargin) py.importlib.import_module('tek_awg'); awg = py.tek_awg.TekAwg.connect_to_ip('169.254.40.80'); +awg.instrument.timeout = py.int(1200000); +% awg = py.tek_awg.TekAwg.connect_raw_visa_socket('169.254.40.80', '4001','@ni'); +% % +tawg=qctoolkit_tek.TektronixAWG(awg, pyargs('synchronize','clear')); % Only real instrument -smdata.inst(sminstlookup(args.tekName)).data.tawg = qctoolkit_tek.TektronixAWG(awg, pyargs('synchronize','clear')); - +smdata.inst(sminstlookup(args.tekName)).data.tawg = tawg; plsdata.awg.inst = smdata.inst(sminstlookup(args.tekName)).data.tawg; if exist('awgctrl_tek.m', 'file') - awgcntrl('off') + awgctrl('off') end % Create hardware setup for qctoolkit integration From 9eb642ba605cb67dbedc008f01a0917693876672 Mon Sep 17 00:00:00 2001 From: Rene Otten Date: Tue, 23 Mar 2021 14:50:42 +0100 Subject: [PATCH 8/9] Change setup to 1 Qubit --- MATLAB/+qc/setup_alazar_measurements.m | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/MATLAB/+qc/setup_alazar_measurements.m b/MATLAB/+qc/setup_alazar_measurements.m index 1642a72b9..705ae3461 100644 --- a/MATLAB/+qc/setup_alazar_measurements.m +++ b/MATLAB/+qc/setup_alazar_measurements.m @@ -39,26 +39,28 @@ defaultArgs = struct( ... 'disp', true, ... - 'nMeasPerQubit', 2, ... - 'nQubits', 2 ... + 'nMeasPerQubit', 1, ... + 'nQubits', 1 ... ); args = util.parse_varargin(varargin, defaultArgs); nAlazarChannels = 4; nQubits = args.nQubits; nMeasPerQubit = args.nMeasPerQubit; - py.setattr(hws, '_measurement_map', py.dict); - py.setattr(daq, '_mask_prototypes', py.dict); +% py.setattr(hws, '_measurement_map', py.dict()); + py.getattr(hws, '_measurement_map').clear(); +% py.setattr(daq, '_mask_prototypes', py.dict() ); + py.getattr(daq, '_mask_prototypes').clear(); warning('Removing measurement_map and measurement_map might break stuff if previously set. Needs testing.'); for q = 1:nQubits for m = 1:nMeasPerQubit % qubitIndex, measIndex, hwChannel, auxFlag1 - add_meas_and_mask(q, m, q+nQubits-1, false); + add_meas_and_mask(q, m, q+nQubits-2, false); end end - for a = 1:(nAlazarChannels-nQubits) + for a = (nAlazarChannels-nQubits-1):nAlazarChannels for m = 1:nMeasPerQubit % qubitIndex, measIndex, hwChannel, auxFlag1 add_meas_and_mask(a, m, a-1, true); @@ -71,7 +73,8 @@ if args.nQubits > 2 warning('Simultaneous measurements for more than 2 qubits not implemented at the moment.'); - end + end + if q == 2 for m = 1:nMeasPerQubit % Q1 Q2 qubitIndex, measIndex, hwChannel, auxFlag1, secondQubitIndex, secondHwChannel, auxFlag2 From 91d713ccee6cb0c8ec76c058dc6ab1fce77098d3 Mon Sep 17 00:00:00 2001 From: Simon Humpohl Date: Tue, 23 Mar 2021 15:51:58 +0100 Subject: [PATCH 9/9] Add interface to use atsaverage auto rearm --- qupulse/hardware/dacs/alazar.py | 36 ++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/qupulse/hardware/dacs/alazar.py b/qupulse/hardware/dacs/alazar.py index c5fee4c2c..c3d28d021 100644 --- a/qupulse/hardware/dacs/alazar.py +++ b/qupulse/hardware/dacs/alazar.py @@ -14,12 +14,16 @@ from qupulse.hardware.dacs.dac_base import DAC +logger = logging.getLogger(__name__) + + class AlazarProgram: def __init__(self): self._sample_factor = None self._masks = {} self.operations = [] self._total_length = None + self._auto_rearm_count = 1 def masks(self, mask_maker: Callable[[str, np.ndarray, np.ndarray], Mask]) -> List[Mask]: return [mask_maker(mask_name, *data) for mask_name, data in self._masks.items()] @@ -39,6 +43,21 @@ def total_length(self) -> int: def total_length(self, val: int): self._total_length = val + @property + def auto_rearm_count(self) -> int: + """This is passed to AlazarCard.startAcquisition. The card will (re-)arm automatically for this many times.""" + return self._auto_rearm_count + + @auto_rearm_count.setter + def auto_rearm_count(self, value: int): + trigger_count = int(value) + if trigger_count == 0: + raise ValueError("Trigger count of 0 is not supported in qupulse (yet) because tracking the number of " + "remaining triggers is too hard in case of infinity :(") + if not 0 < trigger_count < 2**64: + raise ValueError("Trigger count has to be in the interval [0, 2**64-1]") + self._auto_rearm_count = trigger_count + def clear_masks(self): self._masks.clear() @@ -192,6 +211,8 @@ def __init__(self, card, config: Optional[ScanlineConfiguration]=None): # defaults to self.__card.minimum_record_size self._buffer_strategy = None + self._remaining_auto_triggers = 0 + self._mask_prototypes = dict() # type: Dict self._registered_programs = defaultdict(AlazarProgram) # type: Dict[str, AlazarProgram] @@ -248,8 +269,13 @@ def register_operations(self, program_name: str, operations) -> None: self._registered_programs[program_name].operations = operations def arm_program(self, program_name: str) -> None: + logger.debug("Arming program %s on %r", program_name, self.__card) + to_arm = self._registered_programs[program_name] if self.update_settings or self.__armed_program is not to_arm: + logger.info("Arming %r by calling applyConfiguration. Update settings flag: %r", + self.__card, self.update_settings) + config = self.config config.masks, config.operations, total_record_size = self._registered_programs[program_name].iter( self._make_mask) @@ -287,7 +313,15 @@ def arm_program(self, program_name: str) -> None: self.update_settings = False self.__armed_program = to_arm - self.__card.startAcquisition(1) + + elif self.__armed_program is to_arm and self._remaining_auto_triggers > 0: + self._remaining_auto_triggers -= 1 + logger.info("Relying on atsaverage auto-arm with %d auto triggers remaining after this one", + self._remaining_auto_triggers) + return + + self.__card.startAcquisition(to_arm.auto_rearm_count) + self._remaining_auto_triggers = to_arm.auto_rearm_count - 1 def delete_program(self, program_name: str) -> None: self._registered_programs.pop(program_name)