diff --git a/MATLAB/+qc/awg_program.m b/MATLAB/+qc/awg_program.m index 91b06086..b2d37e11 100644 --- a/MATLAB/+qc/awg_program.m +++ b/MATLAB/+qc/awg_program.m @@ -1,320 +1,382 @@ function [program, bool, msg] = awg_program(ctrl, varargin) - % pulse_template can also be a pulse name. In that case the pulse is - % automatically loaded. - - global plsdata - hws = plsdata.awg.hardwareSetup; - daq = plsdata.daq.inst; - - program = struct(); - msg = ''; - bool = false; - - default_args = struct(... - 'program_name', 'default_program', ... - 'pulse_template', 'default_pulse', ... - 'parameters_and_dicts', {plsdata.awg.defaultParametersAndDicts}, ... - 'channel_mapping', plsdata.awg.defaultChannelMapping, ... - 'window_mapping', plsdata.awg.defaultWindowMapping, ... - 'global_transformation', plsdata.awg.globalTransformation, ... - 'add_marker', {plsdata.awg.defaultAddMarker}, ... - 'force_update', false, ... - 'verbosity', 10 ... - ); - a = util.parse_varargin(varargin, default_args); - - % --- add --------------------------------------------------------------- - if strcmp(ctrl, 'add') - [~, bool, msg] = qc.awg_program('fresh', qc.change_field(a, 'verbosity', 0)); - if ~bool || a.force_update - plsdata.awg.currentProgam = ''; - - % Deleting old program should not be necessary. In practice however, - % updating an existing program seemed to crash Matlab sometimes. - % qc.awg_program('remove', qc.change_field(a, 'verbosity', 10)); - - a.pulse_template = pulse_to_python(a.pulse_template); - [a.pulse_template, a.channel_mapping] = add_marker_if_not_empty(a.pulse_template, a.add_marker, a.channel_mapping); - - program = qc.program_to_struct(a.program_name, a.pulse_template, a.parameters_and_dicts, a.channel_mapping, a.window_mapping, a.global_transformation); - plsdata.awg.registeredPrograms.(a.program_name) = program; - - % Save AWG amplitude at instantiation and upload time so that the - % amplitude at the sample can be reconstructed at a later time - if ~isfield(plsdata.awg.registeredPrograms.(a.program_name), 'amplitudes_at_upload') - % program not online yet - plsdata.awg.registeredPrograms.(a.program_name).amplitudes_at_upload = zeros(1, 4); - end - - for ii = int64(1:4) - % query actual amplitude from qupulse - plsdata.awg.registeredPrograms.(a.program_name).amplitudes_at_upload(ii) = plsdata.awg.inst.amplitude(ii); - end - - if a.verbosity > 9 - 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); - - if a.verbosity > 9 - fprintf('took %.0fs\n', toc); - fprintf('Program ''%s'' is now being uploaded...', a.program_name); - tic - end - util.py.call_with_interrupt_check(py.getattr(hws, 'register_program'), program.program_name, instantiated_pulse, pyargs('update', py.True)); - - if a.verbosity > 9 - fprintf('took %.0fs\n', toc); - end - - if bool && a.force_update - msg = ' since update forced'; - else - msg = ''; - end - msg = sprintf('Program ''%s'' added%s', a.program_name, msg); - - bool = true; - else - program = plsdata.awg.registeredPrograms.(a.program_name); - end - - % --- arm --------------------------------------------------------------- - elseif strcmp(ctrl, 'arm') - % Call directly before trigger comes, otherwise you might encounter a - % trigger timeout. Also, call after daq_operations('add')! - [~, bool, msg] = qc.awg_program('present', qc.change_field(a, 'verbosity', 0)); - if bool - % Wait for AWG to stop playing pulse, otherwise this might lead to a - % trigger timeout since the DAQ is not necessarily configured for the - % whole pulse time and can return data before the AWG stops playing - % the pulse. - if ~isempty(plsdata.awg.currentProgam) - waitingTime = min(max(plsdata.awg.registeredPrograms.(plsdata.awg.currentProgam).pulse_duration + plsdata.awg.registeredPrograms.(plsdata.awg.currentProgam).added_to_pulse_duration - (now() - plsdata.awg.triggerStartTime)*24*60*60, 0), plsdata.awg.maxPulseWait); - if waitingTime == plsdata.awg.maxPulseWait - warning('Maximum waiting time ''plsdata.awg.maxPulseWait'' = %g s reached.\nIncrease if you experience problems with the data acquistion.', plsdata.awg.maxPulseWait); - end - pause(waitingTime); - % fprintf('Waited for %.3fs for pulse to complete\n', waitingTime); - end - - % No longer needed since bug has been fixed - % qc.workaround_4chan_program_errors(a); - - hws.arm_program(a.program_name); - - plsdata.awg.currentProgam = a.program_name; - bool = true; - msg = sprintf('Program ''%s'' armed', a.program_name); - end - - % --- arm --------------------------------------------------------------- - elseif strcmp(ctrl, 'arm global') - if ischar(plsdata.awg.armGlobalProgram) - globalProgram = plsdata.awg.armGlobalProgram; - elseif iscell(plsdata.awg.armGlobalProgram) - globalProgram = plsdata.awg.armGlobalProgram{1}; - plsdata.awg.armGlobalProgram = circshift(plsdata.awg.armGlobalProgram, -1); - else - globalProgram = a.program_name; - warning('Not using global program since plsdata.awg.armGlobalProgram must contain a char or a cell.'); - end - - % Set scan axis labels here if the global program armament is called by - % a prefn in smrun. Only for charge scans - if startsWith(globalProgram, 'charge_4chan') - f = figure(a.fig_id); +% pulse_template can also be a pulse name. In that case the pulse is +% automatically loaded. + +global plsdata +hws = plsdata.awg.hardwareSetup; +daq = plsdata.daq.inst; + +%TODO: de-hardcode: handle different awgs +awg = 'hdawg'; +nAwgChan = 8; + +program = struct(); +msg = ''; +bool = false; + +default_args = struct(... + 'program_name', 'default_program', ... + 'pulse_template', 'default_pulse', ... + 'parameters_and_dicts', {plsdata.awg.defaultParametersAndDicts}, ... + 'channel_mapping', plsdata.awg.defaultChannelMapping, ... + 'window_mapping', plsdata.awg.defaultWindowMapping, ... + 'global_transformation', plsdata.awg.globalTransformation, ... + 'add_marker', {plsdata.awg.defaultAddMarker}, ... + 'force_update', false, ... + 'verbosity', 10 ... + ); +a = util.parse_varargin(varargin, default_args); + +% --- add --------------------------------------------------------------- +if strcmp(ctrl, 'add') + [~, bool, msg] = qc.awg_program('fresh', qc.change_field(a, 'verbosity', 0)); + if ~bool || a.force_update + plsdata.awg.currentProgam = ''; + + % Deleting old program should not be necessary. In practice however, + % updating an existing program seemed to crash Matlab sometimes. + % qc.awg_program('remove', qc.change_field(a, 'verbosity', 10)); + + a.pulse_template = pulse_to_python(a.pulse_template); + [a.pulse_template, a.channel_mapping] = add_marker_if_not_empty(a.pulse_template, a.add_marker, a.channel_mapping); + + program = qc.program_to_struct(... + a.program_name, ... + a.pulse_template, ... + a.parameters_and_dicts, ... + ... Resolve mappings from abstracted to hardware-known + qc.resolve_mappings(a.channel_mapping, plsdata.awg.defaultChannelMapping), ... + qc.resolve_mappings(a.window_mapping, plsdata.awg.defaultWindowMapping), ... + map_transformation_channels(a.global_transformation, plsdata.awg.defaultChannelMapping)); + + plsdata.awg.registeredPrograms.(a.program_name) = program; + + % Save AWG amplitude at instantiation and upload time so that the + % amplitude at the sample can be reconstructed at a later time + if ~isfield(plsdata.awg.registeredPrograms.(a.program_name), 'amplitudes_at_upload') + % program not online yet + plsdata.awg.registeredPrograms.(a.program_name).amplitudes_at_upload = zeros(1, nAwgChan); + end + + %TODO: de-hardcode + if strcmp(awg, 'hdawg') + plsdata.awg.registeredPrograms.(a.program_name).amplitudes_at_upload = cell2mat(cell(plsdata.awg.inst.channel_tuples{1}.amplitudes)); + else + for ii = int64(1:nAwgChan) + % query actual amplitude from qupulse + plsdata.awg.registeredPrograms.(a.program_name).amplitudes_at_upload(ii) = plsdata.awg.inst.amplitude(ii); + end + end + + if a.verbosity > 9 + 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 ... + ); + + if a.verbosity > 9 + fprintf('took %.0fs\n', toc); + fprintf('Program ''%s'' is now being uploaded...', a.program_name); + tic + end + % Matlab crashes with this right now (12/20) 2020a, py37 TH + % util.py.call_with_interrupt_check(py.getattr(hws, 'register_program'), program.program_name, instantiated_pulse, pyargs('update', py.True)); + hws.register_program(program.program_name, instantiated_pulse, pyargs('update', py.True)); + + if a.verbosity > 9 + fprintf('took %.0fs\n', toc); + end + + if bool && a.force_update + msg = ' since update forced'; + else + msg = ''; + end + msg = sprintf('Program ''%s'' added%s', a.program_name, msg); + + bool = true; + else + program = plsdata.awg.registeredPrograms.(a.program_name); + end + + % --- arm --------------------------------------------------------------- +elseif strcmp(ctrl, 'arm') + % Call directly before trigger comes, otherwise you might encounter a + % trigger timeout. Also, call after daq_operations('add')! + [~, bool, msg] = qc.awg_program('present', qc.change_field(a, 'verbosity', 0)); + if bool + % Wait for AWG to stop playing pulse, otherwise this might lead to a + % trigger timeout since the DAQ is not necessarily configured for the + % whole pulse time and can return data before the AWG stops playing + % the pulse. + if ~isempty(plsdata.awg.currentProgam) + waitingTime = min(max(plsdata.awg.registeredPrograms.(plsdata.awg.currentProgam).pulse_duration + plsdata.awg.registeredPrograms.(plsdata.awg.currentProgam).added_to_pulse_duration - (now() - plsdata.awg.triggerStartTime)*24*60*60, 0), plsdata.awg.maxPulseWait); + if waitingTime == plsdata.awg.maxPulseWait + warning('Maximum waiting time ''plsdata.awg.maxPulseWait'' = %g s reached.\nIncrease if you experience problems with the data acquistion.', plsdata.awg.maxPulseWait); + end + pause(waitingTime); + % fprintf('Waited for %.3fs for pulse to complete\n', waitingTime); + end + + % No longer needed since bug has been fixed + % qc.workaround_4chan_program_errors(a); + + hws.arm_program(a.program_name); + + plsdata.awg.currentProgam = a.program_name; + bool = true; + msg = sprintf('Program ''%s'' armed', a.program_name); + end + + % --- arm --------------------------------------------------------------- +elseif strcmp(ctrl, 'arm global') + if ischar(plsdata.awg.armGlobalProgram) + globalProgram = plsdata.awg.armGlobalProgram; + elseif iscell(plsdata.awg.armGlobalProgram) + globalProgram = plsdata.awg.armGlobalProgram{1}; + plsdata.awg.armGlobalProgram = circshift(plsdata.awg.armGlobalProgram, -1); + else + globalProgram = a.program_name; + warning('Not using global program since plsdata.awg.armGlobalProgram must contain a char or a cell.'); + end + + % Set scan axis labels here if the global program armament is called by + % a prefn in smrun. Only for charge scans + if startsWith(globalProgram, 'charge_4chan') + f = figure(a.fig_id); + prog = globalProgram(1:strfind(globalProgram, '_d')-1); + + % always query the rf channels being swept so that they can be logged + % by a metafn. Doesn't allow for more than two gates + if contains(globalProgram, 'd12') + idx = [1 2]; + chans = {'RFQ1' 'RFQ2'}; + elseif contains(globalProgram, 'd23') + idx = [2 3]; + chans = {'RFQ2' 'RFQ3'}; + elseif contains(globalProgram, 'd34') + idx = [4 3]; + chans = {'RFQ4' 'RFQ3'}; + elseif contains(globalProgram, 'd14') + idx = [1 4]; + chans = {'RFQ1' 'RFQ4'}; + end + plsdata.awg.currentChannels = chans; + + % compare current AWG channel amplitudes to those at instantiation + % time + currentAmplitudes = plsdata.awg.currentAmplitudes; + uploadAmplitudes = plsdata.awg.registeredPrograms.(globalProgram).amplitudes_at_upload; + + updateRFchans = ~strcmp(globalProgram, a.program_name); + updateRFamps = ~all(currentAmplitudes == uploadAmplitudes); + + if updateRFchans || updateRFamps + + if updateRFamps + % Calculate amplitude at sample from the current amplitude, the + % amplitude at pulse instantiation time, and the pulse parameters + rng = [(plsdata.awg.registeredPrograms.(globalProgram).parameters_and_dicts{2}.([prog '___stop_x']) - ... + plsdata.awg.registeredPrograms.(globalProgram).parameters_and_dicts{2}.([prog '___start_x'])) ... + (plsdata.awg.registeredPrograms.(globalProgram).parameters_and_dicts{2}.([prog '___stop_y']) - ... + plsdata.awg.registeredPrograms.(globalProgram).parameters_and_dicts{2}.([prog '___start_y']))]; + amps = currentAmplitudes(idx) ./ uploadAmplitudes(idx) .* rng * 1e3 ./ 2; + end + for ax = f.Children(2:2:end)' + % Don't use xlabel(), ylabel() to stop matlab from updating the rest of the figure + if updateRFchans + ax.XLabel.String = replace_channel_in_string(ax.XLabel.String, chans{1}); + ax.YLabel.String = replace_channel_in_string(ax.YLabel.String, chans{2}); + end + if updateRFamps + ax.XLabel.String = replace_amplitude_in_string(ax.XLabel.String, amps(1)); + ax.YLabel.String = replace_amplitude_in_string(ax.YLabel.String, amps(2)); + ax.XTick = linspace(ax.XLim(1), ax.XLim(2), 3); + ax.YTick = linspace(ax.YLim(1), ax.YLim(2), 3); + ax.XTickLabel = sprintfc('%d', -1:1); + ax.YTickLabel = sprintfc('%d', -1:1); + end + end + end + end + % This code outputs the wrong pulses and isn't even faster + % - Then why is it still here? - TH + % registered_programs = util.py.py2mat(py.getattr(hws,'_registered_programs')); + % program = registered_programs.(globalProgram); + % awgs_to_upload_to = program{4}; + % dacs_to_arm = program{5}; + % for awgToUploadTo = awgs_to_upload_to + % awgToUploadTo{1}.arm(globalProgram); + % end + % for dacToArm = dacs_to_arm + % dacToArm{1}.arm_program(plsdata.awg.currentProgam); + % end + + qc.awg_program('arm', 'program_name', globalProgram, 'verbosity', a.verbosity, 'arm_global_for_workaround_4chan_program_errors', []); + + % --- remove ------------------------------------------------------------ +elseif strcmp(ctrl, 'remove') + % Arm the idle program so the program to be remove is not active by + % any chance (should not be needed - please test more thorougly whether it is needed) + plsdata.awg.inst.channel_tuples{1}.arm(py.None) + + [~, bool, msg] = qc.awg_program('present', qc.change_field(a, 'verbosity', 0)); + + if bool + bool = false; + + if isfield(plsdata.awg.registeredPrograms, a.program_name) + plsdata.awg.registeredPrograms = rmfield(plsdata.awg.registeredPrograms, a.program_name); + end + + try + hws.remove_program(a.program_name); + bool = true; + catch err + warning('The following error was encountered when running hardware_setup.remove_program.\nPlease debug AWG commands.\nThis might have to do with removing the current program.\n.Trying to recover by deleting operations.\n%s', err.getReport()); + qc.daq_operations('remove', 'program_name', a.program_name, 'verbosity', 10); + end + + msg = sprintf('Program ''%s'' removed', a.program_name); + end + + % --- clear all --------------------------------------------------------- +elseif strcmp(ctrl, 'clear all') % might take a long time + plsdata.awg.registeredPrograms = struct(); + program_names = fieldnames(util.py.py2mat(py.getattr(hws, '_registered_programs'))); + + bool = true; + for program_name = program_names.' + [~, boolNew] = qc.awg_program('remove', 'program_name', program_name{1}, 'verbosity', 10); + bool = bool & boolNew; + end + + if bool + msg = 'All programs cleared'; + else + msg = 'Error when trying to clear all progams'; + end + + % --- clear all fast ---------------------------------------------------- +elseif strcmp(ctrl, 'clear all fast') % fast but need to clear awg manually + plsdata.awg.hardwareSetup.clear_programs(); + py.getattr(daq, '_registered_programs').clear(); + plsdata.awg.registeredPrograms = struct(); + plsdata.awg.registeredPrograms.currentProgam = ''; + + % --- present ----------------------------------------------------------- +elseif strcmp(ctrl, 'present') % returns true if program is present + bool = py.list(hws.registered_programs.keys()).count(a.program_name) ~= 0; + if bool + msg = ''; + else + msg = 'not '; + end + msg = sprintf('Program ''%s'' %spresent', a.program_name, msg); + + % --- fresh ------------------------------------------------------------- +elseif strcmp(ctrl, 'fresh') % returns true if program is present and has not changed + [~, bool, msg] = qc.awg_program('present', qc.change_field(a, 'verbosity', 0)); + + if isfield(plsdata.awg.registeredPrograms, a.program_name) && bool + a.pulse_template = pulse_to_python(a.pulse_template); + [a.pulse_template, a.channel_mapping] = add_marker_if_not_empty(a.pulse_template, a.add_marker, a.channel_mapping); + + newProgram = qc.program_to_struct(... + a.program_name, ... + a.pulse_template, ... + a.parameters_and_dicts, ... + ... Resolve mappings from abstracted to hardware-known + qc.resolve_mappings(a.channel_mapping, plsdata.awg.defaultChannelMapping), ... + qc.resolve_mappings(a.window_mapping, plsdata.awg.defaultWindowMapping), ... + map_transformation_channels(a.global_transformation, plsdata.awg.defaultChannelMapping)); + + newProgram = qc.get_minimal_program(newProgram); + + awgProgram = plsdata.awg.registeredPrograms.(a.program_name); + awgProgram = qc.get_minimal_program(awgProgram); + + bool = isequal(newProgram, awgProgram); + + if bool + msg = ''; + else + msg = 'not '; + end + msg = sprintf('Program ''%s'' is %sup to date (fresh)', a.program_name, msg); + end + % if ~bool + % util.comparedata(newProgram, awgProgram); + % end + +end + +if a.verbosity > 9 + fprintf([msg '\n']); +end +end - % always query the rf channels being swept so that they can be logged - % by a metafn. - if startsWith(globalProgram, 'charge_4chan_d12') - idx = [1 2]; - chans = {'A' 'B'}; - elseif startsWith(globalProgram, 'charge_4chan_d23') - idx = [2 3]; - chans = {'B' 'C'}; - elseif startsWith(globalProgram, 'charge_4chan_d34') - idx = [4 3]; - chans = {'D' 'C'}; - elseif startsWith(globalProgram, 'charge_4chan_d14') - idx = [1 4]; - chans = {'A' 'D'}; - end - plsdata.awg.currentChannels = chans; - - % compare current AWG channel amplitudes to those at instantiation - % time - currentAmplitudes = plsdata.awg.currentAmplitudesHV; - uploadAmplitudes = plsdata.awg.registeredPrograms.(globalProgram).amplitudes_at_upload; - updateRFchans = ~strcmp(globalProgram, a.program_name); - updateRFamps = ~all(currentAmplitudes == uploadAmplitudes); - - if updateRFchans || updateRFamps - - if updateRFamps - % Calculate amplitude at sample from the current amplitude, the - % amplitude at pulse instantiation time, and the pulse parameters - rng = [(plsdata.awg.registeredPrograms.(globalProgram).parameters_and_dicts{2}.charge_4chan___stop_x - ... - plsdata.awg.registeredPrograms.(globalProgram).parameters_and_dicts{2}.charge_4chan___start_x) ... - (plsdata.awg.registeredPrograms.(globalProgram).parameters_and_dicts{2}.charge_4chan___stop_y - ... - plsdata.awg.registeredPrograms.(globalProgram).parameters_and_dicts{2}.charge_4chan___start_y)]; - amps = currentAmplitudes(idx)./uploadAmplitudes(idx).*rng*1e3; - end - for ax = f.Children(2:2:end)' - % Don't use xlabel(), ylabel() to stop matlab from updating the rest of the figure - if updateRFchans - ax.XLabel.String(3) = chans{1}; - ax.YLabel.String(3) = chans{2}; - end - if updateRFamps - ax.XLabel.String(6:9) = sprintf('%.1f', amps(1)); - ax.YLabel.String(6:9) = sprintf('%.1f', amps(2)); - ax.XTick = 0:10:100; - ax.YTick = 0:10:100; - ax.XTickLabel = sprintfc('%.1f', linspace(-amps(1)/2, amps(1)/2, 11)); - ax.YTickLabel = sprintfc('%.1f', linspace(-amps(2)/2, amps(2)/2, 11)); - end - end - end - end -% This code outputs the wrong pulses and isn't even faster -% - Then why is it still here? - TH -% registered_programs = util.py.py2mat(py.getattr(hws,'_registered_programs')); -% program = registered_programs.(globalProgram); -% awgs_to_upload_to = program{4}; -% dacs_to_arm = program{5}; -% for awgToUploadTo = awgs_to_upload_to -% awgToUploadTo{1}.arm(globalProgram); -% end -% for dacToArm = dacs_to_arm -% dacToArm{1}.arm_program(plsdata.awg.currentProgam); -% end - - qc.awg_program('arm', 'program_name', globalProgram, 'verbosity', a.verbosity, 'arm_global_for_workaround_4chan_program_errors', []); - - % --- remove ------------------------------------------------------------ - elseif strcmp(ctrl, 'remove') - % Arm the idle program so the program to be remove is not active by - % any chance (should not be needed - please test more thorougly whether it is needed) - plsdata.awg.inst.channel_pair_AB.arm(py.None); - plsdata.awg.inst.channel_pair_CD.arm(py.None); - - [~, bool, msg] = qc.awg_program('present', qc.change_field(a, 'verbosity', 0)); - - if bool - bool = false; - - if isfield(plsdata.awg.registeredPrograms, a.program_name) - plsdata.awg.registeredPrograms = rmfield(plsdata.awg.registeredPrograms, a.program_name); - end - - try - hws.remove_program(a.program_name); - bool = true; - catch err - warning('The following error was encountered when running hardware_setup.remove_program.\nPlease debug AWG commands.\nThis might have to do with removing the current program.\n.Trying to recover by deleting operations.\n%s', err.getReport()); - qc.daq_operations('remove', 'program_name', a.program_name, 'verbosity', 10); - end - - msg = sprintf('Program ''%s'' removed', a.program_name); - end - - % --- clear all --------------------------------------------------------- - elseif strcmp(ctrl, 'clear all') % might take a long time - plsdata.awg.registeredPrograms = struct(); - program_names = fieldnames(util.py.py2mat(py.getattr(hws, '_registered_programs'))); - - bool = true; - for program_name = program_names.' - [~, boolNew] = qc.awg_program('remove', 'program_name', program_name{1}, 'verbosity', 10); - bool = bool & boolNew; - end - - if bool - msg = 'All programs cleared'; - else - msg = 'Error when trying to clear all progams'; - end - - % --- clear all fast ---------------------------------------------------- - elseif strcmp(ctrl, 'clear all fast') % fast but need to clear awg manually - hws.registered_programs.clear(); - py.getattr(daq, '_registered_programs').clear(); - % --- present ----------------------------------------------------------- - elseif strcmp(ctrl, 'present') % returns true if program is present - bool = py.list(hws.registered_programs.keys()).count(a.program_name) ~= 0; - if bool - msg = ''; - else - msg = 'not '; - end - msg = sprintf('Program ''%s'' %spresent', a.program_name, msg); - - % --- fresh ------------------------------------------------------------- - elseif strcmp(ctrl, 'fresh') % returns true if program is present and has not changed - [~, bool, msg] = qc.awg_program('present', qc.change_field(a, 'verbosity', 0)); - - if isfield(plsdata.awg.registeredPrograms, a.program_name) && bool - a.pulse_template = pulse_to_python(a.pulse_template); - [a.pulse_template, a.channel_mapping] = add_marker_if_not_empty(a.pulse_template, a.add_marker, a.channel_mapping); - - newProgram = qc.program_to_struct(a.program_name, a.pulse_template, a.parameters_and_dicts, a.channel_mapping, a.window_mapping, a.global_transformation); - newProgram = qc.get_minimal_program(newProgram); - - awgProgram = plsdata.awg.registeredPrograms.(a.program_name); - awgProgram = qc.get_minimal_program(awgProgram); - - bool = isequal(newProgram, awgProgram); - - if bool - msg = ''; - else - msg = 'not '; - end - msg = sprintf('Program ''%s'' is %sup to date (fresh)', a.program_name, msg); - end - % if ~bool - % util.comparedata(newProgram, awgProgram); - % end - - end - - if a.verbosity > 9 - fprintf([msg '\n']); - end - - - - function pulse_template = pulse_to_python(pulse_template) - - if ischar(pulse_template) - pulse_template = qc.load_pulse(pulse_template); - end - - if isstruct(pulse_template) - pulse_template = qc.struct_to_pulse(pulse_template); - end - - + +if ischar(pulse_template) + pulse_template = qc.load_pulse(pulse_template); +end + +if isstruct(pulse_template) + pulse_template = qc.struct_to_pulse(pulse_template); +end +end + + function [pulse_template, channel_mapping] = add_marker_if_not_empty(pulse_template, add_marker, channel_mapping) - - if ~iscell(add_marker) - add_marker = {add_marker}; - end - - if ~isempty(add_marker) - marker_pulse = py.qctoolkit.pulses.PointPT({{0, 1},... - {py.getattr(pulse_template, 'duration'), 1}}, add_marker); - pulse_template = py.qctoolkit.pulses.AtomicMultiChannelPT(pulse_template, marker_pulse); - - for ii = 1:numel(add_marker) - channel_mapping.(args.add_marker{ii}) = add_marker{ii}; - end - end - - - \ No newline at end of file + +if ~iscell(add_marker) + add_marker = {add_marker}; +end + +if ~isempty(add_marker) + marker_values = py.dict(py.zip(add_marker, py.itertools.repeat(1))); + + pulse_template = py.qupulse.pulses.ParallelConstantChannelPT(pulse_template, marker_values); + + marker_pulse = py.qctoolkit.pulses.PointPT({{0, 1},... + {py.getattr(pulse_template, 'duration'), 1}}, add_marker); + pulse_template = py.qctoolkit.pulses.AtomicMultiChannelPT(pulse_template, marker_pulse); + + for ii = 1:numel(add_marker) + channel_mapping.(add_marker{ii}) = add_marker{ii}; + end +end +end + +function mat_trafo = map_transformation_channels(mat_trafo, mapping) +if istable(mat_trafo) + for row = mat_trafo.Properties.RowNames' + mat_trafo.Properties.RowNames{row{1}} = mapping.(row{1}); + end + for col = mat_trafo.Properties.VariableNames + mat_trafo.Properties.VariableNames{col{1}} = mapping.(col{1}); + end +end +end + +function s = replace_channel_in_string(s, newval) +s1 = split(s, ' '); +s1{1} = newval; +s = join(s1, ' '); +end + +function s = replace_amplitude_in_string(s, newval) +s1 = split(s, '('); +s2 = split(s1{2}, ' '); +s2{1} = sprintf('%.1f', newval); +s = join([s1(1) join(s2, ' ')], '('); +end diff --git a/MATLAB/+qc/conf_seq.m b/MATLAB/+qc/conf_seq.m index b58afab4..aac1d7a7 100644 --- a/MATLAB/+qc/conf_seq.m +++ b/MATLAB/+qc/conf_seq.m @@ -1,326 +1,345 @@ function scan = conf_seq(varargin) - % CONF_SEQ Create special-measure scans with inline qctoolkit pulses - % - % Only supports inline scans at the moment (could in principle arm a - % different program in each loop iteration using prefns but this is not - % implemented at the moment). - % - % Please only add aditional configfns directly before turning the AWG on - % since some other programs fetch information using configfn indices. - % - % This function gets only underscore arguments to be more consistend with - % qctoolkit. Other variables in this function are camel case. - % - % --- Outputs ------------------------------------------------------------- - % scan : special-measure scan - % - % --- Inputs -------------------------------------------------------------- - % varargin : name-value pairs or parameter struct. For a list of - % parameters see the struct defaultArgs below. - % - % ------------------------------------------------------------------------- - % (c) 2018/02 Pascal Cerfontaine (cerfontaine@physik.rwth-aachen.de) - - global plsdata - - alazarName = plsdata.daq.instSmName; - - % None of the arguments except pulse_template should contain any python - % objects to avoid erroneous saving when the scan is executed. - defaultArgs = struct(... - ... Pulses - 'program_name', 'default_program', ... - 'pulse_template', 'default_pulse', ... - 'parameters_and_dicts', {plsdata.awg.defaultParametersAndDicts}, ... - 'channel_mapping', plsdata.awg.defaultChannelMapping, ... - 'window_mapping', plsdata.awg.defaultWindowMapping, ... - 'add_marker', {plsdata.awg.defaultAddMarker}, ... - 'force_update', false, ... - ... - ... Pulse modification - 'pulse_modifier_args', struct(), ... % Additional arguments passed to the pulse_modifier_fn - 'pulse_modifier', false, ... % Automatically change the variable a (all input arguments) below, can be used to dynamically modify the pulse - 'pulse_modifier_fn', @tune.add_dbz_fid, ... % Can specify a custom function here which modifies the variable a (all input arguments) below - ... - ... 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 - ... - ... Measurements - 'operations', {plsdata.daq.defaultOperations}, ... - ... - ... Other - 'nrep', 10, ... % Numer of repetition of pulse - 'fig_id', 2000, ... - 'fig_position', [], ... - 'disp_ops', ' default', ... % Refers to operations: List of indices of operations to show - 'disp_dim', [1 2], ... % dimension of display - 'delete_getchans', [1], ... % Refers to getchans: Indices of getchans (including those generated by procfns) to delete after the scan is complete - 'procfn_ops', {{}}, ... % Refers to operations: One entry for each virtual channel, each cell entry has four or five element: fn, args, dim, operation index, (optional) identifier - 'saveloop', 0, ... % save every nth loop - 'useCustomCleanupFn', false, ... % If this flag is true - 'customCleanupFn', [], ... % clean up anything else you would like cleaned up - 'useCustomConfigFn', false, ... % If this flag is true - 'customConfigFn', [], ... % add a custom config function which is executed directly before the AWG is turned on - '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 - '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 - ); - a = util.parse_varargin(varargin, defaultArgs); - aOriginal = a; - - if a.pulse_modifier - try - a = feval(a.pulse_modifier_fn, a); % Add any proprietary function here - catch err - warning('Could not run pulse_modifier_fn successfully. Continuing as if pulse_modifier was false:\n%s', err.getReport()); - a = aOriginal; - end - end - - if ~ischar(a.pulse_template) && ~isstruct(a.pulse_template) - a.pulse_template = qc.pulse_to_struct(a.pulse_template); - end - - if numel(a.rf_sources) == 1 +% CONF_SEQ Create special-measure scans with inline qctoolkit pulses +% +% Only supports inline scans at the moment (could in principle arm a +% different program in each loop iteration using prefns but this is not +% implemented at the moment). +% +% Please only add aditional configfns directly before turning the AWG on +% since some other programs fetch information using configfn indices. +% +% This function gets only underscore arguments to be more consistend with +% qctoolkit. Other variables in this function are camel case. +% +% --- Outputs ------------------------------------------------------------- +% scan : special-measure scan +% +% --- Inputs -------------------------------------------------------------- +% varargin : name-value pairs or parameter struct. For a list of +% parameters see the struct defaultArgs below. +% +% ------------------------------------------------------------------------- +% (c) 2018/02 Pascal Cerfontaine (cerfontaine@physik.rwth-aachen.de) + +global plsdata + +alazarName = plsdata.daq.instSmName; + +% None of the arguments except pulse_template should contain any python +% objects to avoid erroneous saving when the scan is executed. +defaultArgs = struct(... + ... Pulses + 'program_name', 'default_program', ... + 'pulse_template', 'default_pulse', ... + 'parameters_and_dicts', {plsdata.awg.defaultParametersAndDicts}, ... + 'channel_mapping', plsdata.awg.defaultChannelMapping, ... + 'window_mapping', plsdata.awg.defaultWindowMapping, ... + 'add_marker', {plsdata.awg.defaultAddMarker}, ... + 'force_update', false, ... + ... + ... Pulse modification + 'pulse_modifier_args', struct(), ... % Additional arguments passed to the pulse_modifier_fn + 'pulse_modifier', false, ... % Automatically change the variable a (all input arguments) below, can be used to dynamically modify the pulse + 'pulse_modifier_fn', @tune.add_dbz_fid, ... % Can specify a custom function here which modifies the variable a (all input arguments) below + ... + ... 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 + ... + ... Measurements + 'operations', {plsdata.daq.defaultOperations}, ... + ... + ... Other + 'nrep', 10, ... % Numer of repetition of pulse + 'fig_id', 2000, ... + 'fig_position', [], ... + 'disp_ops', ' default', ... % Refers to operations: List of indices of operations to show + 'disp_dim', [1 2], ... % dimension of display + 'delete_getchans', [1], ... % Refers to getchans: Indices of getchans (including those generated by procfns) to delete after the scan is complete + 'procfn_ops', {{}}, ... % Refers to operations: One entry for each virtual channel, each cell entry has four or five element: fn, args, dim, operation index, (optional) identifier + 'saveloop', 0, ... % save every nth loop + 'useCustomCleanupFn', false, ... % If this flag is true + 'customCleanupFn', [], ... % clean up anything else you would like cleaned up + 'useCustomConfigFn', false, ... % If this flag is true + 'customConfigFn', [], ... % add a custom config function which is executed directly before the AWG is turned on + '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 + 'defer_markers', true, ... % defer markers to the pulse (ie let qupulse set them) + '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 + ); +a = util.parse_varargin(varargin, defaultArgs); +aOriginal = a; + +if a.pulse_modifier + try + a = feval(a.pulse_modifier_fn, a); % Add any proprietary function here + catch err + warning('Could not run pulse_modifier_fn successfully. Continuing as if pulse_modifier was false:\n%s', err.getReport()); + a = aOriginal; + end +end + +if ~ischar(a.pulse_template) && ~isstruct(a.pulse_template) + a.pulse_template = qc.pulse_to_struct(a.pulse_template); +end + +if numel(a.rf_sources) == 1 a.rf_sources = [a.rf_sources a.rf_sources]; - end - - scan = struct('configfn', [], 'cleanupfn', [], 'loops', struct('prefn', [], 'metafn', [])); - - % Save file and arguments with which scan was created (not stricly necessary) - try - if ischar(aOriginal.pulse_modifier_fn) - scan.data.pulse_modifier_fn = fileread(which(aOriginal.pulse_modifier_fn)); - else - scan.data.pulse_modifier_fn = fileread(which(func2str(aOriginal.pulse_modifier_fn))); - end - catch err - warning('Could not load pulse_modifier_fn for saving in scan for reproducibility:\n%s', err.getReport()); - end - scan.data.conf_seq_fn = fileread([mfilename('fullpath') '.m']); - scan.data.conf_seq_args = aOriginal; - - % Configure channels - scan.loops(1).getchan = {'ATSV', 'time'}; - scan.loops(1).setchan = {'count'}; - scan.loops(1).ramptime = []; - scan.loops(1).npoints = a.nrep; - scan.loops(1).rng = []; - - nGetChan = numel(scan.loops(1).getchan); - nOperations = numel(a.operations); - - % Turn AWG outputs off if scan stops (even if due to error) - scan.configfn(end+1).fn = @qc.cleanupfn_awg; - scan.configfn(end).args = {}; - - % Turn RF sources off if scan stops (even if due to error) - if any(a.rf_sources) - scan.configfn(end+1).fn = @qc.cleanupfn_rf_sources; - scan.configfn(end).args = {}; - end - - % Alazar buffer strategy. Can be used to mitigate buffer artifacts. - scan.configfn(end+1).fn = @smaconfigwrap; - scan.configfn(end).args = [{@qc.set_alazar_buffer_strategy}, a.buffer_strategy]; - - % Configure AWG - % * Calling qc.awg_program('add', ...) makes sure the pulse is uploaded - % again if any parameters changed. - % * If dictionaries were passed as strings, this will automatically - % reload the dictionaries and thus use any changes made in the - % dictionaries in the meantime. - % * The original parameters are saved in scan.data.awg_program. This - % includes the pulse_template in json format and all dictionary - % entries at the time when the scan was executed. - % * If a python pulse_template was passed, this will still save - % correctly since it was converted into a Matlab struct above. - scan.configfn(end+1).fn = @smaconfigwrap_save_data; - scan.configfn(end).args = {'awg_program', @qc.awg_program, 'add', a}; - - % Configure Alazar operations - % * alazar.update_settings = py.True is automatically set. This results - % in reconfiguration of the Alazar which takes a long time. Thus this - % should only be done before a scan is started (i.e. in a configfn). - % * qc.dac_operations('add', a) also resets the virtual channel in - % smdata.inst(sminstlookup(alazarName)).data.virtual_channel. - scan.configfn(end+1).fn = @smaconfigwrap_save_data; - scan.configfn(end).args = {'daq_operations', @qc.daq_operations, 'add', a}; - - % Configure Alazar virtual channel - % * Set datadim of instrument correctly - % * Save operation lengths in scan.data - scan.configfn(end+1).fn = @smaconfigwrap_save_data; - scan.configfn(end).args = {'daq_operations_length', @qc.daq_operations, 'set length', a}; - - % Extract operation data from first channel ('ATSV') - % * Add procfns to scan, one for each operation - % * The configfn qc.conf_seq_procfn sets args and dim of the first n - % procfns, where n is the number of operations. This ensures that start - % and stop always use the correct lengths even if they have changed due - % to changes in pulse dictionaries. qc.conf_seq_procfn assumes that the - % field scan.data.daq_operations_length has been set dynamically by a - % previous configfn. - nGetChan = numel(scan.loops(1).getchan); - for p = 1:numel(a.operations) - scan.loops(1).procfn(nGetChan + p).fn(1) = struct( ... - 'fn', @(x, startInd, stopInd)( x(startInd:stopInd) ), ... - 'args', {{nan, nan}}, ... - 'inchan', 1, ... - 'outchan', nGetChan + p ... - ); - scan.loops(1).procfn(nGetChan + p).dim = nan; - end - scan.configfn(end+1).fn = @qc.conf_seq_procfn; - scan.configfn(end).args = {}; - - if any(a.rf_sources) - % Turn RF switches on - scan.configfn(end+1).fn = @smaconfigwrap; - scan.configfn(end).args = {@smset, 'RF1_on', double(a.rf_sources(1))}; - scan.configfn(end+1).fn = @smaconfigwrap; - scan.configfn(end).args = {@smset, 'RF2_on', double(a.rf_sources(2))}; - scan.configfn(end+1).fn = @smaconfigwrap; - scan.configfn(end).args = {@pause, 0.05}; % So RF sources definitely on - - % Turn RF switches off - % -> already done by qc.cleanupfn_rf_sources called above - end - - % Add custom variables for documentation purposes - scan.configfn(end+1).fn = @smaconfigwrap_save_data; - scan.configfn(end).args = {'custom_var', a.save_custom_var_fn, a.save_custom_var_args}; - - % Add custom cleanup fn - if a.useCustomCleanupFn && ~isempty(a.customCleanupFn) - scan.configfn(end+1).fn = a.customCleanupFn; - scan.configfn(end).args = {}; - end - - % Add custom config fn - if a.useCustomConfigFn && ~isempty(a.customConfigFn) - scan.configfn(end+1).fn = a.customConfigFn; - scan.configfn(end).args = {}; - end - - % Delete unnecessary data - scan.cleanupfn(end+1).fn = @qc.cleanupfn_delete_getchans; - scan.cleanupfn(end).args = {a.delete_getchans}; - - % Allow time logging - % * Update dummy instrument with current time so can get the current time - % using a getchan - scan.loops(1).prefn(end+1).fn = @smaconfigwrap; - scan.loops(1).prefn(end).args = {@(chan)(smset('time', now()))}; - - % Allow logging metadata - for i = 1:length(a.save_metadata_fns) - scan.loops(1).metafn(end+1).fn = @smaconfigwrap_save_metadata; - scan.loops(1).metafn(end).args = {a.save_metadata_fields{i}, a.save_metadata_fns{i}}; - end - - % Turn AWG on - scan.configfn(end+1).fn = @smaconfigwrap; - scan.configfn(end).args = {@awgctrl, 'on'}; - - % Run AWG channel pair 1 - % * Arm the program - % * Trigger the Alazar - % * Will later also trigger the RF switches - % * Will run both channel pairs automatically if they are synced - % which they should be by default. - % * Should be the last prefn so no other channels changed when - % measurement starts (really necessary?) - scan.loops(1).prefn(end+1).fn = @smaconfigwrap; - if ~a.arm_global - scan.loops(1).prefn(end).args = {@qc.awg_program, 'arm', qc.change_field(a, 'verbosity', a.verbosity-1)}; - else - scan.loops(1).prefn(end).args = {@qc.awg_program, 'arm global', qc.change_field(a, 'verbosity', a.verbosity-1)}; - end - scan.loops(1).prefn(end+1).fn = @smaconfigwrap; - scan.loops(1).prefn(end).args = {@awgctrl, 'run', 1}; - - % Get AWG information (not needed at the moment) - % [analogNames, markerNames, channels] = qc.get_awg_channels(); - % [programNames, programs] = qc.get_awg_programs(); - - % Default display - if strcmp(a.disp_ops, 'default') - a.disp_ops = 1:min(4, nOperations); - end - - % Add user procfns - if isfield(scan.loops(1), 'procfn') - nProcFn = numel(scan.loops(1).procfn); - else - nProcFn = 0; - end - for opInd = 1:numel(a.procfn_ops) % count through operations - inchan = nGetChan + a.procfn_ops{opInd}{4}; - scan.loops(1).procfn(end+1).fn(1) = struct( ... - 'fn', a.procfn_ops{opInd}{1}, ... - 'args', {a.procfn_ops{opInd}{2}}, ... - 'inchan', inchan, ... - 'outchan', nProcFn + opInd ... - ); - scan.loops(1).procfn(end).dim = a.procfn_ops{opInd}{3}; - if numel(a.procfn_ops{opInd}) >= 5 - scan.loops(1).procfn(end).identifier = a.procfn_ops{opInd}{5}; - end - end - - % Configure display - scan.figure = a.fig_id; - if ~isempty(a.fig_position) - scan.figpos = a.fig_position; - end - scan.disp = []; - for l = 1:length(a.disp_ops) - for d = a.disp_dim - scan.disp(end+1).loop = 1; - scan.disp(end).channel = nGetChan + a.disp_ops(l); - scan.disp(end).dim = d; - - if a.disp_ops(l) <= nOperations - opInd = a.disp_ops(l); - else - opInd = a.procfn_ops{a.disp_ops(l)-nOperations}{4}; - end - - % added new condition "numel(opInd) == 1" to check for several - % inchans, later they should get a proper title (marcel) - if numel(opInd) == 1 && opInd <= numel(a.operations) - scan.disp(end).title = prepare_title(sprintf(['%s: '], a.operations{opInd}{:})); - elseif numel(opInd) == 1 && length(a.procfn_ops{opInd - nOperations}) > 4 - scan.disp(end).title = prepare_title(sprintf(['%s: '], a.procfn_ops{opInd - nOperations}{5})); - else - scan.disp(end).title = ''; - end - end - end - - if a.saveloop > 0 - scan.saveloop = [1, a.saveloop]; - end - -end - - - +end + +scan = struct('configfn', [], 'cleanupfn', [], 'loops', struct('prefn', [], 'metafn', [])); + +% Save file and arguments with which scan was created (not stricly necessary) +try + if ischar(aOriginal.pulse_modifier_fn) + scan.data.pulse_modifier_fn = fileread(which(aOriginal.pulse_modifier_fn)); + else + scan.data.pulse_modifier_fn = fileread(which(func2str(aOriginal.pulse_modifier_fn))); + end +catch err + warning('Could not load pulse_modifier_fn for saving in scan for reproducibility:\n%s', err.getReport()); +end +scan.data.conf_seq_fn = fileread([mfilename('fullpath') '.m']); +scan.data.conf_seq_args = aOriginal; + +% Configure channels +scan.loops(1).getchan = {'ATSV', 'time'}; +scan.loops(1).setchan = {'count'}; +scan.loops(1).ramptime = []; +scan.loops(1).npoints = a.nrep; +scan.loops(1).rng = []; + +nGetChan = numel(scan.loops(1).getchan); +nOperations = numel(a.operations); + +% Turn AWG outputs off if scan stops (even if due to error) +scan.configfn(end+1).fn = @qc.cleanupfn_awg; +scan.configfn(end).args = {}; + +% Turn RF sources off if scan stops (even if due to error) +if any(a.rf_sources) + scan.configfn(end+1).fn = @qc.cleanupfn_rf_sources; + scan.configfn(end).args = {}; +end + +% Alazar buffer strategy. Can be used to mitigate buffer artifacts. +scan.configfn(end+1).fn = @smaconfigwrap; +scan.configfn(end).args = [{@qc.set_alazar_buffer_strategy}, a.buffer_strategy]; + +% Configure AWG +% * Calling qc.awg_program('add', ...) makes sure the pulse is uploaded +% again if any parameters changed. +% * If dictionaries were passed as strings, this will automatically +% reload the dictionaries and thus use any changes made in the +% dictionaries in the meantime. +% * The original parameters are saved in scan.data.awg_program. This +% includes the pulse_template in json format and all dictionary +% entries at the time when the scan was executed. +% * If a python pulse_template was passed, this will still save +% correctly since it was converted into a Matlab struct above. +scan.configfn(end+1).fn = @smaconfigwrap_save_data; +scan.configfn(end).args = {'awg_program', @qc.awg_program, 'add', a}; + +% Configure Alazar operations +% * alazar.update_settings = py.True is automatically set. This results +% in reconfiguration of the Alazar which takes a long time. Thus this +% should only be done before a scan is started (i.e. in a configfn). +% * qc.dac_operations('add', a) also resets the virtual channel in +% smdata.inst(sminstlookup(alazarName)).data.virtual_channel. +scan.configfn(end+1).fn = @smaconfigwrap_save_data; +scan.configfn(end).args = {'daq_operations', @qc.daq_operations, 'add', a}; + +% Configure Alazar virtual channel +% * Set datadim of instrument correctly +% * Save operation lengths in scan.data +scan.configfn(end+1).fn = @smaconfigwrap_save_data; +scan.configfn(end).args = {'daq_operations_length', @qc.daq_operations, 'set length', a}; + +% Extract operation data from first channel ('ATSV') +% * Add procfns to scan, one for each operation +% * The configfn qc.conf_seq_procfn sets args and dim of the first n +% procfns, where n is the number of operations. This ensures that start +% and stop always use the correct lengths even if they have changed due +% to changes in pulse dictionaries. qc.conf_seq_procfn assumes that the +% field scan.data.daq_operations_length has been set dynamically by a +% previous configfn. +nGetChan = numel(scan.loops(1).getchan); +for p = 1:numel(a.operations) + scan.loops(1).procfn(nGetChan + p).fn(1) = struct( ... + 'fn', @(x, startInd, stopInd)( x(startInd:stopInd) ), ... + 'args', {{nan, nan}}, ... + 'inchan', 1, ... + 'outchan', nGetChan + p ... + ); + scan.loops(1).procfn(nGetChan + p).dim = nan; +end +scan.configfn(end+1).fn = @qc.conf_seq_procfn; +scan.configfn(end).args = {}; + +if any(a.rf_sources) + % Turn RF switches on + scan.configfn(end+1).fn = @smaconfigwrap; + scan.configfn(end).args = {@smset, 'RF1_on', double(a.rf_sources(1))}; + scan.configfn(end+1).fn = @smaconfigwrap; + scan.configfn(end).args = {@smset, 'RF2_on', double(a.rf_sources(2))}; + scan.configfn(end+1).fn = @smaconfigwrap; + scan.configfn(end).args = {@pause, 0.05}; % So RF sources definitely on + + % Turn RF switches off + % -> already done by qc.cleanupfn_rf_sources called above +end + +if a.defer_markers + % Set markers to be read from pulse + scan.configfn(end+1).fn = @smaconfigwrap; + scan.configfn(end).args = {@smset, 'AlazarTrig', Inf}; + scan.configfn(end+1).fn = @smaconfigwrap; + scan.configfn(end).args = {@smset, 'AWGSwitch1', Inf}; + scan.configfn(end+1).fn = @smaconfigwrap; + scan.configfn(end).args = {@smset, 'AWGSwitch2', Inf}; + + % .. and restore previous state + scan.cleanupfn(end+1).fn = @smaconfigwrap; + scan.cleanupfn(end).args = {@smset, 'AlazarTrig', tune.smget_mat('AlazarTrig')}; + scan.cleanupfn(end+1).fn = @smaconfigwrap; + scan.cleanupfn(end).args = {@smset, 'AWGSwitch1', tune.smget_mat('AWGSwitch1')}; + scan.cleanupfn(end+1).fn = @smaconfigwrap; + scan.cleanupfn(end).args = {@smset, 'AWGSwitch2', tune.smget_mat('AWGSwitch2')}; +end + +% Add custom variables for documentation purposes +scan.configfn(end+1).fn = @smaconfigwrap_save_data; +scan.configfn(end).args = {'custom_var', a.save_custom_var_fn, a.save_custom_var_args}; + +% Add custom cleanup fn +if a.useCustomCleanupFn && ~isempty(a.customCleanupFn) + scan.configfn(end+1).fn = a.customCleanupFn; + scan.configfn(end).args = {}; +end + +% Add custom config fn +if a.useCustomConfigFn && ~isempty(a.customConfigFn) + scan.configfn(end+1).fn = a.customConfigFn; + scan.configfn(end).args = {}; +end + +% Delete unnecessary data +scan.cleanupfn(end+1).fn = @qc.cleanupfn_delete_getchans; +scan.cleanupfn(end).args = {a.delete_getchans}; + +% Allow time logging +% * Update dummy instrument with current time so can get the current time +% using a getchan +scan.loops(1).prefn(end+1).fn = @smaconfigwrap; +scan.loops(1).prefn(end).args = {@(chan)(smset('time', now()))}; + +% Allow logging metadata +for i = 1:length(a.save_metadata_fns) + scan.loops(1).metafn(end+1).fn = @smaconfigwrap_save_metadata; + scan.loops(1).metafn(end).args = {a.save_metadata_fields{i}, a.save_metadata_fns{i}}; +end + +% Turn AWG on +scan.configfn(end+1).fn = @smaconfigwrap; +scan.configfn(end).args = {@awgctrl, 'on'}; + +% Run AWG channel pair 1 +% * Arm the program +% * Trigger the Alazar +% * Will later also trigger the RF switches +% * Will run both channel pairs automatically if they are synced +% which they should be by default. +% * Should be the last prefn so no other channels changed when +% measurement starts (really necessary?) +scan.loops(1).prefn(end+1).fn = @smaconfigwrap; +if ~a.arm_global + scan.loops(1).prefn(end).args = {@qc.awg_program, 'arm', qc.change_field(a, 'verbosity', a.verbosity-1)}; +else + scan.loops(1).prefn(end).args = {@qc.awg_program, 'arm global', qc.change_field(a, 'verbosity', a.verbosity-1)}; +end +scan.loops(1).prefn(end+1).fn = @smaconfigwrap; +scan.loops(1).prefn(end).args = {@awgctrl, 'run', 1}; + +% Get AWG information (not needed at the moment) +% [analogNames, markerNames, channels] = qc.get_awg_channels(); +% [programNames, programs] = qc.get_awg_programs(); + +% Default display +if strcmp(a.disp_ops, 'default') + a.disp_ops = 1:min(4, nOperations); +end + +% Add user procfns +if isfield(scan.loops(1), 'procfn') + nProcFn = numel(scan.loops(1).procfn); +else + nProcFn = 0; +end +for opInd = 1:numel(a.procfn_ops) % count through operations + inchan = nGetChan + a.procfn_ops{opInd}{4}; + scan.loops(1).procfn(end+1).fn(1) = struct( ... + 'fn', a.procfn_ops{opInd}{1}, ... + 'args', {a.procfn_ops{opInd}{2}}, ... + 'inchan', inchan, ... + 'outchan', nProcFn + opInd ... + ); + scan.loops(1).procfn(end).dim = a.procfn_ops{opInd}{3}; + if numel(a.procfn_ops{opInd}) >= 5 + scan.loops(1).procfn(end).identifier = a.procfn_ops{opInd}{5}; + end +end + +% Configure display +scan.figure = a.fig_id; +if ~isempty(a.fig_position) + scan.figpos = a.fig_position; +end +scan.disp = []; +for l = 1:length(a.disp_ops) + for d = a.disp_dim + scan.disp(end+1).loop = 1; + scan.disp(end).channel = nGetChan + a.disp_ops(l); + scan.disp(end).dim = d; + + if a.disp_ops(l) <= nOperations + opInd = a.disp_ops(l); + else + opInd = a.procfn_ops{a.disp_ops(l)-nOperations}{4}; + end + + % added new condition "numel(opInd) == 1" to check for several + % inchans, later they should get a proper title (marcel) + if numel(opInd) == 1 && opInd <= numel(a.operations) + scan.disp(end).title = prepare_title(sprintf(['%s: '], a.operations{opInd}{:})); + elseif numel(opInd) == 1 && length(a.procfn_ops{opInd - nOperations}) > 4 + scan.disp(end).title = prepare_title(sprintf(['%s: '], a.procfn_ops{opInd - nOperations}{5})); + else + scan.disp(end).title = ''; + end + end +end + +if a.saveloop > 0 + scan.saveloop = [1, a.saveloop]; +end + +end + + + function str = prepare_title(str) - - str = strrep(str, '_', ' '); - str = str(1:end-2); - - str = strrep(str, 'RepAverage', 'RSA'); - str = strrep(str, 'Downsample', 'DS'); - str = strrep(str, 'Qubit', 'Q'); - -end \ No newline at end of file + +str = strrep(str, '_', ' '); +str = str(1:end-2); + +str = strrep(str, 'RepAverage', 'RSA'); +str = strrep(str, 'Downsample', 'DS'); +str = strrep(str, 'Qubit', 'Q'); + +end diff --git a/MATLAB/+qc/daq_operations.m b/MATLAB/+qc/daq_operations.m index cb1bc51b..d595eb90 100644 --- a/MATLAB/+qc/daq_operations.m +++ b/MATLAB/+qc/daq_operations.m @@ -20,6 +20,8 @@ % --- add --------------------------------------------------------------- if strcmp(ctrl, 'add') % output is operations % Call before qc.awg_program('arm')! + + smdata.inst(instIndex).data.virtual_channel = struct( ... 'operations', {a.operations} ... @@ -57,10 +59,10 @@ elseif strcmp(ctrl, 'get length') % output is length % Operations need to have been added beforehand mask_maker = py.getattr(daq, '_make_mask'); - masks = util.py.py2mat(py.getattr(daq, '_registered_programs')); - masks = util.py.py2mat(masks.(a.program_name)); - operations = masks.operations; - masks = util.py.py2mat(masks.masks(mask_maker)); + programs = util.py.py2mat(py.getattr(daq, '_registered_programs')); + program = util.py.py2mat(programs.(a.program_name)); + operations = program.operations; + masks = util.py.py2mat(program.masks(mask_maker)); maskIdsFromOperations = cellfun(@(x)(char(x.maskID)), util.py.py2mat(operations), 'UniformOutput', false); @@ -81,6 +83,13 @@ error('daq_operations assumes that all masks should have the same length if using ComputeRepAverageDefinition.'); end output(k) = n(1); + elseif isa(operations{k}, 'py.atsaverage._atsaverage_release.ComputeChunkedAverageDefinition') + chunk_size = operations{k}.chunkSize; + window_lengths = double(py.numpy.max(py.numpy.asarray(masks{maskIndex}.length, py.numpy.dtype('u8')))); + max_chunks_per_window = ceil(window_lengths / chunk_size); + n_windows = size(masks{maskIndex}.length); + + output(k) = max_chunks_per_window * n_windows; else error('Operation ''%s'' not yet implemented', class(operations{k})); end diff --git a/MATLAB/+qc/instantiate_pulse.m b/MATLAB/+qc/instantiate_pulse.m index 9e91c397..2d3bdf85 100644 --- a/MATLAB/+qc/instantiate_pulse.m +++ b/MATLAB/+qc/instantiate_pulse.m @@ -1,44 +1,46 @@ function instantiated_pulse = instantiate_pulse(pulse, varargin) - % Plug in parameters - - if qc.is_instantiated_pulse(pulse) - instantiated_pulse = pulse; - - else - default_args = struct(... - 'parameters', py.None, ... - 'channel_mapping', py.None, ... - 'window_mapping' , py.None, ... - 'global_transformation', [], ... - 'to_single_waveform', py.set() ... - ); - - args = util.parse_varargin(varargin, default_args); - - args.channel_mapping = replace_empty_with_pynone(args.channel_mapping); - args.window_mapping = replace_empty_with_pynone(args.window_mapping); - args.global_transformation = qc.to_transformation(args.global_transformation); - - kwargs = pyargs( ... - 'parameters' , args.parameters, ... - 'channel_mapping', args.channel_mapping, ... - 'measurement_mapping' , args.window_mapping, ... - 'global_transformation', args.global_transformation, ... - 'to_single_waveform', args.to_single_waveform ... - ); - - instantiated_pulse = util.py.call_with_interrupt_check(py.getattr(pulse, 'create_program'), kwargs); - end -end +% Plug in parameters +if qc.is_instantiated_pulse(pulse) + instantiated_pulse = pulse; + +else + default_args = struct(... + 'parameters', py.None, ... + 'channel_mapping', py.None, ... + 'window_mapping' , py.None, ... + 'global_transformation', [], ... + 'to_single_waveform', py.set() ... + ); + + args = util.parse_varargin(varargin, default_args); + + args.channel_mapping = replace_empty_with_pynone(args.channel_mapping); + args.window_mapping = replace_empty_with_pynone(args.window_mapping); + args.global_transformation = qc.to_transformation(args.global_transformation); + + kwargs = pyargs( ... + 'parameters' , args.parameters, ... + 'channel_mapping', args.channel_mapping, ... + 'measurement_mapping' , args.window_mapping, ... + 'global_transformation', args.global_transformation, ... + 'to_single_waveform', args.to_single_waveform ... + ); + + % Matlab crashes with this right now (12/20) 2020a, py37 TH + % instantiated_pulse = util.py.call_with_interrupt_check(py.getattr(pulse, 'create_program'), kwargs); + instantiated_pulse = pulse.create_program(kwargs); + +end +end function mappingStruct = replace_empty_with_pynone(mappingStruct) - - for fn = fieldnames(mappingStruct)' - if isempty(mappingStruct.(fn{1})) - mappingStruct.(fn{1}) = py.None; - end - end - + +for fn = fieldnames(mappingStruct)' + if isempty(mappingStruct.(fn{1})) + mappingStruct.(fn{1}) = py.None; + end +end + end diff --git a/MATLAB/+qc/operations_to_python.m b/MATLAB/+qc/operations_to_python.m index 5790f0b5..a1e7f25e 100644 --- a/MATLAB/+qc/operations_to_python.m +++ b/MATLAB/+qc/operations_to_python.m @@ -21,6 +21,10 @@ pyOp = py.atsaverage.operations.RepAverage(args{:}); case 'RepeatedDownsample' pyOp = py.atsaverage.operations.RepeatedDownsample(args{:}); + case 'ChunkedAverage' + assert(numel(args) == 3); + args{3} = py.int(args{3}); + pyOp = py.atsaverage.operations.ChunkedAverage(args{:}); otherwise error('Operation %s not recognized', operations{k}{1}); end diff --git a/MATLAB/+qc/plot_pulse_4chan.m b/MATLAB/+qc/plot_pulse_4chan.m index 61680fb9..d8356681 100644 --- a/MATLAB/+qc/plot_pulse_4chan.m +++ b/MATLAB/+qc/plot_pulse_4chan.m @@ -5,76 +5,76 @@ % (c) 2018/06 Pascal Cerfontaine (cerfontaine@physik.rwth-aachen.de) defaultArgs = struct(... - 'charge_diagram_data_structs', {{}}, ... Should contain 2 structs in a cell array with fields x, y - ... and data, where data{1} contains the charge diagram data - 'plot_charge_diagram', true, ... - 'lead_points_cell', {{}}, ... Should contain a cell with a lead_points entry for each qubit - 'special_points_cell', {{}}, ... Should contain a cell with a special_points entry for each qubit - 'channels', {{'W', 'X', 'Y', 'Z'}}, ... - 'measurements', {{'A', 'A', 'B', 'B'}}, ... - 'markerChannels', {{'M1', '', 'M2', ''}} ... - ); + 'charge_diagram_data_structs', {{}}, ... Should contain 2 structs in a cell array with fields x, y + ... and data, where data{1} contains the charge diagram data + 'plot_charge_diagram', true, ... + 'lead_points_cell', {{}}, ... Should contain a cell with a lead_points entry for each qubit + 'special_points_cell', {{}}, ... Should contain a cell with a special_points entry for each qubit + 'channels', {{'W', 'X', 'Y', 'Z'}}, ... + 'measurements', {{'A', 'A', 'B', 'B'}}, ... + 'markerChannels', {{'M1', '', 'M2', ''}} ... + ); args = util.parse_varargin(varargin, defaultArgs); - - - for chrgInd = 1:2 - k = chrgInd + double(chrgInd==2); - q = 4 - k; - - if numel(args.charge_diagram_data_structs) >= chrgInd - args.charge_diagram_data = args.charge_diagram_data_structs{chrgInd}; - args.charge_diagram_data = {args.charge_diagram_data.x, args.charge_diagram_data.y, args.charge_diagram_data.data{1}}; - else - args.charge_diagram_data = {}; - end - - if numel(args.lead_points_cell) >= chrgInd - args.lead_points = args.lead_points_cell{chrgInd}; - else - args.lead_points = {}; - end - - if numel(args.special_points_cell) >= chrgInd - args.special_points = args.special_points_cell{chrgInd}; - else - args.special_points = {}; - end - - args.charge_diagram = args.channels(k:k+1); - if args.plot_charge_diagram - args.subplots = [220+k 220+k+1]; - else - args.subplots = [210+chrgInd]; - end - args.clear_fig = k==1; - [t, channels, measurements, instantiatedPulse] = qc.plot_pulse(pulse, args); - xlabel(args.channels(k)); - ylabel(args.channels(k+1)); - - if args.plot_charge_diagram - subplot(args.subplots(1)); - end - set(findall(gca, 'DisplayName', sprintf('Chan: %s', args.channels{q})), 'Visible', 'off'); - set(findall(gca, 'DisplayName', sprintf('Chan: %s', args.channels{q+1})), 'Visible', 'off'); - set(findall(gca, 'DisplayName', sprintf('Chan: %s', args.markerChannels{q})), 'Visible', 'off'); - set(findall(gca, 'DisplayName', sprintf('Chan: %s', args.markerChannels{q+1})), 'Visible', 'off'); - set(findall(gca, 'DisplayName', sprintf('Meas: %s', args.measurements{q})), 'Visible', 'off'); - set(findall(gca, 'DisplayName', sprintf('Meas: %s', args.measurements{q+1})), 'Visible', 'off'); - - [hLeg, hObj] = legend(gca); - for l = 1:numel(hLeg.String) - if strcmp(hLeg.String{l}, sprintf('Chan: %s', args.channels{q})) || ... - strcmp(hLeg.String{l}, sprintf('Chan: %s', args.channels{q+1})) || ... - strcmp(hLeg.String{l}, sprintf('Chan: %s', args.markerChannels{q})) || ... - strcmp(hLeg.String{l}, sprintf('Chan: %s', args.markerChannels{q+1})) || ... - strcmp(hLeg.String{l}, sprintf('Meas: %s', args.measurements{q})) || ... - strcmp(hLeg.String{l}, sprintf('Meas: %s', args.measurements{q+1})) - hLeg.String{l} = ''; - end - findobj(hObj, 'type', 'line'); - set(hObj, 'lineWidth', 2); - end - - - + + +for chrgInd = 1:2 + k = chrgInd + double(chrgInd==2); + q = 4 - k; + + if numel(args.charge_diagram_data_structs) >= chrgInd + args.charge_diagram_data = args.charge_diagram_data_structs{chrgInd}; + args.charge_diagram_data = {args.charge_diagram_data.x, args.charge_diagram_data.y, args.charge_diagram_data.data{1}}; + else + args.charge_diagram_data = {}; + end + + if numel(args.lead_points_cell) >= chrgInd + args.lead_points = args.lead_points_cell{chrgInd}; + else + args.lead_points = {}; + end + + if numel(args.special_points_cell) >= chrgInd + args.special_points = args.special_points_cell{chrgInd}; + else + args.special_points = {}; + end + + args.charge_diagram = args.channels(k:k+1); + if args.plot_charge_diagram + args.subplots = [220+k 220+k+1]; + else + args.subplots = [210+chrgInd]; + end + args.clear_fig = k==1; + [t, channels, measurements, instantiatedPulse] = qc.plot_pulse(pulse, args); + xlabel(args.channels(k)); + ylabel(args.channels(k+1)); + + if args.plot_charge_diagram + subplot(args.subplots(1)); + end + set(findall(gca, 'DisplayName', sprintf('Chan: %s', args.channels{q})), 'Visible', 'off'); + set(findall(gca, 'DisplayName', sprintf('Chan: %s', args.channels{q+1})), 'Visible', 'off'); + set(findall(gca, 'DisplayName', sprintf('Chan: %s', args.markerChannels{q})), 'Visible', 'off'); + set(findall(gca, 'DisplayName', sprintf('Chan: %s', args.markerChannels{q+1})), 'Visible', 'off'); + set(findall(gca, 'DisplayName', sprintf('Meas: %s', args.measurements{q})), 'Visible', 'off'); + set(findall(gca, 'DisplayName', sprintf('Meas: %s', args.measurements{q+1})), 'Visible', 'off'); + + [hLeg, hObj] = legend(gca); + for l = 1:numel(hLeg.String) + if strcmp(hLeg.String{l}, sprintf('Chan: %s', args.channels{q})) || ... + strcmp(hLeg.String{l}, sprintf('Chan: %s', args.channels{q+1})) || ... + strcmp(hLeg.String{l}, sprintf('Chan: %s', args.markerChannels{q})) || ... + strcmp(hLeg.String{l}, sprintf('Chan: %s', args.markerChannels{q+1})) || ... + strcmp(hLeg.String{l}, sprintf('Meas: %s', args.measurements{q})) || ... + strcmp(hLeg.String{l}, sprintf('Meas: %s', args.measurements{q+1})) + hLeg.String{l} = ''; + end + findobj(hObj, 'type', 'line'); + set(hObj, 'lineWidth', 2); + end + + + end \ No newline at end of file diff --git a/MATLAB/+qc/resolve_mappings.m b/MATLAB/+qc/resolve_mappings.m new file mode 100644 index 00000000..9cf6c22e --- /dev/null +++ b/MATLAB/+qc/resolve_mappings.m @@ -0,0 +1,52 @@ +function resolved = resolve_mappings(varargin) +% Takes a variable number of parameter mappings (structs) and resolves them +% sequentially. Eg. if the first maps A to B and the second B to C, returns +% a mapping A to C. Goes left-to-right (first to last). +% +% >> qc.resolve_mappings(struct('a', 'b'), struct('b', 'c', 'x', 'y'), ... +% struct('c', 'd'), struct('x', 'z', 'asdf', 'jkl'), ... +% struct('z', 'a', 'bla', 'foo')) +% +% Warning: Field clash. Value to the right supercedes. +% > In qc.resolve_mappings (line 14) +% +% ans = +% +% struct with fields: +% +% a: 'd' +% x: 'a' +% asdf: 'jkl' +% bla: 'foo' +% ========================================================================= +resolved = varargin{1}; +varargin = varargin(2:end); + +while ~isempty(varargin) + visited_fields = {}; + for f = fieldnames(resolved)' + field = f{1}; + value = resolved.(field); + if isfield(varargin{1}, field) + warning('Field clash. Value to the right supercedes.') + resolved.(field) = varargin{1}.(field); + visited_fields = [visited_fields field]; + elseif isfield(varargin{1}, value) + resolved.(field) = varargin{1}.(value); + visited_fields = [visited_fields value]; + end + end + + % Add unchanged new fields from varargin{1} + for f = fieldnames(varargin{1})' + field = f{1}; + value = varargin{1}.(field); + if ~ismember(visited_fields, field) + resolved.(field) = value; + end + end + + varargin = varargin(2:end); +end + +end \ No newline at end of file diff --git a/MATLAB/+qc/setup_alazar_measurements.m b/MATLAB/+qc/setup_alazar_measurements.m index 1642a72b..1b18e0c9 100644 --- a/MATLAB/+qc/setup_alazar_measurements.m +++ b/MATLAB/+qc/setup_alazar_measurements.m @@ -47,8 +47,8 @@ nQubits = args.nQubits; nMeasPerQubit = args.nMeasPerQubit; - py.setattr(hws, '_measurement_map', py.dict); - py.setattr(daq, '_mask_prototypes', py.dict); + py.getattr(hws, '_measurement_map').clear(); + 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 @@ -81,12 +81,12 @@ % Q1 A1 qubitIndex, measIndex, hwChannel, auxFlag1, secondQubitIndex, secondHwChannel, auxFlag2 add_meas_and_mask(1, m, 2, false, 1, 0 , true); - % Q1 A2 qubitIndex, measIndex, hwChannel, auxFlag1, secondQubitIndex, secondHwChannel, auxFlag2 + % Q1 A2 qubitIndex, measIndex, hwChannel, auxFlag1, secondQubitIndex, secondHwChannel, auxFlag2 add_meas_and_mask(1, m, 2, false, 2, 1 , true); % Q2 A1 qubitIndex, measIndex, hwChannel, auxFlag1, secondQubitIndex, secondHwChannel, auxFlag2 add_meas_and_mask(2, m, 3, false, 1, 0 , true); - % Q2 A2 qubitIndex, measIndex, hwChannel, auxFlag1, secondQubitIndex, secondHwChannel, auxFlag2 + % Q2 A2 qubitIndex, measIndex, hwChannel, auxFlag1, secondQubitIndex, secondHwChannel, auxFlag2 add_meas_and_mask(2, m, 3, false, 2, 1 , true); end end diff --git a/qupulse/_program/_loop.py b/qupulse/_program/_loop.py index 83da2837..e488946d 100644 --- a/qupulse/_program/_loop.py +++ b/qupulse/_program/_loop.py @@ -208,10 +208,6 @@ def _get_repr(self, first_prefix, other_prefixes) -> Generator[str, None, None]: yield from cast(Loop, elem)._get_repr(other_prefixes + ' ->', other_prefixes + ' ') def __repr__(self) -> str: - is_circular = is_tree_circular(self) - if is_circular: - return '{}: Circ {}'.format(id(self), is_circular) - str_len = 0 repr_list = [] for sub_repr in self._get_repr('', ''): diff --git a/qupulse/_program/seqc.py b/qupulse/_program/seqc.py index 52d6042a..90e54fdc 100644 --- a/qupulse/_program/seqc.py +++ b/qupulse/_program/seqc.py @@ -101,7 +101,7 @@ def markers_ch1(self): @property def markers_ch2(self): - return np.bitwise_and(self.marker_data, 0b1100) + return np.right_shift(np.bitwise_and(self.marker_data, 0b1100), 2) @classmethod def from_sampled(cls, ch1: Optional[np.ndarray], ch2: Optional[np.ndarray], diff --git a/qupulse/expressions.py b/qupulse/expressions.py index 5a191040..6acf514d 100644 --- a/qupulse/expressions.py +++ b/qupulse/expressions.py @@ -63,6 +63,8 @@ def _parse_evaluate_numeric_result(self, if isinstance(result, tuple): result = numpy.array(result) if isinstance(result, numpy.ndarray): + if result.shape == (): + return self._parse_evaluate_numeric_result(result[()], call_arguments) if issubclass(result.dtype.type, allowed_types): return result else: diff --git a/qupulse/hardware/awgs/zihdawg.py b/qupulse/hardware/awgs/zihdawg.py index 135deefe..2ec388f2 100644 --- a/qupulse/hardware/awgs/zihdawg.py +++ b/qupulse/hardware/awgs/zihdawg.py @@ -9,6 +9,7 @@ import hashlib import argparse import re +import abc try: import zhinst.ziPython @@ -334,7 +335,8 @@ def _initialize_awg_module(self): self.awg_module.set('awgModule/device', self.device.serial) self.awg_module.set('awgModule/index', self.awg_group_index) self.awg_module.execute() - self._elf_manager = ELFManager(self.awg_module) + # use the SimpleELFManager because the Caching one fails if a program is updated + self._elf_manager = SimpleELFManager(self.awg_module) def disconnect_group(self): """Disconnect this group from device so groups of another size can be used""" @@ -447,12 +449,24 @@ def upload(self, name: str, self._start_compile_and_upload() def _start_compile_and_upload(self): + if self._upload_generator is not None: + # TODO: figure out a way to cleanly abort upload + logger.info("Waiting for upload currently in progress before a new one can be started.") + self._wait_for_compile_and_upload() + assert self._upload_generator is None self._upload_generator = self._elf_manager.compile_and_upload(self._required_seqc_source) + logger.debug(f"_start_compile_and_upload: %r", next(self._upload_generator, "Finished")) def _wait_for_compile_and_upload(self): - for state in self._upload_generator: - logger.debug("wait_for_compile_and_upload: %r", state) - time.sleep(.1) + if self._upload_generator is None: + logger.info("Not compile or upload in progress") + return + try: + for state in self._upload_generator: + logger.debug("wait_for_compile_and_upload: %r", state) + time.sleep(.1) + finally: + self._upload_generator = None self._uploaded_seqc_source = self._required_seqc_source logger.debug("AWG %d: wait_for_compile_and_upload has finished", self.awg_group_index) @@ -499,7 +513,10 @@ def arm(self, name: Optional[str]) -> None: `waitDigTrigger` to do that. """ if self._required_seqc_source != self._uploaded_seqc_source: + if self._upload_generator is None: + self._start_compile_and_upload() self._wait_for_compile_and_upload() + assert self._required_seqc_source == self._uploaded_seqc_source self.user_register(self._program_manager.Constants.TRIGGER_REGISTER, 0) @@ -524,7 +541,9 @@ def arm(self, name: Optional[str]) -> None: # this is a workaround for problems in the past and should be re-thought in case of a re-write for ch_pair in self.device.channel_tuples: ch_pair._wait_for_compile_and_upload() - self.enable(True) + + if name is not None: + self.enable(True) def run_current_program(self) -> None: """Run armed program.""" @@ -637,7 +656,7 @@ def offsets(self) -> Tuple[float, ...]: return tuple(map(self.device.offset, self._channels())) -class ELFManager: +class ELFManager(metaclass=abc.ABCMeta): class AWGModule: def __init__(self, awg_module: zhinst.ziPython.AwgModule): """Provide an easily mockable interface to the zhinst AwgModule object""" @@ -673,6 +692,14 @@ def compiler_source_file(self) -> str: def compiler_source_file(self, source_file: str): self._module.set('compiler/sourcefile', source_file) + @property + def compiler_source_string(self) -> str: + return self._module.getString('compiler/sourcestring') + + @compiler_source_string.setter + def compiler_source_string(self, source_string: str): + self._module.set('compiler/sourcestring', source_string) + @property def compiler_upload(self) -> bool: """auto upload after compiling""" @@ -707,20 +734,8 @@ def index(self) -> int: return self._module.getInt('index') def __init__(self, awg_module: zhinst.ziPython.AwgModule): - """This class organizes compiling and uploading of compiled programs. The source code file is named based on the - code hash to cache compilation results. This requires that the waveform names are unique. - - The compilation and upload itself are done asynchronously by zhinst.ziPython. To avoid spawning a useless - thread for updating the status the method :py:meth:`~ELFManager.compile_and_upload` returns a generator which - talks to the undelying library when needed.""" self.awg_module = self.AWGModule(awg_module) - # automatically upload after successful compilation - self.awg_module.compiler_upload = True - - self._compile_job = None # type: Optional[Union[str, Tuple[str, int, str]]] - self._upload_job = None # type: Optional[Union[Tuple[str, float], Tuple[str, int]]] - def clear(self): """Deletes all files with a SHA512 hash name""" src_regex = re.compile(r'[a-z0-9]{128}\.seqc') @@ -747,6 +762,84 @@ def _source_hash(source_string: str) -> str: # use utf-16 because str is UTF16 on most relevant machines (Windows) return hashlib.sha512(bytes(source_string, 'utf-16')).hexdigest() + @abc.abstractmethod + def compile_and_upload(self, source_string: str) -> Generator[str, str, None]: + """The function returns a generator that yields the current state of the progress. The generator is empty iff + the upload is complete. An exception is raised if there is an error. + + To abort send 'abort' to the generator. (not implemented :P) + + Example: + >>> my_source = 'playWave("my_wave");' + >>> for state in elf_manager.compile_and_upload(my_source): + ... print('Current state:', state) + ... time.sleep(1) + + Args: + source_string: Source code to compile + + Returns: + Generator object that needs to be consumed + """ + + +class SimpleELFManager(ELFManager): + def __init__(self, awg_module: zhinst.ziPython.AwgModule): + """This class organizes compiling and uploading of compiled programs. The source code file is named based on the + code hash to cache compilation results. This requires that the waveform names are unique. + + The compilation and upload itself are done asynchronously by zhinst.ziPython. To avoid spawning a useless + thread for updating the status the method :py:meth:`~ELFManager.compile_and_upload` returns a generator which + talks to the undelying library when needed.""" + super().__init__(awg_module) + + def compile_and_upload(self, source_string: str) -> Generator[str, str, None]: + self.awg_module.compiler_upload = True + self.awg_module.compiler_source_string = source_string + + while True: + status, msg = self.awg_module.compiler_status + if status == - 1: + yield 'compiling' + elif status == 0: + break + elif status == 1: + raise HDAWGCompilationException(msg) + elif status == 2: + logger.warning("Compiler warings: %s", msg) + break + else: + raise RuntimeError("Unexpected status", status, msg) + + while True: + status_int, progress = self.awg_module.elf_status + if progress == 1.0: + break + elif status_int == 1: + HDAWGUploadException(self.awg_module.compiler_status) + else: + yield 'uploading @ %d%%' % (100*progress) + + +class CachingELFManager(ELFManager): + def __init__(self, awg_module: zhinst.ziPython.AwgModule): + """FAILS TO UPLOAD THE CORRECT ELF FOR SOME REASON + TODO: Investigat + + This class organizes compiling and uploading of compiled programs. The source code file is named based on the + code hash to cache compilation results. This requires that the waveform names are unique. + + The compilation and upload itself are done asynchronously by zhinst.ziPython. To avoid spawning a useless + thread for updating the status the method :py:meth:`~ELFManager.compile_and_upload` returns a generator which + talks to the undelying library when needed.""" + super().__init__(awg_module) + + # automatically upload after successful compilation + self.awg_module.compiler_upload = True + + self._compile_job = None # type: Optional[Union[str, Tuple[str, int, str]]] + self._upload_job = None # type: Optional[Union[Tuple[str, float], Tuple[str, int]]] + def _update_compile_job_status(self): """Store current compile status in self._compile_job.""" compiler_start = self.awg_module.compiler_start @@ -755,8 +848,7 @@ def _update_compile_job_status(self): elif isinstance(self._compile_job, str): if compiler_start: - # compilation is running - pass + logger.debug("Compiler is running.") else: compiler_status, status_string = self.awg_module.compiler_status diff --git a/qupulse/hardware/dacs/alazar.py b/qupulse/hardware/dacs/alazar.py index 6d4b12ab..58ee64fa 100644 --- a/qupulse/hardware/dacs/alazar.py +++ b/qupulse/hardware/dacs/alazar.py @@ -1,4 +1,4 @@ -from typing import Dict, Any, Optional, Tuple, List, Iterable, Callable, Sequence +from typing import Dict, Any, Optional, Tuple, List, Iterable, Callable, Sequence, Mapping, FrozenSet from collections import defaultdict import copy import warnings @@ -6,6 +6,7 @@ import functools import abc import logging +import types import numpy as np @@ -27,6 +28,11 @@ def __init__(self): self.operations = [] self._total_length = None self._auto_rearm_count = 1 + self._buffer_strategy = None + + @property + def mask_names(self) -> FrozenSet[str]: + return frozenset(self._masks.keys()) 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()] @@ -68,6 +74,24 @@ def clear_masks(self): def sample_factor(self) -> Optional[TimeType]: return self._sample_factor + def _mask_to_samples(self, begins, lengths) -> Tuple[np.ndarray, np.ndarray]: + assert self._sample_factor is not None + assert begins.dtype == np.float and lengths.dtype == np.float + + sample_factor = self._sample_factor + + begins = np.rint(begins * float(sample_factor)).astype(dtype=np.uint64) + lengths = np.floor_divide(lengths * float(sample_factor.numerator), float(sample_factor.denominator)).astype( + dtype=np.uint64) + + sorting_indices = np.argsort(begins) + begins = begins[sorting_indices] + lengths = lengths[sorting_indices] + + begins.flags.writeable = False + lengths.flags.writeable = False + return begins, lengths + def set_measurement_mask(self, mask_name: str, sample_factor: TimeType, begins: np.ndarray, lengths: np.ndarray) -> Tuple[np.ndarray, np.ndarray]: """Raise error if sample factor has changed""" @@ -77,27 +101,44 @@ def set_measurement_mask(self, mask_name: str, sample_factor: TimeType, elif sample_factor != self._sample_factor: raise RuntimeError('class AlazarProgram has already masks with differing sample factor') - assert begins.dtype == float and lengths.dtype == float + self._masks[mask_name] = self._mask_to_samples(begins, lengths) - # optimization potential here (hash input?) - begins = np.rint(begins * float(sample_factor)).astype(dtype=np.uint64) - lengths = np.floor_divide(lengths * float(sample_factor.numerator), float(sample_factor.denominator)).astype(dtype=np.uint64) + return begins, lengths - sorting_indices = np.argsort(begins) - begins = begins[sorting_indices] - lengths = lengths[sorting_indices] + def update_measurement_masks(self, sample_factor: TimeType, + masks: Mapping[str, Tuple[np.ndarray, np.ndarray]]) -> bool: + """ - begins.flags.writeable = False - lengths.flags.writeable = False + Args: + sample_factor: + masks: - self._masks[mask_name] = begins, lengths + Returns: + True if the measurement masks changed + """ + self._sample_factor = sample_factor - return begins, lengths + update_required = False + old_masks = self._masks.copy() + self._masks.clear() + for mask_name, (begins, lengths) in masks.items(): + begins, lengths = self._mask_to_samples(begins, lengths) + if not update_required: + # check if the masks changed + if not np.array_equal(old_masks.pop(mask_name, None), (begins, lengths)): + update_required = True + self._masks[mask_name] = (begins, lengths) + + if len(old_masks) != 0: + # there are removed masks + update_required = True + + return update_required def iter(self, mask_maker): yield self.masks(mask_maker) yield self.operations - yield self.total_length + yield self._total_length def gcd_set(data): @@ -186,11 +227,14 @@ def calculate_acquisition_properties(self, buffer_length_divisor: int) -> Tuple[int, int]: gcd = None for mask in masks: + if mask.begin.size < 2: + continue c_gcd = gcd_set(np.unique(np.diff(mask.begin.as_ndarray()))) if gcd is None: gcd = c_gcd else: gcd = math.gcd(gcd, c_gcd) + gcd = gcd or 1 buffer_size = max((gcd // buffer_length_divisor) * buffer_length_divisor, buffer_length_divisor) mtl = self.minimum_total_length(masks) @@ -295,6 +339,8 @@ def _make_mask(self, mask_id: str, begins, lengths) -> Mask: def set_measurement_mask(self, program_name, mask_name, begins, lengths) -> Tuple[np.ndarray, np.ndarray]: sample_factor = TimeType.from_fraction(int(self.default_config.captureClockConfiguration.numeric_sample_rate(self.card.model)), 10**9) + if program_name is self.__armed_program: + self.update_settings = True return self._registered_programs[program_name].set_measurement_mask(mask_name, sample_factor, begins, lengths) def register_measurement_windows(self, @@ -303,13 +349,14 @@ def register_measurement_windows(self, program = self._registered_programs[program_name] sample_factor = TimeType.from_fraction(int(self.default_config.captureClockConfiguration.numeric_sample_rate(self.card.model)), 10 ** 9) - program.clear_masks() - - for mask_name, (begins, lengths) in windows.items(): - program.set_measurement_mask(mask_name, sample_factor, begins, lengths) + if program.update_measurement_masks(sample_factor, windows) and program is self.__armed_program: + self.update_settings = True def register_operations(self, program_name: str, operations) -> None: - self._registered_programs[program_name].operations = operations + program = self._registered_programs[program_name] + if program is self.__armed_program and program.operations != operations: + self.update_settings = True + program.operations = operations def arm_program(self, program_name: str) -> None: logger.debug("Arming program %s on %r", program_name, self.__card) @@ -339,17 +386,22 @@ def arm_program(self, program_name: str) -> None: raise RuntimeError("Masks were registered with a different sample rate {}!={}".format( self._registered_programs[program_name].sample_factor, sample_factor)) + if total_record_size is None: + buffer_size, total_record_size = self.buffer_strategy.calculate_acquisition_properties( + config.masks, + self.record_size_factor) + config.aimedBufferSize = buffer_size + + else: + warnings.warn("Hardcoded record size for program", DeprecationWarning) + assert total_record_size > 0 - # extend the total record size to be a multiple of record_size_factor - record_size_factor = self.record_size_factor - total_record_size = (((total_record_size - 1) // record_size_factor) + 1) * record_size_factor + if config.totalRecordSize: + warnings.warn("Total record size of config is ignored", DeprecationWarning) + + config.totalRecordSize = total_record_size - if config.totalRecordSize == 0: - config.totalRecordSize = total_record_size - elif config.totalRecordSize < total_record_size: - raise ValueError('specified total record size is smaller than needed {} < {}'.format(config.totalRecordSize, - total_record_size)) self.__card.applyConfiguration(config, True) self._current_config = config @@ -374,8 +426,8 @@ def clear(self) -> None: self.__armed_program = None @property - def mask_prototypes(self) -> Dict[str, Tuple[int, str]]: - return self._mask_prototypes + def mask_prototypes(self) -> Mapping[str, Tuple[int, str]]: + return types.MappingProxyType(self._mask_prototypes) def register_mask_for_channel(self, mask_id: str, hw_channel: int, mask_type='auto') -> None: """ @@ -389,7 +441,11 @@ def register_mask_for_channel(self, mask_id: str, hw_channel: int, mask_type='au raise ValueError('{} is not a valid hw channel'.format(hw_channel)) if mask_type not in ('auto', 'cross_buffer', None): raise NotImplementedError('Currently only can do cross buffer mask') - self._mask_prototypes[mask_id] = (hw_channel, mask_type) + val = (hw_channel, mask_type) + if self._mask_prototypes.get(mask_id, None) != val: + self._mask_prototypes[mask_id] = val + if self.__armed_program is not None and mask_id in self._registered_programs[self.__armed_program].mask_names: + self.update_settings = True def measure_program(self, channels: Iterable[str]) -> Dict[str, np.ndarray]: """ diff --git a/qupulse/pulses/arithmetic_pulse_template.py b/qupulse/pulses/arithmetic_pulse_template.py index 1606c753..b96008ae 100644 --- a/qupulse/pulses/arithmetic_pulse_template.py +++ b/qupulse/pulses/arithmetic_pulse_template.py @@ -180,7 +180,8 @@ def __init__(self, arithmetic_operator: str, rhs: Union[PulseTemplate, ExpressionLike, Mapping[ChannelID, ExpressionLike]], *, - identifier: Optional[str] = None): + identifier: Optional[str] = None, + registry: Optional[PulseRegistryType] = None): """ Args: @@ -222,6 +223,7 @@ def __init__(self, self._scalar = scalar self._arithmetic_operator = arithmetic_operator + self._register(registry=registry) @staticmethod def _parse_operand(operand: Union[ExpressionLike, Mapping[ChannelID, ExpressionLike]], diff --git a/qupulse/pulses/function_pulse_template.py b/qupulse/pulses/function_pulse_template.py index 1913c869..4dd64bec 100644 --- a/qupulse/pulses/function_pulse_template.py +++ b/qupulse/pulses/function_pulse_template.py @@ -99,11 +99,15 @@ def build_waveform(self, if channel is None: return None + duration = self.__duration_expression.evaluate_with_exact_rationals(parameters) + if duration == 0: + return None + if 't' in parameters: parameters = {k: v for k, v in parameters.items() if k != 't'} expression = self.__expression.evaluate_symbolic(substitutions=parameters) - duration = self.__duration_expression.evaluate_with_exact_rationals(parameters) + return FunctionWaveform(expression=expression, duration=duration, diff --git a/qupulse/pulses/time_reversal_pulse_template.py b/qupulse/pulses/time_reversal_pulse_template.py index 83997477..d3840d66 100644 --- a/qupulse/pulses/time_reversal_pulse_template.py +++ b/qupulse/pulses/time_reversal_pulse_template.py @@ -42,9 +42,9 @@ def integral(self) -> Dict[ChannelID, ExpressionScalar]: def _internal_create_program(self, *, parent_loop: Loop, **kwargs) -> None: inner_loop = Loop() self._inner._internal_create_program(parent_loop=inner_loop, **kwargs) - inner_loop.reverse_inplace() - - parent_loop.append_child(inner_loop) + if inner_loop != Loop(): + inner_loop.reverse_inplace() + parent_loop.append_child(inner_loop) def build_waveform(self, *args, **kwargs) -> Optional[Waveform]: diff --git a/tests/_program/seqc_tests.py b/tests/_program/seqc_tests.py index 02e791ec..9bb04dd3 100644 --- a/tests/_program/seqc_tests.py +++ b/tests/_program/seqc_tests.py @@ -1049,4 +1049,134 @@ def test_full_run_with_dynamic_rate_reduction(self): wait(IDLE_WAIT_CYCLES); } }""" - self.assertEqual(expected_program, seqc_program) \ No newline at end of file + self.assertEqual(expected_program, seqc_program) + + def test_shuttle_pulse(self): + from qupulse.pulses import PointPT, RepetitionPT, SequencePT, MappingPT, ForLoopPT + import sympy + + sample_rate, n_segments, t_hold = sympy.sympify('sample_rate, n_segments, t_hold') + t_segment = n_segments / sample_rate + + # segment = PointPT([(0, 'v_hold'), + # (t_segment, 'v_hold')], + # channel_names=('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'), + # ) + + # hold = RepetitionPT(segment, t_hold // t_segment, + # measurements=[('M', 3*t_segment, t_hold - 2*t_segment)]) + + segment = PointPT([(0, 'v_hold'), + (t_segment, 'v_hold')], + channel_names=('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'), + measurements=[('M', t_segment / 3, t_segment / 3 * 2)] + ) + + hold = RepetitionPT(segment, t_hold // t_segment) + + shuttle_segment = PointPT([(0, 'v_hold'), + (t_segment, 'v_hold')], + channel_names=('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H') + ) + + shuttle_hold = RepetitionPT(shuttle_segment, t_hold // t_segment, + measurements=[('M', 't_hold/3', 't_hold/3')] + ) + + # instantiate square pulse for loading: + + load_full = SequencePT( + (hold, {'v_hold': 'V_load_0', 't_hold': 't_load_0'}), + (hold, {'v_hold': 'V_load_1', 't_hold': 't_load_1'}), + (hold, {'v_hold': 'V_load_2', 't_hold': 't_load_2'}), + ) + + # generalize sine waves for shuttling: + + f_shuttle, t_shuttle, amp_shuttle, res_shuttle = sympy.simplify( + 'f_shuttle, t_shuttle, amp_shuttle, res_shuttle') + + shuttle_body = MappingPT(shuttle_hold, parameter_mapping={ + 't_hold': res_shuttle, 'v_hold': 'amp_shuttle*cos(2*pi*i*res_shuttle*f_shuttle + phase)+offset'} + ) + + # instantiate pulse for shuttling in and out: + + shuttle_in = ForLoopPT(shuttle_body, 'i', 't_shuttle // res_shuttle') + shuttle_out = ForLoopPT(shuttle_body, 'i', ('t_shuttle // res_shuttle', 0, -1)) + + # read + + read_pls = SequencePT( + (hold, {'v_hold': 'V_read_0', 't_hold': 't_read_0'}), + (hold, {'v_hold': 'V_read_1', 't_hold': 't_read_1'}), + (hold, {'v_hold': 'V_read_2', 't_hold': 't_read_2'}), + ) + + # wait + wait_pls = MappingPT( + hold, parameter_mapping={'v_hold': 'V_wait', 't_hold': 't_wait'}, + ) + + only_shuttle = shuttle_in @ shuttle_out + + shuttle_amp = 150e-3 + shuttle_offset = -60e-3 + shuttle_amp_mask = np.array([1, 0.5, 1, 0.5, 0, 0, 0, 0]) + shuttle_offset_mask = np.array([1, 0, 1, 0, 1, 1, 0, 0]) + + shuttle_init_L2 = 440e-3 + shuttle_init_T = 0 + # shuttle_init_phase = -0.5*np.pi*np.append(np.array([3., 2., 1., 0., 2., 3.])-4*shuttle_init_T, np.ones(2)) + # shuttle_init_awg = shuttle_amp*np.multiply(shuttle_amp_mask,np.cos(shuttle_init_phase))+shuttle_offset*shuttle_offset_mask + shuttle_init_phase = -0.5 * np.pi * np.append(np.array([3., 2., 1., 0.]) - 4 * shuttle_init_T, np.ones(4)) + shuttle_init_awg = shuttle_amp * np.multiply(shuttle_amp_mask, np.cos( + shuttle_init_phase)) + shuttle_offset * shuttle_offset_mask + np.array( + [0., 0., 0., 0., -shuttle_offset - shuttle_amp * 0.75, 0., 0., 0.]) + + # %% + cmpst_fac_TRB1 = 1.7879 + + default_parameters = { + 't_load_0': 5e6, + 't_load_1': 25e6, + 't_load_2': 5e6, + 'V_load_0': shuttle_init_awg + np.append(np.zeros(4), [0.00, 0., 0.02 * cmpst_fac_TRB1, -0.02]), + 'V_load_1': shuttle_init_awg + np.append(np.zeros(4), [0.00, 0., -0.10 * cmpst_fac_TRB1, 0.01]), + 'V_load_2': shuttle_init_awg + np.append(np.zeros(4), [0.00, 0., 0.02 * cmpst_fac_TRB1, -0.02]), + 't_shuttle': 6 / 1e-7, + 'f_shuttle': 1e-7, + 'res_shuttle': 19200 * 5, + 'amp_shuttle': np.multiply(shuttle_amp_mask, shuttle_amp), + 'phase': shuttle_init_phase, + 'offset': np.multiply(shuttle_init_awg, shuttle_offset_mask) + np.append(np.zeros(4), + [0.00, 0., 0.02 * cmpst_fac_TRB1, + -0.02]), + 't_wait': 25e6, + 'V_wait': np.multiply(np.multiply(shuttle_amp_mask, shuttle_amp), np.cos( + 2 * np.pi * (1e8 // (19200 * 5)) * 19200 * 5 * 1e-8 + shuttle_init_phase)) + np.multiply( + shuttle_init_awg, shuttle_offset_mask) + np.append(np.zeros(4), + [0.00, 0., 0.02 * cmpst_fac_TRB1, -0.02]), + 't_read_0': 5e6, + 't_read_1': 25e6, + 't_read_2': 5e6, + 'V_read_0': shuttle_init_awg + np.append(np.zeros(4), [0.00, 0., 0.02 * cmpst_fac_TRB1, -0.020]), + 'V_read_1': shuttle_init_awg + np.append(np.zeros(4), [0.00, 0., -0.15 * cmpst_fac_TRB1, 0.015]), + 'V_read_2': shuttle_init_awg + np.append(np.zeros(4), [0.00, 0., 0.02 * cmpst_fac_TRB1, -0.020]), + 'sample_rate': 0.0125, + 'n_segments': 192, + } + + program = only_shuttle.create_program(parameters=default_parameters) + + manager = HDAWGProgramManager() + + manager.add_program('test', program, + ('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'), + (None,) * 16, (4.,)*8, (0.,)*8, (None,)*8, + default_parameters['sample_rate']) + + + + +