From 1f2c09c9841c6bd68435a4253b3a7bb0e3048b95 Mon Sep 17 00:00:00 2001 From: Simon Humpohl Date: Thu, 20 Jun 2024 16:23:49 +0200 Subject: [PATCH 01/10] Remove qctoolkit alias --- changes.d/841.removal | 1 + qctoolkit/__init__.py | 34 ---------------------------------- tests/qctoolkit_alias_tests.py | 16 ---------------- 3 files changed, 1 insertion(+), 50 deletions(-) create mode 100644 changes.d/841.removal delete mode 100644 qctoolkit/__init__.py delete mode 100644 tests/qctoolkit_alias_tests.py diff --git a/changes.d/841.removal b/changes.d/841.removal new file mode 100644 index 000000000..9ae7d7acf --- /dev/null +++ b/changes.d/841.removal @@ -0,0 +1 @@ +Remove qctoolkit alias for qupulse. \ No newline at end of file diff --git a/qctoolkit/__init__.py b/qctoolkit/__init__.py deleted file mode 100644 index 208c2083f..000000000 --- a/qctoolkit/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -# SPDX-FileCopyrightText: 2014-2024 Quantum Technology Group and Chair of Software Engineering, RWTH Aachen University -# -# SPDX-License-Identifier: GPL-3.0-or-later - -"""This is a (hopefully temporary) alias package to not break existing code. If you know a better way please change""" -import sys -import re -import pkgutil -import importlib -import warnings -import logging - -qupulse = importlib.import_module('qupulse') -sys.modules[__name__] = qupulse - -aliased = {} - -""" import all subpackages and submodules to assert that -from qupulse.pulse import TablePT as T1 -from qctoolkit.pulse import TablePT as T2 -assert T1 is T2 -""" -for _, name, ispkg in pkgutil.walk_packages(qupulse.__path__, 'qupulse.'): - alias = re.sub('^qupulse.', '%s.' % __name__, name) - - try: - imported = importlib.import_module(name) - except ImportError: - warnings.warn('Could not import %s. The alias %s was NOT created.' % (name, alias)) - continue - sys.modules[alias] = imported - aliased[alias] = name - -logging.info('Created module aliases:', aliased) diff --git a/tests/qctoolkit_alias_tests.py b/tests/qctoolkit_alias_tests.py deleted file mode 100644 index 1ae446776..000000000 --- a/tests/qctoolkit_alias_tests.py +++ /dev/null @@ -1,16 +0,0 @@ -import unittest - - -class QctoolkitAliasTest(unittest.TestCase): - def test_alias(self): - import qctoolkit.pulses - import qupulse.pulses - - self.assertIs(qctoolkit.pulses, qupulse.pulses) - self.assertIs(qctoolkit.pulses.TablePT, qupulse.pulses.TablePT) - - def test_class_identity(self): - from qupulse.program.loop import Loop as Loop_qu - from qctoolkit.program.loop import Loop as Loop_qc - - self.assertIs(Loop_qc, Loop_qu) From 5732d2faefc5f73319eac6b25c253b360e652c08 Mon Sep 17 00:00:00 2001 From: Simon Humpohl Date: Fri, 21 Jun 2024 10:29:12 +0200 Subject: [PATCH 02/10] Remove qctoolkit test trigger --- .github/workflows/pythontest.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pythontest.yaml b/.github/workflows/pythontest.yaml index b9b6b82bf..20a265eb5 100644 --- a/.github/workflows/pythontest.yaml +++ b/.github/workflows/pythontest.yaml @@ -11,7 +11,6 @@ on: - '**' paths: - 'qupulse/**y' - - 'qctoolkit/**' - 'tests/**' - 'setup.*' - 'pyproject.toml' From 084c2077d47548e7000f1d4a165123af032c7487 Mon Sep 17 00:00:00 2001 From: Simon Humpohl Date: Fri, 21 Jun 2024 10:34:53 +0200 Subject: [PATCH 03/10] Adjust error on disabled qctoolkit import --- tests/serialization_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/serialization_tests.py b/tests/serialization_tests.py index 537e642c2..a49ae7e6c 100644 --- a/tests/serialization_tests.py +++ b/tests/serialization_tests.py @@ -736,7 +736,7 @@ def my_callable(): self.assertIs(finder['qctoolkit.asd'], my_callable) finder.qctoolkit_alias = False - with self.assertRaises(KeyError): + with self.assertRaises(ModuleNotFoundError): finder['qctoolkit.asd'] From 62a1be490fdb678acc37f85e998a8caa5e9c6492 Mon Sep 17 00:00:00 2001 From: Simon Humpohl Date: Thu, 23 Jan 2025 14:22:54 +0100 Subject: [PATCH 04/10] Remove qctoolkit serialization tests --- tests/serialization_tests.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/tests/serialization_tests.py b/tests/serialization_tests.py index a49ae7e6c..0a5d62181 100644 --- a/tests/serialization_tests.py +++ b/tests/serialization_tests.py @@ -726,19 +726,6 @@ def test_auto_import(self): finder['qupulse.pulses.table_pulse_template.TablePulseTemplate'] import_module.assert_not_called() - def test_qctoolkit_import(self): - def my_callable(): - pass - - finder = DeserializationCallbackFinder() - - finder['qupulse.asd'] = my_callable - self.assertIs(finder['qctoolkit.asd'], my_callable) - - finder.qctoolkit_alias = False - with self.assertRaises(ModuleNotFoundError): - finder['qctoolkit.asd'] - class SerializableMetaTests(unittest.TestCase): def test_native_deserializable(self): From 39bc99286a2636e2c5653370508f987190f148b9 Mon Sep 17 00:00:00 2001 From: Simon Humpohl Date: Thu, 23 Jan 2025 14:29:40 +0100 Subject: [PATCH 05/10] Remove MATLAB code --- MATLAB/+qc/AWGwatch.m | 21 -- MATLAB/+qc/AWGwatch/awgdisp_app.mlapp | Bin 130663 -> 0 bytes MATLAB/+qc/add_params_to_dict.m | 26 -- MATLAB/+qc/array2list.m | 9 - MATLAB/+qc/array2row.m | 9 - MATLAB/+qc/awg_program.m | 320 ----------------- MATLAB/+qc/awgdisp.m | 20 -- MATLAB/+qc/change_armed_program.m | 32 -- MATLAB/+qc/change_field.m | 3 - MATLAB/+qc/check_pulse_parameter_dependency.m | 64 ---- MATLAB/+qc/cleanupfn_awg.m | 9 - MATLAB/+qc/cleanupfn_delete_getchans.m | 7 - MATLAB/+qc/cleanupfn_rf_sources.m | 11 - MATLAB/+qc/compensate_channels.m | 90 ----- MATLAB/+qc/conf_seq.m | 326 ------------------ MATLAB/+qc/conf_seq_procfn.m | 15 - MATLAB/+qc/daq_operations.m | 125 ------- MATLAB/+qc/dict.m | 10 - MATLAB/+qc/dict_apply_globals.m | 27 -- MATLAB/+qc/dict_to_parameter_struct.m | 18 - MATLAB/+qc/disp_awg_seq_table.m | 95 ----- MATLAB/+qc/disp_dict.m | 31 -- MATLAB/+qc/get_alazar_measurements.m | 32 -- MATLAB/+qc/get_awg_channels.m | 16 - MATLAB/+qc/get_awg_memory.m | 44 --- MATLAB/+qc/get_awg_programs.m | 10 - MATLAB/+qc/get_awg_seq_table.m | 13 - MATLAB/+qc/get_dnp_pulse_duration.m | 55 --- MATLAB/+qc/get_dnp_pulse_duration_offset.m | 55 --- MATLAB/+qc/get_minimal_program.m | 24 -- MATLAB/+qc/get_optimal_awg_time.m | 24 -- MATLAB/+qc/get_optimal_pulse_time.m | 10 - MATLAB/+qc/get_program.m | 12 - MATLAB/+qc/get_pulse_duration.m | 15 - MATLAB/+qc/get_pulse_params.m | 9 - MATLAB/+qc/get_pumping_from_awg.m | 68 ---- MATLAB/+qc/get_segment_waveform.m | 53 --- MATLAB/+qc/get_sequence_table.m | 91 ----- .../+qc/get_sequence_table_from_simulator.m | 99 ------ MATLAB/+qc/instantiate_pulse.m | 44 --- MATLAB/+qc/is_dict.m | 4 - MATLAB/+qc/is_instantiated_pulse.m | 3 - MATLAB/+qc/join_params_and_dicts.m | 62 ---- MATLAB/+qc/join_structs.m | 17 - MATLAB/+qc/load_dict.m | 56 --- MATLAB/+qc/load_pulse.m | 4 - MATLAB/+qc/operations_to_python.m | 34 -- MATLAB/+qc/params_add_delim.m | 9 - MATLAB/+qc/params_rm_delim.m | 13 - MATLAB/+qc/personalPaths_README.txt | 22 -- MATLAB/+qc/plot_program_tree.m | 60 ---- MATLAB/+qc/plot_pulse.m | 178 ---------- MATLAB/+qc/plot_pulse_4chan.m | 80 ----- MATLAB/+qc/plot_tabor_pulse.m | 23 -- MATLAB/+qc/program_to_struct.m | 36 -- MATLAB/+qc/pulse_add_delim.m | 35 -- MATLAB/+qc/pulse_seq.m | 142 -------- MATLAB/+qc/pulse_to_struct.m | 15 - MATLAB/+qc/qcToolkitTestSetupUploadProgram.m | 67 ---- MATLAB/+qc/qctoolkitTestSetup.m | 94 ----- MATLAB/+qc/qctoolkit_programs.m | 55 --- MATLAB/+qc/readme.txt | 8 - MATLAB/+qc/save_dict.m | 18 - MATLAB/+qc/save_pulse.m | 30 -- MATLAB/+qc/set_alazar_buffer_strategy.m | 58 ---- MATLAB/+qc/set_pumping_at_awg.m | 108 ------ MATLAB/+qc/set_sequence_table.m | 96 ------ MATLAB/+qc/setup_alazar_measurements.m | 140 -------- MATLAB/+qc/setup_tabor_awg.m | 129 ------- MATLAB/+qc/strrep.m | 15 - MATLAB/+qc/struct_to_pulse.m | 15 - MATLAB/+qc/to_transformation.m | 18 - MATLAB/+qc/workaround_4chan_program_errors.m | 51 --- ...karound_alazar_single_buffer_acquisition.m | 23 -- MATLAB/+qctoolkit/arm_pulse.m | 60 ---- MATLAB/+qctoolkit/convert_qctoolkit.m | 34 -- MATLAB/+qctoolkit/example_scan_no_alazar.m | 128 ------- MATLAB/+qctoolkit/get_pulse_duration.m | 17 - MATLAB/+qctoolkit/get_pulse_parameters.m | 6 - MATLAB/+qctoolkit/instantiate_pulse.m | 18 - MATLAB/+qctoolkit/load_pulse.m | 7 - MATLAB/+qctoolkit/plot_pulse.m | 30 -- MATLAB/+qctoolkit/plot_tabor_pulse.m | 23 -- MATLAB/+qctoolkit/qctoolkitTestSetup.m | 86 ----- 84 files changed, 4069 deletions(-) delete mode 100644 MATLAB/+qc/AWGwatch.m delete mode 100644 MATLAB/+qc/AWGwatch/awgdisp_app.mlapp delete mode 100644 MATLAB/+qc/add_params_to_dict.m delete mode 100644 MATLAB/+qc/array2list.m delete mode 100644 MATLAB/+qc/array2row.m delete mode 100644 MATLAB/+qc/awg_program.m delete mode 100644 MATLAB/+qc/awgdisp.m delete mode 100644 MATLAB/+qc/change_armed_program.m delete mode 100644 MATLAB/+qc/change_field.m delete mode 100644 MATLAB/+qc/check_pulse_parameter_dependency.m delete mode 100644 MATLAB/+qc/cleanupfn_awg.m delete mode 100644 MATLAB/+qc/cleanupfn_delete_getchans.m delete mode 100644 MATLAB/+qc/cleanupfn_rf_sources.m delete mode 100644 MATLAB/+qc/compensate_channels.m delete mode 100644 MATLAB/+qc/conf_seq.m delete mode 100644 MATLAB/+qc/conf_seq_procfn.m delete mode 100644 MATLAB/+qc/daq_operations.m delete mode 100644 MATLAB/+qc/dict.m delete mode 100644 MATLAB/+qc/dict_apply_globals.m delete mode 100644 MATLAB/+qc/dict_to_parameter_struct.m delete mode 100644 MATLAB/+qc/disp_awg_seq_table.m delete mode 100644 MATLAB/+qc/disp_dict.m delete mode 100644 MATLAB/+qc/get_alazar_measurements.m delete mode 100644 MATLAB/+qc/get_awg_channels.m delete mode 100644 MATLAB/+qc/get_awg_memory.m delete mode 100644 MATLAB/+qc/get_awg_programs.m delete mode 100644 MATLAB/+qc/get_awg_seq_table.m delete mode 100644 MATLAB/+qc/get_dnp_pulse_duration.m delete mode 100644 MATLAB/+qc/get_dnp_pulse_duration_offset.m delete mode 100644 MATLAB/+qc/get_minimal_program.m delete mode 100644 MATLAB/+qc/get_optimal_awg_time.m delete mode 100644 MATLAB/+qc/get_optimal_pulse_time.m delete mode 100644 MATLAB/+qc/get_program.m delete mode 100644 MATLAB/+qc/get_pulse_duration.m delete mode 100644 MATLAB/+qc/get_pulse_params.m delete mode 100644 MATLAB/+qc/get_pumping_from_awg.m delete mode 100644 MATLAB/+qc/get_segment_waveform.m delete mode 100644 MATLAB/+qc/get_sequence_table.m delete mode 100644 MATLAB/+qc/get_sequence_table_from_simulator.m delete mode 100644 MATLAB/+qc/instantiate_pulse.m delete mode 100644 MATLAB/+qc/is_dict.m delete mode 100644 MATLAB/+qc/is_instantiated_pulse.m delete mode 100644 MATLAB/+qc/join_params_and_dicts.m delete mode 100644 MATLAB/+qc/join_structs.m delete mode 100644 MATLAB/+qc/load_dict.m delete mode 100644 MATLAB/+qc/load_pulse.m delete mode 100644 MATLAB/+qc/operations_to_python.m delete mode 100644 MATLAB/+qc/params_add_delim.m delete mode 100644 MATLAB/+qc/params_rm_delim.m delete mode 100644 MATLAB/+qc/personalPaths_README.txt delete mode 100644 MATLAB/+qc/plot_program_tree.m delete mode 100644 MATLAB/+qc/plot_pulse.m delete mode 100644 MATLAB/+qc/plot_pulse_4chan.m delete mode 100644 MATLAB/+qc/plot_tabor_pulse.m delete mode 100644 MATLAB/+qc/program_to_struct.m delete mode 100644 MATLAB/+qc/pulse_add_delim.m delete mode 100644 MATLAB/+qc/pulse_seq.m delete mode 100644 MATLAB/+qc/pulse_to_struct.m delete mode 100644 MATLAB/+qc/qcToolkitTestSetupUploadProgram.m delete mode 100644 MATLAB/+qc/qctoolkitTestSetup.m delete mode 100644 MATLAB/+qc/qctoolkit_programs.m delete mode 100644 MATLAB/+qc/readme.txt delete mode 100644 MATLAB/+qc/save_dict.m delete mode 100644 MATLAB/+qc/save_pulse.m delete mode 100644 MATLAB/+qc/set_alazar_buffer_strategy.m delete mode 100644 MATLAB/+qc/set_pumping_at_awg.m delete mode 100644 MATLAB/+qc/set_sequence_table.m delete mode 100644 MATLAB/+qc/setup_alazar_measurements.m delete mode 100644 MATLAB/+qc/setup_tabor_awg.m delete mode 100644 MATLAB/+qc/strrep.m delete mode 100644 MATLAB/+qc/struct_to_pulse.m delete mode 100644 MATLAB/+qc/to_transformation.m delete mode 100644 MATLAB/+qc/workaround_4chan_program_errors.m delete mode 100644 MATLAB/+qc/workaround_alazar_single_buffer_acquisition.m delete mode 100644 MATLAB/+qctoolkit/arm_pulse.m delete mode 100644 MATLAB/+qctoolkit/convert_qctoolkit.m delete mode 100644 MATLAB/+qctoolkit/example_scan_no_alazar.m delete mode 100644 MATLAB/+qctoolkit/get_pulse_duration.m delete mode 100644 MATLAB/+qctoolkit/get_pulse_parameters.m delete mode 100644 MATLAB/+qctoolkit/instantiate_pulse.m delete mode 100644 MATLAB/+qctoolkit/load_pulse.m delete mode 100644 MATLAB/+qctoolkit/plot_pulse.m delete mode 100644 MATLAB/+qctoolkit/plot_tabor_pulse.m delete mode 100644 MATLAB/+qctoolkit/qctoolkitTestSetup.m diff --git a/MATLAB/+qc/AWGwatch.m b/MATLAB/+qc/AWGwatch.m deleted file mode 100644 index 79e7a80db..000000000 --- a/MATLAB/+qc/AWGwatch.m +++ /dev/null @@ -1,21 +0,0 @@ -function AWGwatch() -% starts a matlab (vers 2018a) app to DISPLAY SEQUENCER TABLES AND -% WAFEFORMS in qctoolkit and on the Tabor AWG simulatar -% ------------------------------------------------------------------------- -% - to edit the app open awgdisp_app.mlapp in the Matlab app designer -% - user preferences can be edited in the app private properties directly -% at the top in awgdisp_app.mlapp -% ------------------------------------------------------------------------- -% App written by Marcel Meyer 08|2018 marcel.meyer1@rwth-aachen.de - - disp('AWGwatch - app is started'); - - % the app is not on path +qc because then one has problems debugging it - pathOfApp = which('qc.AWGwatch'); - pathOfApp = pathOfApp(1:end-10); - pathOfApp = [pathOfApp 'AWGwatch\']; - addpath(pathOfApp); - - awgdisp_app(); - -end \ No newline at end of file diff --git a/MATLAB/+qc/AWGwatch/awgdisp_app.mlapp b/MATLAB/+qc/AWGwatch/awgdisp_app.mlapp deleted file mode 100644 index 25de9342c13291424ed9cf8c95fae9b5695a2124..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 130663 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≷k3K$bGqIi6Tk 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 diff --git a/MATLAB/+qc/add_params_to_dict.m b/MATLAB/+qc/add_params_to_dict.m deleted file mode 100644 index 64d15b5c5..000000000 --- a/MATLAB/+qc/add_params_to_dict.m +++ /dev/null @@ -1,26 +0,0 @@ -function d = add_params_to_dict(d, parameters, pulse_name) - - if nargin < 3 - pulse_name= []; - end - - delim = '___'; - - fn = fieldnames(parameters)'; - - if ~isempty(fn) && util.str_contains(fn{1}, delim) - [parameters, extracted_pulse_name] = qc.params_rm_delim(parameters); - - if isempty(pulse_name) - pulse_name = extracted_pulse_name; - end - end - - if isempty(pulse_name) - error('Pulse name must not be empty'); - end - - d = qc.load_dict(d); - d.(pulse_name) = parameters; - - diff --git a/MATLAB/+qc/array2list.m b/MATLAB/+qc/array2list.m deleted file mode 100644 index 9865457fb..000000000 --- a/MATLAB/+qc/array2list.m +++ /dev/null @@ -1,9 +0,0 @@ -function sOut = array2list(sIn) - if isstruct(sIn) - sOut = structfun(@qc.array2list, sIn, 'UniformOutput', false); - elseif isnumeric(sIn) && ~isscalar(sIn) - sOut = py.list(sIn(:).'); - else - sOut = sIn; - end -end \ No newline at end of file diff --git a/MATLAB/+qc/array2row.m b/MATLAB/+qc/array2row.m deleted file mode 100644 index f82e19a7d..000000000 --- a/MATLAB/+qc/array2row.m +++ /dev/null @@ -1,9 +0,0 @@ -function sOut = array2row(sIn) - if isstruct(sIn) - sOut = structfun(@qc.array2row, sIn, 'UniformOutput', false); - elseif isnumeric(sIn) && ~isscalar(sIn) - sOut = sIn(:).'; - else - sOut = sIn; - end -end \ No newline at end of file diff --git a/MATLAB/+qc/awg_program.m b/MATLAB/+qc/awg_program.m deleted file mode 100644 index 91b06086c..000000000 --- a/MATLAB/+qc/awg_program.m +++ /dev/null @@ -1,320 +0,0 @@ -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); - - % 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 - - -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 diff --git a/MATLAB/+qc/awgdisp.m b/MATLAB/+qc/awgdisp.m deleted file mode 100644 index f92b16727..000000000 --- a/MATLAB/+qc/awgdisp.m +++ /dev/null @@ -1,20 +0,0 @@ -function awgdisp(source) - -if nargin < 1 - source = 'qctk'; -end - -% get AWG objects -switch source - case 'qctk' - disp('source = qctoolkit') - case 'sim' - disp('simulaotr') - otherwise - disp('no input') -end - - -% get sequence tables - -% create app \ No newline at end of file diff --git a/MATLAB/+qc/change_armed_program.m b/MATLAB/+qc/change_armed_program.m deleted file mode 100644 index df91085dd..000000000 --- a/MATLAB/+qc/change_armed_program.m +++ /dev/null @@ -1,32 +0,0 @@ -function change_armed_program(program_name, turn_awg_off) -% CHANGE_ARMED_PROGRAM Force arming of a program on Tabor AWG -% This function calls change_armed_program and each Tabor channel pair -% which contains the indicated program. The program needs to be already -% present on the AWG. -% --- Inputs -------------------------------------------------------------- -% program_name : Program name which is armed -% turn_awg_off : Turn AWG off after arming the program. -% Default is true. -% ------------------------------------------------------------------------- -% (c) 2018/06 Pascal Cerfontaine (cerfontaine@physik.rwth-aachen.de) - -global plsdata -hws = plsdata.awg.hardwareSetup; - -if nargin < 2 || isempty(turn_awg_off) - turn_awg_off = true; -end - -known_awgs = util.py.py2mat(hws.known_awgs); - -for k = 1:length(known_awgs) - known_programs{k} = util.py.py2mat(py.getattr(known_awgs{k}, '_known_programs')); - - if isfield(known_programs{k}, program_name) - known_awgs{k}.change_armed_program(program_name); - end -end - -if turn_awg_off - awgctrl('off'); -end \ No newline at end of file diff --git a/MATLAB/+qc/change_field.m b/MATLAB/+qc/change_field.m deleted file mode 100644 index dd22675eb..000000000 --- a/MATLAB/+qc/change_field.m +++ /dev/null @@ -1,3 +0,0 @@ -function s = change_field(s, fieldName, value) - s.(fieldName) = value; -end \ No newline at end of file diff --git a/MATLAB/+qc/check_pulse_parameter_dependency.m b/MATLAB/+qc/check_pulse_parameter_dependency.m deleted file mode 100644 index e8d281dbf..000000000 --- a/MATLAB/+qc/check_pulse_parameter_dependency.m +++ /dev/null @@ -1,64 +0,0 @@ -function check_pulse_parameter_dependency(pulse, check_parameter, check_values, varargin) -% plot function to visualize the influence of one pulse parameter on the -% pulse shape: -% ---------------------------------------------------------------------- -% input: -% pulse : loaded qctoolkit pulse -% check_paramter : name of the paramter under investigation -% check_values : values the parameter is set to, array [] -% varargin : standard varargin that one also uses for -% qc.plot_pulse -% ---------------------------------------------------------------------- -% written by Marcel Meyer 08|2018 (marcel.meyer1@rwth-aachen.de) - -global plsdata - -defaultArgs = struct(... - 'sample_rate', plsdata.awg.sampleRate, ... % in 1/s, converted to 1/ns below - 'channel_mapping', py.None, ... - 'window_mapping' , py.None, ... - 'parameters', struct(), ... - 'removeTrigChans', true, ... - 'figID', 2018 ... - ); - -args = util.parse_varargin(varargin, defaultArgs); - -if isempty(args.channel_mapping) || args.channel_mapping == py.None - - args.channel_mapping = py.dict(py.zip(pulse.defined_channels, pulse.defined_channels)); -end - -args.sample_rate = args.sample_rate * 1e-9; % convert to 1/ns - -figure(args.figID); -clf; - -for k = 1:numel(check_values) - args.parameters.(check_parameter) = check_values(k); - - instantiatedPulse = qc.instantiate_pulse(pulse, 'parameters', args.parameters, 'channel_mapping', args.channel_mapping, 'window_mapping', args.window_mapping); - data = util.py.py2mat(py.qctoolkit.pulses.plotting.render(instantiatedPulse, pyargs('sample_rate', args.sample_rate, 'render_measurements', true))); - - subplot(1, numel(check_values), k); - - hold on; - - if args.removeTrigChans - data{2} = rmfield(data{2}, 'MTrig'); - data{2} = rmfield(data{2}, 'M1'); - data{2} = rmfield(data{2}, 'M2'); - end - - channelNames = fieldnames(data{2}); - - for channelInd = 1:numel(channelNames) - plot(data{1}*1e-9, data{2}.(channelNames{channelInd})); - end - hold off; - legend(channelNames); - xlabel('t(s)'); - title(sprintf('%s = %.2d', check_parameter, check_values(k)), 'interpreter', 'none'); -end - -end \ No newline at end of file diff --git a/MATLAB/+qc/cleanupfn_awg.m b/MATLAB/+qc/cleanupfn_awg.m deleted file mode 100644 index 3e3a8a438..000000000 --- a/MATLAB/+qc/cleanupfn_awg.m +++ /dev/null @@ -1,9 +0,0 @@ -function scan = cleanupfn_awg(scan) - - if nargin < 1 - scan = []; - end - - evalin('caller', 'cleanupFnAwg = onCleanup(@()({awgctrl(''off''), fprintf(''Executing cleanup function: Turned AWG outputs off\n'')}));'); - -end \ No newline at end of file diff --git a/MATLAB/+qc/cleanupfn_delete_getchans.m b/MATLAB/+qc/cleanupfn_delete_getchans.m deleted file mode 100644 index 67216b0d4..000000000 --- a/MATLAB/+qc/cleanupfn_delete_getchans.m +++ /dev/null @@ -1,7 +0,0 @@ -function scan = cleanupfn_delete_getchans(scan, getchans) - - for getchan = getchans - evalin('caller', sprintf('data{%i} = {};', getchan)); - end - -end \ No newline at end of file diff --git a/MATLAB/+qc/cleanupfn_rf_sources.m b/MATLAB/+qc/cleanupfn_rf_sources.m deleted file mode 100644 index 018ba0830..000000000 --- a/MATLAB/+qc/cleanupfn_rf_sources.m +++ /dev/null @@ -1,11 +0,0 @@ -function scan = cleanupfn_rf_sources(scan) - - if nargin < 1 - scan = []; - end - - evalin('caller', 'cleanupFnRfMsg = onCleanup(@()(fprintf(''Executing cleanup function: Turned RF sources off\n'')));'); - evalin('caller', 'cleanupFnRf1 = onCleanup(@()(smset(''RF1_on'', 0)));'); - evalin('caller', 'cleanupFnRf2 = onCleanup(@()(smset(''RF2_on'', 0)));'); - -end \ No newline at end of file diff --git a/MATLAB/+qc/compensate_channels.m b/MATLAB/+qc/compensate_channels.m deleted file mode 100644 index ec6252637..000000000 --- a/MATLAB/+qc/compensate_channels.m +++ /dev/null @@ -1,90 +0,0 @@ -function [W, X, Y, Z] = compensate_channels(t, W, X, Y, Z, interpolation, comp_param_name) -% ADD_CHANNELS Compensate linear crosstalk for two ST0 qubits -% This function adds W and X to Y and Z (and vice-versa) with individual -% multipliers. It is specifically designed for compensating linear control -% crosstalk two ST0 qubits but might be suited for other qubit -% implementations as well. -% -% --- Outputs ------------------------------------------------------------- -% W, X : Compensated qubit 1 channel values (cell) -% Y, Z : Compensated qubit 2 channel values (cell) -% -% --- Inputs -------------------------------------------------------------- -% t, W, X, Y, Z, interpolation, MTrig, M1, M2 are cells with the same -% number of entries -% -% t : Time indices of the channel values (cell) -% One row: Applied to all channels -% *Two rows: Row 1 applied to W, X and row 2 to Y, Z -% *Four rows: One row for each channel -% W, X : Qubit 1 channel values (cell) -% Y, Z : Qubit 2 channel values (cell) -% interpolation : Interpolation strategies, use ‘hold’ for default -% qupulse behaviour -% One row: Applied to all channels -% *Two rows: Row 1 applied to W, X and row 2 to Y, Z -% *Four rows: One row for each channel -% comp_param_name: Name of the compensation parameters -% -% * Currently disabled since compensation might not work if using -% different times -% -% ------------------------------------------------------------------------- -% (c) 2018/05 Pascal Cerfontaine (cerfontaine@physik.rwth-aachen.de) - - assert(all(numel(t) == cellfun(@numel, {W, X, Y, Z, interpolation})), 'Cell input arguments must have the same number of elements'); - assert(size(t, 1) == 1 && size(interpolation, 1) == 1, 'Compensation of different times and interpolation strategies not currently supported'); - - if nargin < 7 || isempty(comp_param_name) - comp_param_name = 'globals___comp'; - end - - if ~strcmp(comp_param_name, 'compensation_off') - [W, X, Y, Z] = comp_channels(W, X, Y, Z, comp_param_name); - end - [W, X, Y, Z] = format_channels(t, interpolation, W, X, Y, Z); - -end - - -function [W, X, Y, Z] = comp_channels(W, X, Y, Z, comp_param_name) - for k = 1:numel(W) - Wk = W{k}; - Xk = X{k}; - - W{k} = [W{k} ' + ' comp_param_name '_w_y*( ' Y{k} ' ) + ' comp_param_name '_w_z*( ' Z{k} ' )']; - X{k} = [X{k} ' + ' comp_param_name '_x_y*( ' Y{k} ' ) + ' comp_param_name '_x_z*( ' Z{k} ' )']; - - Y{k} = [Y{k} ' + ' comp_param_name '_y_w*( ' Wk ' ) + ' comp_param_name '_y_x*( ' Xk ' )']; - Z{k} = [Z{k} ' + ' comp_param_name '_z_w*( ' Wk ' ) + ' comp_param_name '_z_x*( ' Xk ' )']; - end - -end - -function varargout = format_channels(t, interpolation, varargin) - - varargout = cell(numel(varargin), 1); - - if size(interpolation, 1) == 1 - interpolation = repmat(interpolation, 4, 1); - elseif size(interpolation, 1) == 2 - interpolation = [ repmat(interpolation(1, :), 2, 1) ; - repmat(interpolation(2, :), 2, 1) ]; - end - - if size(t, 1) == 1 - t = repmat(t, 4, 1); - elseif size(t, 1) == 2 - t = [ repmat(t(1, :), 2, 1) ; - repmat(t(2, :), 2, 1) ]; - end - - for k = 1:size(t, 2) - for v = 1:numel(varargin) - - varargout{v}{end+1} = { t{v, k}, varargin{v}{k}, interpolation{v, k} }; - - end - end - -end \ No newline at end of file diff --git a/MATLAB/+qc/conf_seq.m b/MATLAB/+qc/conf_seq.m deleted file mode 100644 index b58afab43..000000000 --- a/MATLAB/+qc/conf_seq.m +++ /dev/null @@ -1,326 +0,0 @@ -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 - 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 - - - -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 diff --git a/MATLAB/+qc/conf_seq_procfn.m b/MATLAB/+qc/conf_seq_procfn.m deleted file mode 100644 index 8a884b2dd..000000000 --- a/MATLAB/+qc/conf_seq_procfn.m +++ /dev/null @@ -1,15 +0,0 @@ -function scan = conf_seq_procfn(scan) - % Dynamically changes the procfn arguments for each operation which - % extracts the data from the channel ATSV - % - % Assumes that the field scan.data.daq_operations_length has been set to - % the lengths of the operations. - - nGetChan = numel(scan.loops(1).getchan); - lengths = scan.data.daq_operations_length; - startInd = 1; - for p = 1:numel(lengths) - scan.loops(1).procfn(nGetChan + p).fn(1).args = {startInd, startInd+lengths(p)-1}; - scan.loops(1).procfn(nGetChan + p).dim = [lengths(p)]; - startInd = startInd + lengths(p); - end \ No newline at end of file diff --git a/MATLAB/+qc/daq_operations.m b/MATLAB/+qc/daq_operations.m deleted file mode 100644 index cb1bc51b5..000000000 --- a/MATLAB/+qc/daq_operations.m +++ /dev/null @@ -1,125 +0,0 @@ -function [output, bool, msg] = daq_operations(ctrl, varargin) - - global plsdata smdata - hws = plsdata.awg.hardwareSetup; - daq = plsdata.daq.inst; - instIndex = sminstlookup(plsdata.daq.instSmName); - - program = struct(); - msg = ''; - bool = false; - - default_args = struct(... - 'program_name', 'default_program', ... - 'operations', {plsdata.daq.defaultOperations}, ... - 'verbosity', 10 ... - ); - a = util.parse_varargin(varargin, default_args); - output = a.operations; - - % --- add --------------------------------------------------------------- - if strcmp(ctrl, 'add') % output is operations - % Call before qc.awg_program('arm')! - - smdata.inst(instIndex).data.virtual_channel = struct( ... - 'operations', {a.operations} ... - ); - - % alazar.update_settings = py.True is automatically set if - % register_operations is executed. This results in reconfiguration - % of the Alazar which takes a long time. Thus, we avoid registering - % operations if the last armed program is the same as the currently - % armed program. We know plsdata.awg.currentProgam contains the last - % armed program since qc.daq_operations should be called before - % qc.awg_program('arm'). - if plsdata.daq.reuseOperations && ~plsdata.daq.operationsExternallyModified && strcmp(plsdata.awg.currentProgam, a.program_name) - msg = sprintf('Operations from last armed program ''%s'' reused.\n If an error occurs, try executing another program\n first to update the operations.', plsdata.awg.currentProgam); - else - daq.register_operations(a.program_name, qc.operations_to_python(a.operations)); - msg = sprintf('Operations for program ''%s'' added', a.program_name); - - if plsdata.daq.operationsExternallyModified - plsdata.daq.inst.update_settings = py.True; - end - - plsdata.daq.operationsExternallyModified = false; - % qc.workaround_alazar_single_buffer_acquisition(); - end - bool = true; - - % --- set length -------------------------------------------------------- - elseif strcmp(ctrl, 'set length') % output is length - % Operations need to have been added beforehand - output = qc.daq_operations('get length', a); - smdata.inst(instIndex).cntrlfn([instIndex nan 999], output); - - % --- get length -------------------------------------------------------- - 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)); - - - maskIdsFromOperations = cellfun(@(x)(char(x.maskID)), util.py.py2mat(operations), 'UniformOutput', false); - maskIdsFromMasks = cellfun(@(x)(char(x.identifier)), util.py.py2mat(masks), 'UniformOutput', false); - - output = []; - for k = 1:length(operations) - maskIndex = find( cellfun(@(x)(strcmp(x, maskIdsFromOperations{k})), maskIdsFromMasks) ); - if numel(maskIndex) ~= 1 - error('Found several masks with same identifier. Might be a problem in qctoolkit or in this function.'); - end - - if isa(operations{k}, 'py.atsaverage._atsaverage_release.ComputeDownsampleDefinition') - output(k) = util.py.py2mat(size(masks{maskIndex}.length)); - elseif isa(operations{k}, 'py.atsaverage._atsaverage_release.ComputeRepAverageDefinition') - n = util.py.py2mat(masks{maskIndex}.length.to_ndarray); - if any(n ~= n(1)) - error('daq_operations assumes that all masks should have the same length if using ComputeRepAverageDefinition.'); - end - output(k) = n(1); - else - error('Operation ''%s'' not yet implemented', class(operations{k})); - end - end - if isempty(output) - warning('No masks configured'); - end - - % --- get --------------------------------------------------------------- - elseif strcmp(ctrl, 'get programs') % output is registered programs - % Operations need to have been added beforehand - % masks = util.py.py2mat(daq.config.masks); % this worked sometimes but sometimes not - output = util.py.py2mat(py.getattr(daq, '_registered_programs')); - - % --- remove ------------------------------------------------------------ - elseif strcmp(ctrl, 'remove') % output is operations - % Should not call this usually. Call qc.awg_program('remove') instead. - smdata.inst(instIndex).data.virtual_channel = struct( ... - 'operations', {{}} ... - ); - programs = fieldnames(qc.daq_operations('get programs')); - if any(cellfun(@(x)(strcmp(x, a.program_name)), programs)) - daq.delete_program(a.program_name); - msg = sprintf('Operations for program ''%s'' deleted', a.program_name); - bool = true; - else - msg = sprintf('Operations for program ''%s'' were not registered', a.program_name); - bool = true; - end - - % --- clear all --------------------------------------------------------- - elseif strcmp(ctrl, 'clear all') - alazarPackage = py.importlib.import_module('qctoolkit.hardware.dacs.alazar'); - py.setattr(daq, '_registered_programs', py.collections.defaultdict(alazarPackage.AlazarProgram)); - bool = true; - msg = 'All programs cleared from DAQ'; - - end - - if a.verbosity > 9 - fprintf([msg '\n']); - end \ No newline at end of file diff --git a/MATLAB/+qc/dict.m b/MATLAB/+qc/dict.m deleted file mode 100644 index 9e8675ba6..000000000 --- a/MATLAB/+qc/dict.m +++ /dev/null @@ -1,10 +0,0 @@ -function d = dict(varargin) -% Wrapper for struct so do not need to have three curly braces when -% creating pulse templates -% -% varargin needs to the same as for struct(.) - -for k = 2:2:numel(varargin) - varargin{k} = {varargin{k}}; -end -d = struct(varargin{:}); \ No newline at end of file diff --git a/MATLAB/+qc/dict_apply_globals.m b/MATLAB/+qc/dict_apply_globals.m deleted file mode 100644 index 7fe8665bf..000000000 --- a/MATLAB/+qc/dict_apply_globals.m +++ /dev/null @@ -1,27 +0,0 @@ -function d = dict_apply_globals(d) - % Replace all parameters by their global values - if qc.is_dict(d) && isfield(d, 'global') - delim = '___'; - globals = fieldnames(d.global); - - for pulseName = fieldnames(d)' - if strcmp(pulseName{1}, strcat('dict', delim, 'name')) - continue - end - - % Only add global parameters which are defined in pulse parameters - % for paramName = fieldnames(d.(pulseName{1}))' - % bool = cellfun(@(x)(strcmp(paramName{1}, x)), globals, 'UniformOutput', true); - % if any(bool) - % d.(pulseName{1}).(paramName{1}) = d.global.(globals{bool}); - % end - % end - - % Add all global parameters to pulse irrespective of whether they are - % defined in pulse parameters - d.(pulseName{1}) = qc.join_structs(d.(pulseName{1}), d.global); - - end - d = rmfield(d, 'global'); - end -end \ No newline at end of file diff --git a/MATLAB/+qc/dict_to_parameter_struct.m b/MATLAB/+qc/dict_to_parameter_struct.m deleted file mode 100644 index 2d997c9bc..000000000 --- a/MATLAB/+qc/dict_to_parameter_struct.m +++ /dev/null @@ -1,18 +0,0 @@ -function p = dict_to_parameter_struct(d) - % Flatten dict into a parameter struct - if qc.is_dict(d) - delim = '___'; - d = rmfield(d, strcat('dict', delim, 'name')); - - p = {}; - for pulseName = fieldnames(d)' - for paramName = fieldnames(d.(pulseName{1}))' - p{end+1} = strcat(pulseName{1}, delim, paramName{1}); - p{end+1} = d.(pulseName{1}).(paramName{1}); - end - end - p = struct(p{:}); - else - p = d; - end -end \ No newline at end of file diff --git a/MATLAB/+qc/disp_awg_seq_table.m b/MATLAB/+qc/disp_awg_seq_table.m deleted file mode 100644 index 7dc7a2781..000000000 --- a/MATLAB/+qc/disp_awg_seq_table.m +++ /dev/null @@ -1,95 +0,0 @@ -% function to display the sequence table hold by qctoolkit Tabor instance -% or given in the varargins -% ------------------------------------------------------------------------- -% Notes: -% - if varargin.seq_table is empty the sequence table saved in the qctoolkit -% Tabor object is plotted -> function uses qc.get_sequence_table internaly -% ------------------------------------------------------------------------- -% written by Marcel Meyer 08|2018 - - -function disp_awg_seq_table(varargin) - - global plsdata - - defaultArgs = struct(... - 'seq_table', {{}}, ... - 'programName', plsdata.awg.currentProgam, ... - 'advancedSeqTableFlag', false ... - ); - args = util.parse_varargin(varargin, defaultArgs); - - - if isempty(args.seq_table) - seq_table = qc.get_sequence_table(args.programName, args.advancedSeqTableFlag); - else - assert(iscell(args.seq_table), 'wrong format sequence table') - seq_table = args.seq_table; - end - - disp(' '); - disp('[i] Table 1 is for channel pair AB and table 2 for channel pair CD.'); - disp(' '); - - counter = 0; - tmpEntry = ''; - - for k = 1:2 - if isempty(seq_table{k}) - warning('-- empty sequence table at channel nr %i -- \n', k); - else - if ~args.advancedSeqTableFlag - - fprintf('--- table %d -----------------\n', k); - for n = 1:length(seq_table{k}) - fprintf(' -- sub table %d ---------\n', n); - - tmpEntry = seq_table{k}{n}{1}; - counter = 0; - for i=1:length(seq_table{k}{n}) - if isequal(tmpEntry, seq_table{k}{n}{i}) - counter = counter+1; - else - - fprintf(' rep = %d', counter); - disp(tmpEntry); - - tmpEntry = seq_table{k}{n}{i}; - counter = 1; - end - end - fprintf(' rep = %d', counter); - disp(tmpEntry); - disp('-----------------------------') - end - - - - - else - - fprintf('--- table %d -----------------\n', k); - - tmpEntry = seq_table{k}{1}; - counter = 0; - for i=1:length(seq_table{k}) - if isequal(tmpEntry, seq_table{k}{i}) - counter = counter+1; - else - fprintf(' rep = %d', counter); - disp(tmpEntry); - - tmpEntry = seq_table{k}{i}; - counter = 0; - end - end - fprintf(' rep = %d', counter); - disp(tmpEntry); - disp('-----------------------------') - - end - - - end - end -end \ No newline at end of file diff --git a/MATLAB/+qc/disp_dict.m b/MATLAB/+qc/disp_dict.m deleted file mode 100644 index bdc50ac37..000000000 --- a/MATLAB/+qc/disp_dict.m +++ /dev/null @@ -1,31 +0,0 @@ -function text = disp_dict(dict_string_or_struct) - global plsdata - delim = '___'; - - text = ''; - - if isstruct(dict_string_or_struct) - if ~isfield(dict_string_or_struct, ['dict' delim 'name']) - error('Please pass a valid dictionary struct. The passed argument is missing the field ''%s''.\n', ['dict' delim 'name']); - end - text = py.json.dumps(dict_string_or_struct, pyargs('indent', int8(4), 'sort_keys', true)); - text = char(text); - dict_string_or_struct = dict_string_or_struct.(['dict' delim 'name']); - elseif ischar(dict_string_or_struct) - file_name = fullfile(plsdata.dict.path, [dict_string_or_struct '.json']); - if exist(file_name, 'file') - text = fileread(file_name); - else - error('Dictionary ''%s'' could not be loaded since file ''%s'' does not exist\n', dict_string_or_struct, file_name); - end - else - error('Please pass a valid dictonary struct or string\n'); - end - - if ~isempty(text) - util.disp_section(sprintf('Dictionary %s', dict_string_or_struct)); - fprintf('%s\n', text); - util.disp_section(); - end - - end \ No newline at end of file diff --git a/MATLAB/+qc/get_alazar_measurements.m b/MATLAB/+qc/get_alazar_measurements.m deleted file mode 100644 index a5d79e86c..000000000 --- a/MATLAB/+qc/get_alazar_measurements.m +++ /dev/null @@ -1,32 +0,0 @@ -function [mask_prototypes, measurement_map, txt] = get_alazar_measurements(varargin) - - global plsdata - hws = plsdata.awg.hardwareSetup; - daq = plsdata.daq.inst; - - - defaultArgs = struct( ... - 'disp', true ... - ); - args = util.parse_varargin(varargin, defaultArgs); - - mask_prototypes = util.py.py2mat(daq.mask_prototypes); - measurement_map = util.py.py2mat(py.getattr(hws,'_measurement_map')); - - txt = sprintf('%-30s %-30s %-30s\\n', 'Measurement', 'Mask', 'Hardware Channel'); - txt = strcat(txt, [ones(1,85)*'-' '\n']); - - measurement_map = orderfields(measurement_map); - for measName = fieldnames(measurement_map)' - masks = measurement_map.(measName{1}); - for k = 1:numel(masks) - maskName = char(masks{k}.mask_name); - txt = strcat(txt, sprintf('%-30s %-30s %-30i\\n', measName{1}, maskName, mask_prototypes.(maskName){1})); - end - end - - txt = strcat(txt, [ones(1,85)*'-' '\n']); - - if args.disp - fprintf(txt); - end \ No newline at end of file diff --git a/MATLAB/+qc/get_awg_channels.m b/MATLAB/+qc/get_awg_channels.m deleted file mode 100644 index 067b7dd4a..000000000 --- a/MATLAB/+qc/get_awg_channels.m +++ /dev/null @@ -1,16 +0,0 @@ -function [analogNames, markerNames, channels] = get_awg_channels() - - global plsdata - - % Get AWG analog channels and markers - channels = struct(plsdata.awg.hardwareSetup.registered_channels); - analogNames = {}; - markerNames = {}; - for chanName = fieldnames(channels)' - chan = util.py.py2mat(channels.(chanName{1})); - if isa(chan{1}, 'py.qctoolkit.hardware.setup.MarkerChannel') - markerNames{end+1} = chanName{1}; - elseif isa(chan{1}, 'py.qctoolkit.hardware.setup.PlaybackChannel') - analogNames{end+1} = chanName{1}; - end - end \ No newline at end of file diff --git a/MATLAB/+qc/get_awg_memory.m b/MATLAB/+qc/get_awg_memory.m deleted file mode 100644 index 7b830b479..000000000 --- a/MATLAB/+qc/get_awg_memory.m +++ /dev/null @@ -1,44 +0,0 @@ -% function to get waveforms and sequence tables from the AWG -% ------------------------------------------------------------------------- -% Notes: -% - the function only works with the Tabor AWG Simulator not on the real -% Tabor AWG -% - the function arms the program that is inspected -% ------------------------------------------------------------------------- -% written by Marcel Meyer 08|2018 - -function awg_memory_struct = get_awg_memory(program_name, awg_channel_pair_identifier) - - global plsdata - - assert(ischar(program_name), 'first argument of get_awg_memory must be string'); - - if nargin < 2 || isempty(awg_channel_pair_identifier) - awg_channel_pair_identifier = 'AB'; - else - assert(ischar(awg_channel_pair_identifier), 'second argument of get_awg_memory must be string'); - end - - % get AWG channelpair python object - hws = plsdata.awg.hardwareSetup; - known_awgs = util.py.py2mat(hws.known_awgs); - sort_indices = cellfun(@(x)(~isempty(strfind(char(x.identifier), awg_channel_pair_identifier))), known_awgs); - channelpair = known_awgs(find(sort_indices)); - channelpair = channelpair{1}; - - % arm program at AWG - try - channelpair.arm(program_name); - catch err - warning('program seems not to be on AWG, upload it first, returning without returning memory'); - warning(err.message); - return - end - - % get a plottable program object -> qctoolkit Tabor driver gets sequence - % tables and waveforms from the simulator - plottableProgram = channelpair.read_complete_program(); - - awg_memory_struct = util.py.py2mat(plottableProgram.to_builtin()); - -end \ No newline at end of file diff --git a/MATLAB/+qc/get_awg_programs.m b/MATLAB/+qc/get_awg_programs.m deleted file mode 100644 index 3d764b8e4..000000000 --- a/MATLAB/+qc/get_awg_programs.m +++ /dev/null @@ -1,10 +0,0 @@ -function [programNames, programs] = get_awg_programs() - - global plsdata - - programs = util.py.py2mat(plsdata.awg.hardwareSetup.registered_programs); - programNames = fieldnames(programs); - - if ~isempty(setdiff(fieldnames(rmfield(plsdata.awg.registeredPrograms, 'currentProgam')), programNames)) - warning('''plsdata.awg.registeredPrograms'' out of sync with ''plsdata.awg.hardwareSetup.registered_programs''. Clear all programs by executing qc.awg_program(''clear all'') to remedy.'); - end \ No newline at end of file diff --git a/MATLAB/+qc/get_awg_seq_table.m b/MATLAB/+qc/get_awg_seq_table.m deleted file mode 100644 index 36fb340fc..000000000 --- a/MATLAB/+qc/get_awg_seq_table.m +++ /dev/null @@ -1,13 +0,0 @@ -function seq_table = get_awg_seq_table(varargin) - - global plsdata - - defaultArgs = struct(... - 'programName', plsdata.awg.currentProgam, ... - 'advancedSeqTableFlag', false ... - ); - args = util.parse_varargin(varargin, defaultArgs); - - seq_table = qc.get_sequence_table(args.programName, args.advancedSeqTableFlag); - -end \ No newline at end of file diff --git a/MATLAB/+qc/get_dnp_pulse_duration.m b/MATLAB/+qc/get_dnp_pulse_duration.m deleted file mode 100644 index 5e2188ea8..000000000 --- a/MATLAB/+qc/get_dnp_pulse_duration.m +++ /dev/null @@ -1,55 +0,0 @@ -function currentPulseDurationOffset = get_dnp_pulse_duration_offset() - - global tunedata - - t = tunedata.run{tunedata.runIndex}.global_opts.dnp.durations; - -% qc.get_pulse_duration(... -% qc.struct_to_pulse(tunedata.run{tunedata.runIndex}.global_opts.dnp.scan.configfn(4).args{end}.pulse_template), ... -% qc.join_params_and_dicts('common', 'common_d12', 'common_d34', 'common_d12 d12', 'common_d34 d34').... -% ); - -% t_pumpingPart = qc.get_pulse_duration(... -% qc.load_pulse('dnp_ABCD_4chan'), ... -% qc.join_params_and_dicts('common', 'common_d12', 'common_d34', 'common_d12 d12', 'common_d34 d34').... -% ); -% -% t_s_AB = qc.get_pulse_duration(... -% qc.load_pulse('s_pumping_AB_4chan'), ... -% qc.join_params_and_dicts('common', 'common_d12', 'common_d34', 'common_d12 d12', 'common_d34 d34').... -% ); -% t_cs_AB = qc.get_pulse_duration(... -% qc.load_pulse('t_pumping_AB_4chan'), ... -% qc.join_params_and_dicts('common', 'common_d12', 'common_d34', 'common_d12 d12', 'common_d34 d34').... -% ); -% t_t_AB = qc.get_pulse_duration(... -% qc.load_pulse('cs_pumping_AB_4chan'), ... -% qc.join_params_and_dicts('common', 'common_d12', 'common_d34', 'common_d12 d12', 'common_d34 d34').... -% ); -% -% t_s_CD = qc.get_pulse_duration(... -% qc.load_pulse('s_pumping_CD_4chan'), ... -% qc.join_params_and_dicts('common', 'common_d12', 'common_d34', 'common_d12 d12', 'common_d34 d34').... -% ); -% t_t_CD = qc.get_pulse_duration(... -% qc.load_pulse('t_pumping_CD_4chan'), ... -% qc.join_params_and_dicts('common', 'common_d12', 'common_d34', 'common_d12 d12', 'common_d34 d34').... -% ); -% t_cs_CD = qc.get_pulse_duration(... -% qc.load_pulse('cs_pumping_CD_4chan'), ... -% qc.join_params_and_dicts('common', 'common_d12', 'common_d34', 'common_d12 d12', 'common_d34 d34').... -% ); - - pumpConf = tunedata.run{1}.global_opts.dnp.pumpingConfigArgs.currentConfig; - - t.current_dnp_pulse = ... - t.s_AB * double(pumpConf.n_s_AB) + ... - t.t_AB * double(pumpConf.n_t_AB) + ... - t.cs_AB * double(pumpConf.n_cs_AB) + ... - t.s_CD * double(pumpConf.n_s_CD) + ... - t.t_CD * double(pumpConf.n_t_CD) + ... - t.cs_CD * double(pumpConf.n_cs_CD); - - currentPulseDurationOffset = t.current_dnp_pulse - t.dict_dnp_pulse; - -end \ No newline at end of file diff --git a/MATLAB/+qc/get_dnp_pulse_duration_offset.m b/MATLAB/+qc/get_dnp_pulse_duration_offset.m deleted file mode 100644 index 5e2188ea8..000000000 --- a/MATLAB/+qc/get_dnp_pulse_duration_offset.m +++ /dev/null @@ -1,55 +0,0 @@ -function currentPulseDurationOffset = get_dnp_pulse_duration_offset() - - global tunedata - - t = tunedata.run{tunedata.runIndex}.global_opts.dnp.durations; - -% qc.get_pulse_duration(... -% qc.struct_to_pulse(tunedata.run{tunedata.runIndex}.global_opts.dnp.scan.configfn(4).args{end}.pulse_template), ... -% qc.join_params_and_dicts('common', 'common_d12', 'common_d34', 'common_d12 d12', 'common_d34 d34').... -% ); - -% t_pumpingPart = qc.get_pulse_duration(... -% qc.load_pulse('dnp_ABCD_4chan'), ... -% qc.join_params_and_dicts('common', 'common_d12', 'common_d34', 'common_d12 d12', 'common_d34 d34').... -% ); -% -% t_s_AB = qc.get_pulse_duration(... -% qc.load_pulse('s_pumping_AB_4chan'), ... -% qc.join_params_and_dicts('common', 'common_d12', 'common_d34', 'common_d12 d12', 'common_d34 d34').... -% ); -% t_cs_AB = qc.get_pulse_duration(... -% qc.load_pulse('t_pumping_AB_4chan'), ... -% qc.join_params_and_dicts('common', 'common_d12', 'common_d34', 'common_d12 d12', 'common_d34 d34').... -% ); -% t_t_AB = qc.get_pulse_duration(... -% qc.load_pulse('cs_pumping_AB_4chan'), ... -% qc.join_params_and_dicts('common', 'common_d12', 'common_d34', 'common_d12 d12', 'common_d34 d34').... -% ); -% -% t_s_CD = qc.get_pulse_duration(... -% qc.load_pulse('s_pumping_CD_4chan'), ... -% qc.join_params_and_dicts('common', 'common_d12', 'common_d34', 'common_d12 d12', 'common_d34 d34').... -% ); -% t_t_CD = qc.get_pulse_duration(... -% qc.load_pulse('t_pumping_CD_4chan'), ... -% qc.join_params_and_dicts('common', 'common_d12', 'common_d34', 'common_d12 d12', 'common_d34 d34').... -% ); -% t_cs_CD = qc.get_pulse_duration(... -% qc.load_pulse('cs_pumping_CD_4chan'), ... -% qc.join_params_and_dicts('common', 'common_d12', 'common_d34', 'common_d12 d12', 'common_d34 d34').... -% ); - - pumpConf = tunedata.run{1}.global_opts.dnp.pumpingConfigArgs.currentConfig; - - t.current_dnp_pulse = ... - t.s_AB * double(pumpConf.n_s_AB) + ... - t.t_AB * double(pumpConf.n_t_AB) + ... - t.cs_AB * double(pumpConf.n_cs_AB) + ... - t.s_CD * double(pumpConf.n_s_CD) + ... - t.t_CD * double(pumpConf.n_t_CD) + ... - t.cs_CD * double(pumpConf.n_cs_CD); - - currentPulseDurationOffset = t.current_dnp_pulse - t.dict_dnp_pulse; - -end \ No newline at end of file diff --git a/MATLAB/+qc/get_minimal_program.m b/MATLAB/+qc/get_minimal_program.m deleted file mode 100644 index 43c6c8241..000000000 --- a/MATLAB/+qc/get_minimal_program.m +++ /dev/null @@ -1,24 +0,0 @@ -function reduced_program = get_minimal_program(program) - % Return program with all parameters not needed by its pulse template - % removed - - pulse_template = qc.struct_to_pulse(program.pulse_template); - parameter_names = util.py.py2mat(pulse_template.parameter_names); - - program.parameters_and_dicts = qc.join_params_and_dicts(program.parameters_and_dicts); - reduced_program = program; - - % amplitude_at_upload is only used to keep track of the AWG amplitude if - % it was dynamically changed. - if isfield(reduced_program, 'amplitudes_at_upload') - reduced_program = rmfield(reduced_program, 'amplitudes_at_upload'); - end - - reduced_program.parameters_and_dicts = struct(); - % Remove all fields in parameters_and_dicts not needed by pulse_template - for p = parameter_names - reduced_program.parameters_and_dicts.(p{1}) = program.parameters_and_dicts.(p{1}); - end - - - \ No newline at end of file diff --git a/MATLAB/+qc/get_optimal_awg_time.m b/MATLAB/+qc/get_optimal_awg_time.m deleted file mode 100644 index 858a277d5..000000000 --- a/MATLAB/+qc/get_optimal_awg_time.m +++ /dev/null @@ -1,24 +0,0 @@ -function [optimalTime, addTime, optimalNsamp] = get_optimal_awg_time(desiredTime, sampleRate) - % Tabor AWG closest optimal number of samples: sampleQuantum + n*sampleQuantum - % Time is in s, sampleRate is in Sa/s - - global plsdata - - sampleQuantum = plsdata.awg.sampleQuantum; - minSamples = plsdata.awg.minSamples; - - global plsdata - if nargin < 2 || isempty(sampleRate) - sampleRate = plsdata.awg.sampleRate; - end - - desiredNsamp = desiredTime*sampleRate; - - if desiredNsamp <= minSamples - optimalNsamp = minSamples; - else - optimalNsamp = minSamples + sampleQuantum*round((desiredNsamp-minSamples)/sampleQuantum); - end - - optimalTime = optimalNsamp / sampleRate; - addTime = optimalTime - desiredTime; \ No newline at end of file diff --git a/MATLAB/+qc/get_optimal_pulse_time.m b/MATLAB/+qc/get_optimal_pulse_time.m deleted file mode 100644 index f11d48c4b..000000000 --- a/MATLAB/+qc/get_optimal_pulse_time.m +++ /dev/null @@ -1,10 +0,0 @@ -function [optimalTime, addTime, optimalNsamp, actualTime] = get_optimal_pulse_time(pulse, varargin) - - if ischar(pulse) - pulse = qc.load_pulse(pulse); - end - - actualTime = qc.get_pulse_duration(pulse, qc.join_params_and_dicts(varargin{:})); - [optimalTime, addTime, optimalNsamp] = qc.get_optimal_awg_time(actualTime); - - \ No newline at end of file diff --git a/MATLAB/+qc/get_program.m b/MATLAB/+qc/get_program.m deleted file mode 100644 index f5d0a79c8..000000000 --- a/MATLAB/+qc/get_program.m +++ /dev/null @@ -1,12 +0,0 @@ -function [program, channels] = get_program(pulse, varargin) - - if ischar(pulse) - pulse = qc.load_pulse(pulse); - end - - instantiated_pulse = qc.instantiate_pulse(pulse, 'parameters', qc.join_params_and_dicts(varargin{:})); - - tmp = py.qctoolkit.hardware.program.MultiChannelProgram(instantiated_pulse); - program = py.next(py.iter(tmp.programs.values())); - channels = py.next(py.iter(tmp.programs.keys())); - \ No newline at end of file diff --git a/MATLAB/+qc/get_pulse_duration.m b/MATLAB/+qc/get_pulse_duration.m deleted file mode 100644 index 50aaaa154..000000000 --- a/MATLAB/+qc/get_pulse_duration.m +++ /dev/null @@ -1,15 +0,0 @@ -function pulse_length = get_pulse_duration(pulse_template, parameters) - % Return pulse length in s - - kwargs = cell2namevalpairs(fieldnames(parameters), struct2cell(parameters)); - pulse_length = py.getattr(pulse_template, 'duration').evaluate_numeric(pyargs(kwargs{:}))*1e-9; - - -function cellarr = cell2namevalpairs(field_names, values) - cellarr={}; - for k = 1:numel(field_names) - cellarr{end+1}=field_names{k}; - cellarr{end+1}=values{k}; - end - - \ No newline at end of file diff --git a/MATLAB/+qc/get_pulse_params.m b/MATLAB/+qc/get_pulse_params.m deleted file mode 100644 index 210a67ada..000000000 --- a/MATLAB/+qc/get_pulse_params.m +++ /dev/null @@ -1,9 +0,0 @@ -function pulseParameters = get_pulse_params(pulse_name_or_template) - - if ischar(pulse_name_or_template) - pulse_template = qc.load_pulse(pulse_name_or_template); - else - pulse_template = pulse_name_or_template; - end - pulseParameters = sort(util.py.py2mat(pulse_template.parameter_names)); - \ No newline at end of file diff --git a/MATLAB/+qc/get_pumping_from_awg.m b/MATLAB/+qc/get_pumping_from_awg.m deleted file mode 100644 index 605c7b81b..000000000 --- a/MATLAB/+qc/get_pumping_from_awg.m +++ /dev/null @@ -1,68 +0,0 @@ -function pumpingConfig = get_pumping_from_awg(varargin) - -global plsdata - -defaultArgs = struct(... - 'programName', plsdata.awg.currentProgam ... - ); -args = util.parse_varargin(varargin, defaultArgs); - -pumpingConfig = struct(); - -seqTable = qc.get_sequence_table(args.programName, false); -seqTableCheck = true; -report = ''; -pumpSubTab = seqTable{1}{end}; - - - - - -%---------------- some checks --------------------------------------------- - -%check if there are six entries for the three pumping types for each qubit -if length(pumpSubTab) < 6 - seqTableCheck = false; - report = ' -- There are not six waveforms at the end of the sequence table! They might be put together or not uploaded or not at the end of the sequence table. -- '; -end - -%test if every waveform is different/has a different -if seqTableCheck - for i = 0:5 - for j = 0:5 - if (i~=j) && (pumpSubTab{end-i}{2} == pumpSubTab{end-j}{2}) - report = ' -- Not all waveforms for pumping (that are assumed to be different) are different to each other! -- '; - seqTableCheck = false; - end - end - end -end - -%test if both channel pairs have the same pumping sequence table part -for i = 1:6 - if seqTableCheck && ~isequal(seqTable{1}{end}{end-i+1}, seqTable{2}{end}{end-i+1}) - report = ' -- Not the same pumping configuration on both channel pairs of the AWG! -- '; - seqTableCheck = false; - end -end - - - - -%------------- reading out the pumping configuration ---------------------- - -if ~seqTableCheck - warning(report); -else - pumpingConfig.n_s_AB = pumpSubTab{end-5}{1}; - pumpingConfig.n_t_AB = pumpSubTab{end-4}{1}; - pumpingConfig.n_cs_AB = pumpSubTab{end-3}{1}; - pumpingConfig.n_s_CD = pumpSubTab{end-2}{1}; - pumpingConfig.n_t_CD = pumpSubTab{end-1}{1}; - pumpingConfig.n_cs_CD = pumpSubTab{end-0}{1}; -end - - - - -end \ No newline at end of file diff --git a/MATLAB/+qc/get_segment_waveform.m b/MATLAB/+qc/get_segment_waveform.m deleted file mode 100644 index 8526e4906..000000000 --- a/MATLAB/+qc/get_segment_waveform.m +++ /dev/null @@ -1,53 +0,0 @@ -function [wf1, wf2] = get_segment_waveform(program_name, channel_pair_index, memory_index, awg_channel_pair_identifiers) -% Get Wafeform of Sequencer Table Element -% PLEASE NOTE: works only for the Tabor AWG SIMULATOR -% PLEASE NOTE: program gets armed by calling this function -% -% --- Outputs ------------------------------------------------------------- -% wf1 : first channel y-values of AWG channelpair -% wf2 : second channel y-values of AWG channelpair -% -% --- Inputs -------------------------------------------------------------- -% program_name : Program name for which wafeform is -% returned -% channel_pair_index : 1 for channelpair AB and 2 for channelpair -% CD. Also see awg_channel_pair_identifier -% input -% memory_index : identifier number of element at the Tabor -% AWG (corresponds to second column in -% Sequencer Table -% awg_channel_pair_identifiers : Some substring in the channel pair -% identifiers to be matched. Sequence tables -% are sorted in the same order as channel -% pair identifiers substrings passed in this -% variable. Default is {'AB', 'CD'}. -% -% ------------------------------------------------------------------------- -% 2018/08 Marcel Meyer -% (marcel.meyer1@rwth-aachen.de) - -global plsdata - hws = plsdata.awg.hardwareSetup; - - if nargin < 4 || isempty(awg_channel_pair_identifiers) - awg_channel_pair_identifiers = {'AB', 'CD'}; - end - - known_awgs = util.py.py2mat(hws.known_awgs); - sort_indices = cellfun(@(x)(find( cellfun(@(y)(~isempty(strfind(char(x.identifier), y))), awg_channel_pair_identifiers) )), known_awgs); - known_awgs = known_awgs(sort_indices); - - %one has to arm the program to access the plottableProgram object of the - %program - known_awgs{channel_pair_index}.arm(program_name); - - plottableProgram = known_awgs{channel_pair_index}.read_complete_program(); - - wf1 = plottableProgram.get_segment_waveform(uint8(0), uint8(memory_index)); - wf2 = plottableProgram.get_segment_waveform(uint8(1), uint8(memory_index)); - - wf1 = util.py.py2mat(wf1); - wf2 = util.py.py2mat(wf2); - - wf1 = cell2mat(wf1); - wf2 = cell2mat(wf2); \ No newline at end of file diff --git a/MATLAB/+qc/get_sequence_table.m b/MATLAB/+qc/get_sequence_table.m deleted file mode 100644 index 489c6e835..000000000 --- a/MATLAB/+qc/get_sequence_table.m +++ /dev/null @@ -1,91 +0,0 @@ -function seq_table = get_sequence_table(program_name, advanced_seq_table_flag, awg_channel_pair_identifiers, verbosity, return_python_list) -% GET_SEQUENCE_TABLE Get sequence table of program on Tabor AWG -% (not actually from AWG but from the qctoolkit Tabor Driver instance) -% -% --- Outputs ------------------------------------------------------------- -% seq_table : Cell of sequence tables for each Tabor -% channel pair -% -% --- Inputs -------------------------------------------------------------- -% program_name : Program name for which sequence table is -% returned -% advanced_seq_table_flag : Get advanced sequence table if true. -% Default is false. -% awg_channel_pair_identifiers : Some substring in the channel pair -% identifiers to be matched. Sequence tables -% are sorted in the same order as channel -% pair identifiers substrings passed in this -% variable. Default is {'AB', 'CD'}. -% verbosity : Print sequence table to command line. -% Default is 0. -% return_python_list : Returns a python list object instead of a -% matlab cell. This makes the function -% faster as the conversion is slow. -% Dafault is false. -% -% ------------------------------------------------------------------------- -% (c) 2018/06 Pascal Cerfontaine and Marcel Meyer -% (cerfontaine@physik.rwth-aachen.de) - -global plsdata -hws = plsdata.awg.hardwareSetup; - -if nargin < 2 || isempty(advanced_seq_table_flag) - advanced_seq_table_flag = false; -end -if nargin < 3 || isempty(awg_channel_pair_identifiers) - awg_channel_pair_identifiers = {'AB', 'CD'}; -end -if nargin < 4 || isempty(verbosity) - verbosity = 0; -end -if nargin < 5 || isempty(return_python_list) - return_python_list = false; -end -if advanced_seq_table_flag - seq_txt = 'A'; -else - seq_txt = ''; -end - -known_awgs = util.py.py2mat(hws.known_awgs); -sort_indices = cellfun(@(x)(find( cellfun(@(y)(~isempty(strfind(char(x.identifier), y))), awg_channel_pair_identifiers) )), known_awgs); -known_awgs = known_awgs(sort_indices); - -for k = 1:length(known_awgs) - known_programs{k} = util.py.py2mat(py.getattr(known_awgs{k}, '_known_programs')); - - if verbosity > 0 - util.disp_section(sprintf('%s %sST: %s', awg_channel_pair_identifiers{k}, seq_txt, program_name)); - end - - if isfield(known_programs{k}, program_name) - tabor_program{k} = known_programs{k}.(program_name){2}; - - if advanced_seq_table_flag - seq_table{k} = py.getattr(tabor_program{k}, '_advanced_sequencer_table'); - else - seq_table{k} = py.getattr(tabor_program{k}, '_sequencer_tables'); - end - - if verbosity > 0 - disp(seq_table{k}); - end - - if ~return_python_list - seq_table{k} = util.py.py2mat(seq_table{k}); - end - else - tabor_program{k} = {}; - seq_table{k} = {}; - - if verbosity > 0 - disp(' Program not present'); - end - end -end - -if verbosity > 0 - fprintf('\n'); - util.disp_section(); -end diff --git a/MATLAB/+qc/get_sequence_table_from_simulator.m b/MATLAB/+qc/get_sequence_table_from_simulator.m deleted file mode 100644 index 776584024..000000000 --- a/MATLAB/+qc/get_sequence_table_from_simulator.m +++ /dev/null @@ -1,99 +0,0 @@ -function seq_table = get_sequence_table_from_simulator(program_name, advanced_seq_table_flag, awg_channel_pair_identifiers, verbosity, return_python_list) -% GET_SEQUENCE_TABLE Get sequence table of program on Tabor AWG Simulator -% PLEASE NOTE: the program gets armed by the function -% -% --- Outputs ------------------------------------------------------------- -% seq_table : Cell of sequence tables for each Tabor -% channel pair -% -% --- Inputs -------------------------------------------------------------- -% program_name : Program name for which sequence table is -% returned -% advanced_seq_table_flag : Get advanced sequence table if true. -% Default is false. -% awg_channel_pair_identifiers : Some substring in the channel pair -% identifiers to be matched. Sequence tables -% are sorted in the same order as channel -% pair identifiers substrings passed in this -% variable. Default is {'AB', 'CD'}. -% verbosity : Print sequence table to command line. -% Default is 0. -% return_python_list : Returns a python list object instead of a -% matlab cell. This makes the function -% faster as the conversion is slow. -% Dafault is false. -% -% ------------------------------------------------------------------------- -% 2018/08 Marcel Meyer -% based on qc.get_sequence_table by Pascal Cerfontaine and Marcel Meyer -% (marcel.meyer1@rwth-aachen.de) - - global plsdata - hws = plsdata.awg.hardwareSetup; - - if nargin < 2 || isempty(advanced_seq_table_flag) - advanced_seq_table_flag = false; - end - if nargin < 3 || isempty(awg_channel_pair_identifiers) - awg_channel_pair_identifiers = {'AB', 'CD'}; - end - if nargin < 4 || isempty(verbosity) - verbosity = 0; - end - - if nargin < 5 || isempty(return_python_list) - return_python_list = false; - end - - if advanced_seq_table_flag - seq_txt = 'A'; - else - seq_txt = ''; - end - - known_awgs = util.py.py2mat(hws.known_awgs); - sort_indices = cellfun(@(x)(find( cellfun(@(y)(~isempty(strfind(char(x.identifier), y))), awg_channel_pair_identifiers) )), known_awgs); - known_awgs = known_awgs(sort_indices); - - for k = 1:length(known_awgs) - known_programs{k} = util.py.py2mat(py.getattr(known_awgs{k}, '_known_programs')); - - if verbosity > 0 - util.disp_section(sprintf('%s %sST: %s', awg_channel_pair_identifiers{k}, seq_txt, program_name)); - end - - if isfield(known_programs{k}, program_name) - - % one has to arm the program before accessing its plottableProgram - % object - known_awgs{k}.arm(program_name); - plottableProgram = known_awgs{k}.read_complete_program(); - - if advanced_seq_table_flag - seq_table{k} = py.getattr(plottableProgram, '_advanced_sequence_table'); - else - seq_table{k} = py.getattr(plottableProgram, '_sequence_tables'); - end - - if verbosity > 0 - disp(seq_table{k}); - end - - if ~return_python_list - seq_table{k} = util.py.py2mat(seq_table{k}); - end - - else - tabor_program{k} = {}; - seq_table{k} = {}; - - if verbosity > 0 - disp(' Program not present'); - end - end - end - -if verbosity > 0 - fprintf('\n'); - util.disp_section(); -end \ No newline at end of file diff --git a/MATLAB/+qc/instantiate_pulse.m b/MATLAB/+qc/instantiate_pulse.m deleted file mode 100644 index 9e91c397c..000000000 --- a/MATLAB/+qc/instantiate_pulse.m +++ /dev/null @@ -1,44 +0,0 @@ -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 - - -function mappingStruct = replace_empty_with_pynone(mappingStruct) - - for fn = fieldnames(mappingStruct)' - if isempty(mappingStruct.(fn{1})) - mappingStruct.(fn{1}) = py.None; - end - end - -end - diff --git a/MATLAB/+qc/is_dict.m b/MATLAB/+qc/is_dict.m deleted file mode 100644 index 4e37478c7..000000000 --- a/MATLAB/+qc/is_dict.m +++ /dev/null @@ -1,4 +0,0 @@ -function bool = is_dict(dp) - delim = '___'; - bool = ischar(dp) || (isstruct(dp) && isfield(dp, strcat('dict', delim, 'name'))); -end \ No newline at end of file diff --git a/MATLAB/+qc/is_instantiated_pulse.m b/MATLAB/+qc/is_instantiated_pulse.m deleted file mode 100644 index 6d4d394e3..000000000 --- a/MATLAB/+qc/is_instantiated_pulse.m +++ /dev/null @@ -1,3 +0,0 @@ -function bool = is_instantiated_pulse(pulse) - bool = strcmp(class(pulse), 'py.qctoolkit._program.instructions.ImmutableInstructionBlock'); - bool = bool || strcmp(class(pulse), 'py.qctoolkit._program._loop.Loop'); diff --git a/MATLAB/+qc/join_params_and_dicts.m b/MATLAB/+qc/join_params_and_dicts.m deleted file mode 100644 index a80f58d6e..000000000 --- a/MATLAB/+qc/join_params_and_dicts.m +++ /dev/null @@ -1,62 +0,0 @@ -function parameters = join_params_and_dicts(varargin) - % Each argument is either a dictionary name as a string, a dictionary - % struct or a parameter struct. This function joins all input arguments to - % a single parameters struct. If conflicts occur, the field of the rightmost - % argument takes precedence. - % - % Dictionaries are saved as json files in the repository qctoolkit-dicts. - % Each dictionary is a struct with a field for each pulse. The field names - % are the same as the pulse names (as pulse names are unique identifiers). - % Each field has subfields which store pulse parameters for that pulse. - % - % When defining pulses in qctoolkit, prefix each parameter name with the - % pulsename followed by three underscores. I.e. for a pules 'meas' the - % parameter 'waiting_time' should be called 'meas___waiting_time'. The - % dictionary however should only have the parameter field 'waiting_time', - % i.e struct('meas', struct('waiting_time', 1)). - % - % Each dictionary also has a global field which also contains a struct. The - % field names of this struct refer to parameters which can be set to the - % same value across different pulses in the same dictionary. For example - % struct('global', struct('waiting_time', 1)) will set the parameter - % 'waiting_time' to 1 for all pulses which have the parameter - % 'waiting_time' in the dictionary where the global is defined. - % - % Globals do not take precedence over parameters passed in more to the - % right. - % - % Each dictionary also has a field 'dict___name' which specifies - % the dictionary name - - if numel(varargin) == 1 && iscell(varargin{1}) - p = varargin{1}; - else - p = varargin; - end - p = cellfun(@qc.load_dict, p, 'UniformOutput', false); - p = cellfun(@qc.dict_apply_globals, p, 'UniformOutput', false); - p = cellfun(@qc.dict_to_parameter_struct, p, 'UniformOutput', false); - - parameters = struct(); - for k = 1:numel(p) - parameters = join(parameters, p{k}); - end - - parameters = qc.array2row(parameters); - parameters = orderfields(parameters); - -end - - -function p = join(p1, p2) - % Join two parameter structs. This process is additive, fields in p2 take - % precedence - p = p1; - - for paramName = fieldnames(p2)' - p.(paramName{1}) = p2.(paramName{1}); - end -end - - - diff --git a/MATLAB/+qc/join_structs.m b/MATLAB/+qc/join_structs.m deleted file mode 100644 index fd8e8ab2d..000000000 --- a/MATLAB/+qc/join_structs.m +++ /dev/null @@ -1,17 +0,0 @@ -function S = join_structs(varargin) - % Fields of arguments passed in more on the left get overwritten by - % arguments passed in more to the right - - S = struct(); - for v = varargin - S = join_2_structs(S, v{1}); - end - -end - -function A = join_2_structs(A, B) - % Fields in B are added to A, fields in A with same name get overwritten - for f = fieldnames(B).' - A.(f{1}) = B.(f{1}); - end -end \ No newline at end of file diff --git a/MATLAB/+qc/load_dict.m b/MATLAB/+qc/load_dict.m deleted file mode 100644 index 100bcf164..000000000 --- a/MATLAB/+qc/load_dict.m +++ /dev/null @@ -1,56 +0,0 @@ -function dict_string_or_struct = load_dict(dict_string_or_struct, create_dict) - % Load dict if d is a string. Otherwise leave d untouched. - % - % Important: this does not (re)load a dict if the passed in variable is - % already a struct. - % - % You can specifiy a suffix to be appended to all pulse names in the - % dictionary separated by a space, i.e. if the dictionary name is - % 'common' you can pass 'common d12' and '_d12' will be appended to each - % pulse name. - - global plsdata - delim = '___'; - - if nargin < 2 || isempty(create_dict) - create_dict = false; - end - create_dict = create_dict || isempty(dict_string_or_struct); - - if ischar(dict_string_or_struct) - dict_string_or_struct = strsplit(dict_string_or_struct, ' '); - - if numel(dict_string_or_struct) > 1 - suffix = ['_' dict_string_or_struct{2}]; - else - suffix = ''; - end - dict_string_or_struct = dict_string_or_struct{1}; - - file_name = fullfile(plsdata.dict.path, [dict_string_or_struct '.json']); - if exist(file_name, 'file') - text = fileread(file_name); - dict_string_or_struct = jsondecode(text); - dict_string_or_struct = qc.array2row(dict_string_or_struct); - elseif create_dict - if strcmp(suffix, '') - dict_string_or_struct = struct(strcat('dict', delim, 'name'), dict_string_or_struct, 'global', struct()); - else - error('Cannot create dictionary ''%s %s'' since it contains a space', dict_string_or_struct, suffix(2:end)); - end - else - error('Dictionary ''%s'' does not exist', dict_string_or_struct); - end - - if ~strcmp(suffix, '') - for fn = fieldnames(dict_string_or_struct)' - if ~strcmp(fn{1}, strcat('dict', delim, 'name')) && ~strcmp(fn{1}, 'global') - dict_string_or_struct.([fn{1} suffix]) = dict_string_or_struct.(fn{1}); - dict_string_or_struct = rmfield(dict_string_or_struct, fn{1}); - end - end - end - - end - -end \ No newline at end of file diff --git a/MATLAB/+qc/load_pulse.m b/MATLAB/+qc/load_pulse.m deleted file mode 100644 index 4c651bb92..000000000 --- a/MATLAB/+qc/load_pulse.m +++ /dev/null @@ -1,4 +0,0 @@ -function pulse = load_pulse(pulse_name) - - global plsdata - pulse = plsdata.qc.pulse_storage{pulse_name}; \ No newline at end of file diff --git a/MATLAB/+qc/operations_to_python.m b/MATLAB/+qc/operations_to_python.m deleted file mode 100644 index 5790f0b5d..000000000 --- a/MATLAB/+qc/operations_to_python.m +++ /dev/null @@ -1,34 +0,0 @@ -function pyOperations = operations_to_python(operations) - % Convert operations struct from matlab to python - - pyOperations = {}; - - for k = 1:numel(operations) - - if numel(operations{k}) > 1 - args = operations{k}(2:end); - else - args = {}; - end - switch(operations{k}{1}) - case 'AlgebraicMoment' - pyOp = py.atsaverage.operations.AlgebraicMoment(args{:}); - case 'Downsample' - pyOp = py.atsaverage.operations.Downsample(args{:}); - case 'Histogram' - pyOp = py.atsaverage.operations.Histogram(args{:}); - case 'RepAverage' - pyOp = py.atsaverage.operations.RepAverage(args{:}); - case 'RepeatedDownsample' - pyOp = py.atsaverage.operations.RepeatedDownsample(args{:}); - otherwise - error('Operation %s not recognized', operations{k}{1}); - end - pyOperations{end+1} = pyOp; - - end - - pyOperations = py.list(pyOperations); - - - diff --git a/MATLAB/+qc/params_add_delim.m b/MATLAB/+qc/params_add_delim.m deleted file mode 100644 index dca1ed276..000000000 --- a/MATLAB/+qc/params_add_delim.m +++ /dev/null @@ -1,9 +0,0 @@ -function parameters = params_add_delim(parameters, pulse_name) - % Prefix all parameters in pulseTemplate with the pulse name followed by - % three underscores (needed for dictionaries). - - delim = '___'; - for fn = fieldnames(parameters)' - parameters.(strcat(pulse_name, delim, fn{1})) = parameters.(fn{1}); - parameters = rmfield(parameters, fn{1}); - end \ No newline at end of file diff --git a/MATLAB/+qc/params_rm_delim.m b/MATLAB/+qc/params_rm_delim.m deleted file mode 100644 index 6a498e4af..000000000 --- a/MATLAB/+qc/params_rm_delim.m +++ /dev/null @@ -1,13 +0,0 @@ -function [parameters, pulse_name] = params_rm_delim(parameters) - % Remove prefix from parameters identified by three underscores - % (needed for dictionaries). - - delim = '___'; - fn = fieldnames(parameters)'; - [i1, i2] = regexp(fn{1}, '^.+___'); - pulse_name = fn{1}(i1:i2-numel(delim)); - - for fn = fieldnames(parameters)' - parameters.(fn{1}(i2+1:end)) = parameters.(fn{1}); - parameters = rmfield(parameters, fn{1}); - end \ No newline at end of file diff --git a/MATLAB/+qc/personalPaths_README.txt b/MATLAB/+qc/personalPaths_README.txt deleted file mode 100644 index ee36d7590..000000000 --- a/MATLAB/+qc/personalPaths_README.txt +++ /dev/null @@ -1,22 +0,0 @@ -README path file for qc.qctoolkitTestSetup ------------------------------------------- -written by M. Meyer 10|2018 - - -Run the following code after inserting paths to create a path file. The generated file should be on the git ignore list. - - -%% ---------------------------- -a = struct(); -a.pulses_repo = ''; -a.dicts_repo = ''; -a.loadPath = ''; -a.tunePath = ''; -a.loadFile = ''; -a.taborDriverPath = ''; - -quPulsePath = '...\qc-toolkit\MATLAB\+qc'; - -personalPathsStruct = a; -save([pathFile_save_path '\personalPaths'], personalPathsStruct); -%% ----------------------------- \ No newline at end of file diff --git a/MATLAB/+qc/plot_program_tree.m b/MATLAB/+qc/plot_program_tree.m deleted file mode 100644 index 960e12358..000000000 --- a/MATLAB/+qc/plot_program_tree.m +++ /dev/null @@ -1,60 +0,0 @@ -function plot_program_tree(program, maxElements, figId) - % Modified from Wolfie at - % https://stackoverflow.com/questions/45666560/ploting-a-nested-cell-as-a-tree-using-treeplot-matlab/45676012 - - if nargin < 2 || isempty(maxElements) - maxElements = 10; - end - if nargin < 3 || isempty(figId) - figId = 120; - end - - [treearray, nodeVals] = getTreeArray(program, maxElements); - - figure(figId); clf; - treeplot(treearray); - title(sprintf(['Duration %g' 10 'Showing first %g entries'], double(program.duration.numerator/program.duration.denominator), maxElements)) - - % Get the position of each node on the plot - [x, y] = treelayout(treearray); - - % Get the indices of the nodes which have values stored - nodeIndices = cell2mat(nodeVals(1,:)); - - % Get the labels (values) corresponding to those nodes. Must be strings in cell array - labels = cellfun(@(x)(double(x.repetition_count)), nodeVals(2,:), 'uniformoutput', 0); - - % Add labels, with a vertical offset to the y coords so that labels don't sit on nodes - text(x(nodeIndices), y(nodeIndices) - 0.03, labels); - -end - -function [treearray, nodeVals] = getTreeArray(program, maxElements) - % Initialise the array construction from node 0 - - children = program.children(1:min(maxElements, end)); - - [nodes, ~, nodeVals] = treebuilder(children, 1); - treearray = [0, nodes]; - nodeVals(:, end+1) = {1; program}; - - % Recursive tree building function - function [nodes, currentNode, nodeVals] = treebuilder(children, rootNode) - % Set up variables to be populated whilst looping - nodes = []; nodeVals = {}; - - % Start node off at root node - currentNode = rootNode; - - % Loop over array elements, either recurse or add node - for ii = 1:min(size(children, 2)) - currentNode = currentNode + 1; - try - nodeVals = [nodeVals, {currentNode; children{ii}}]; - [subtreeNodes, currentNode, newNodeVals] = treebuilder(children{ii}.children, currentNode); - nodes = [nodes, rootNode, subtreeNodes]; - nodeVals = [nodeVals, newNodeVals]; - end - end - end -end \ No newline at end of file diff --git a/MATLAB/+qc/plot_pulse.m b/MATLAB/+qc/plot_pulse.m deleted file mode 100644 index f63c67b42..000000000 --- a/MATLAB/+qc/plot_pulse.m +++ /dev/null @@ -1,178 +0,0 @@ -function [t, channels, measurements, instantiatedPulse] = plot_pulse(pulse, varargin) - - global plsdata - - defaultArgs = struct(... - 'sample_rate', plsdata.awg.sampleRate, ... % in 1/s, converted to 1/ns below - 'channel_names', {{}}, ... % names of channels to plot, all if empty - 'parameters', struct(), ... - 'channel_mapping', [], ... - 'window_mapping' , py.None, ... - 'fig_id', plsdata.qc.figId, ... - 'subplots', [121 122], ... - 'charge_diagram_data', {{}}, ... % inputs to imagesc - 'clear_fig', true, ... - 'charge_diagram', {{'X', 'Y'}}, ... - 'plot_charge_diagram', true, ... - 'lead_points', 1e-3*[-4 -1; -1 -2; 0 -4; 4 0; 2 1; 1 4], ... - 'special_points', struct('M', [0 0], 'R1', [-2.5e-3 -3.75e-3], 'R2', [-2e-3 1e-3], 'S', [-2e-3 -1e-3], 'Tp', [1.75e-3 0], 'STp', [1e-3 -1e-3]), ... - 'plot_range', [-8e-3 8e-3], ... - 'max_n_points', 1e5,... - 'dont_plot', false ... - ); - - args = util.parse_varargin(varargin, defaultArgs); - - if isempty(args.channel_mapping) - args.channel_mapping = py.dict(py.zip(pulse.defined_channels, pulse.defined_channels)); - end - - args.sample_rate = args.sample_rate * 1e-9; % convert to 1/ns - instantiatedPulse = qc.instantiate_pulse(pulse, 'parameters', args.parameters, 'channel_mapping', args.channel_mapping, 'window_mapping', args.window_mapping); - - if ~qc.is_instantiated_pulse(pulse) - nPoints = qc.get_pulse_duration(pulse, args.parameters) * args.sample_rate * 1e9; - if nPoints > args.max_n_points - warning('Number of points %g > %g (maximum number of points). Aborting.\n', ceil(nPoints), args.max_n_points); - return - end - end - - try - data = util.py.py2mat(py.qctoolkit.pulses.plotting.render(instantiatedPulse, pyargs('sample_rate', args.sample_rate, 'render_measurements', true))); - catch err - warning('The following error occurred when plotting. This might have to do with Python dicts not being convertable to Matlab because of illegal struct field name:\n%s', err.getReport()) - end - - t = data{1}*1e-9; - - channels = data{2}; - if ~isempty(args.plot_range) - for chan_name = fieldnames(channels)' - channels.(chan_name{1}) = util.clamp(channels.(chan_name{1}), args.plot_range); - end - end - measurements = struct(); - for m = data{3} - if ~isfield(measurements, m{1}{1}) - measurements.(m{1}{1}) = []; - end - if strcmp(class(m{1}{2}), 'py.fractions.Fraction') - m{1}{2} = m{1}{2}.numerator/m{1}{2}.denominator; - end - if strcmp(class(m{1}{3}), 'py.fractions.Fraction') - m{1}{3} = m{1}{3}.numerator/m{1}{3}.denominator; - end - measurements.(m{1}{1})(end+1, 1:2) = [m{1}{2} m{1}{2}+m{1}{3}] * 1e-9; - end - - if args.dont_plot - return; - end - - plotChargeDiagram = args.plot_charge_diagram && ~isempty(args.charge_diagram) && all(cellfun(@(x)(isfield(channels, x)), args.charge_diagram)); - - hFig = figure(args.fig_id); - if ~qc.is_instantiated_pulse(pulse) - pulseName = sprintf('Pulse: %s', char(pulse.identifier)); - else - pulseName = 'Pulse'; - end - set(hFig, 'Name', pulseName); - if args.clear_fig - clf - end - if plotChargeDiagram || numel(args.subplots) == 1 - subplot(args.subplots(1)); - end - hold on - - legendEntries = {}; - legendHandles = []; - - for meas_name = fieldnames(measurements)' - hLines = plot(measurements.(meas_name{1}).', measurements.(meas_name{1}).'*0, 'lineWidth', 100, 'displayName', ['Meas: ' meas_name{1}]); - for h = hLines(:)' - color = rgb2hsv(h.Color); - h.Color = hsv2rgb([color(1) 0.1 1]); - end - legendHandles(end+1) = h(1); - legendEntries{end+1} = ['Meas: ' meas_name{1}]; - end - - for chan_name = fieldnames(channels)' - if isempty(args.channel_names) || any(cellfun(@(x)(strcmp(x, chan_name{1})), args.channel_names)) - h = util.rectplot(t, channels.(chan_name{1}), '.-'); - legendHandles(end+1) = h(1); - legendEntries{end+1} = ['Chan: ' chan_name{1}]; - end - end - - if ~isempty(args.plot_range) - title(['Plot range: ' sprintf('%g ', args.plot_range)]); - end - xlabel('t(s)'); - [~, hObj] = legend(legendHandles, legendEntries); - hObj = findobj(hObj, 'type', 'line'); - set(hObj, 'lineWidth', 2); - - if plotChargeDiagram - subplot(args.subplots(2)); - hold on - ax = gca; - userData = get(ax, 'userData'); - if ~isempty(args.plot_range) - title(['Plot range: ' sprintf('%g ', args.plot_range)]); - end - - if ~isempty(args.charge_diagram_data) - if numel(size(squeeze(args.charge_diagram_data{3}))) == 3 - args.charge_diagram_data{3} = squeeze(nanmean(args.charge_diagram_data{3},1)); - end - imagesc(args.charge_diagram_data{:}); - end - - if isempty(userData) || ~isstruct(userData) || ~isfield(userData, 'leadsPlotted') || ~userData.leadsPlotted - color = [0 0 0 0.1]; - lineWidth = 3; - - if ~isempty(args.lead_points) - plot(args.lead_points(1:3,1), args.lead_points(1:3,2), '-', 'lineWidth', lineWidth, 'color', color); - plot(args.lead_points(4:6,1), args.lead_points(4:6,2), '-', 'lineWidth', lineWidth, 'color', color); - plot(args.lead_points([2 5],1), args.lead_points([2 5],2), '--', 'lineWidth', lineWidth, 'color', color); - - offset = abs(max(args.lead_points(:))-min(args.lead_points(:)))*0.05; - else - offset = 4e-4; - end - - for name = fieldnames(args.special_points)' - xy = args.special_points.(name{1}); - plot(xy(1), xy(2), 'g.', 'markerSize', 24, 'color', color); - text(xy(1), xy(2)+offset, name{1}, 'horizontalAlignment', 'center', 'color', color); - end - - set(gca, 'UserData', struct('leadsPlotted', true)); - end - - x = channels.(args.charge_diagram{1}); - y = channels.(args.charge_diagram{2}); - - ax.ColorOrderIndex = 1; - lineWidth = 1; - h = plot(x, y, '.-', 'markerSize', 8, 'lineWidth', lineWidth); - plot(x(1), y(1), 's', 'markerSize', 12, 'color', h.Color, 'lineWidth', lineWidth); - - dx = diff(x); - dy = diff(y); - r = sqrt(dx.^2 + dy.^2); - - nArrows = min(numel(r), floor(sum(r)/0.5e-3)); - [~, ind] = sort(r); - ind = ind(end-nArrows+1:end); - ind = sort(ind); - - quiver(x(ind), y(ind), dx(ind), dy(ind), 0, 'color', h.Color, 'lineWidth', lineWidth); - end - -end \ No newline at end of file diff --git a/MATLAB/+qc/plot_pulse_4chan.m b/MATLAB/+qc/plot_pulse_4chan.m deleted file mode 100644 index 61680fb99..000000000 --- a/MATLAB/+qc/plot_pulse_4chan.m +++ /dev/null @@ -1,80 +0,0 @@ -function [t, channels, measurements, instantiatedPulse] = plot_pulse_4chan(pulse, varargin) -% PLOT_PULSE_4CHAN Wrapper for plot_pulse specific for plotting pulses for -% two qubits with two control channels each -% -% (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', ''}} ... - ); -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 - - - -end \ No newline at end of file diff --git a/MATLAB/+qc/plot_tabor_pulse.m b/MATLAB/+qc/plot_tabor_pulse.m deleted file mode 100644 index c6355d39e..000000000 --- a/MATLAB/+qc/plot_tabor_pulse.m +++ /dev/null @@ -1,23 +0,0 @@ -function plot_tabor_pulse(awg) - -program = awg.read_complete_program(); - -wfs = util.py.py2mat(program.get_waveforms()); -reps = util.py.py2mat(program.get_repetitions()); -n_wfs = numel(wfs); - -f = figure; - - - -tabgroup = uitabgroup(mainfig, 'Position', [.05 .1 .9 .8]); - -for k = 1:n_wfs - tab(k)=uitab(tabgroup,'Title', sprintf('Wf_%i', k)); - - axes('parent',tab(k)) - - plot(wfs{k}); - - legend(sprintf('%i times', reps(k))); -end \ No newline at end of file diff --git a/MATLAB/+qc/program_to_struct.m b/MATLAB/+qc/program_to_struct.m deleted file mode 100644 index dfa327195..000000000 --- a/MATLAB/+qc/program_to_struct.m +++ /dev/null @@ -1,36 +0,0 @@ -function program = program_to_struct(program_name, pulse_template, parameters_and_dicts, channel_mapping, window_mapping, global_transformation) - - if ischar(pulse_template) - error('Variable ''pulse_template'' must not be of type char to make sure the correct pulse is saved. Please pass a pulse template.') - end - - % Make sure all dictionaries are loaded so not just saving strings - if ~iscell(parameters_and_dicts) - parameters_and_dicts = {parameters_and_dicts}; - end - parameters_and_dicts = cellfun(@qc.load_dict, parameters_and_dicts, 'UniformOutput', false); - - program = struct( ... - 'program_name', program_name, ... - 'pulse_template', qc.pulse_to_struct(pulse_template), ... - 'parameters_and_dicts', {parameters_and_dicts}, ... - 'channel_mapping', channel_mapping, ... - 'global_transformation', global_transformation, ... - 'window_mapping', window_mapping ... - ); - program.pulse_duration = qc.get_pulse_duration(pulse_template, qc.join_params_and_dicts(program.parameters_and_dicts)); - program.added_to_pulse_duration = 0; - - for name = fieldnames(program.channel_mapping)' - if strcmp(class(program.channel_mapping.(name{1})), 'py.NoneType') - program.channel_mapping.(name{1}) = py.None; - end - end - - for name = fieldnames(program.window_mapping)' - if strcmp(class(program.window_mapping.(name{1})), 'py.NoneType') - program.window_mapping.(name{1}) = py.None; - end - end - - \ No newline at end of file diff --git a/MATLAB/+qc/pulse_add_delim.m b/MATLAB/+qc/pulse_add_delim.m deleted file mode 100644 index a36e82d79..000000000 --- a/MATLAB/+qc/pulse_add_delim.m +++ /dev/null @@ -1,35 +0,0 @@ -function mapped_pulse_template = pulse_add_delim(pulse_template, pulse_name, set_pulse_identifier) - % Prefix all parameters in pulseTemplate with the pulse name followed by - % three underscores (needed for dictionaries). - - if nargin < 3 || isempty(set_pulse_identifier) - set_pulse_identifier = true; - end - - delim = '___'; - parameters = qc.get_pulse_params(pulse_template); - parameter_mapping = cell(1, 2*numel(parameters)); - for k = 1:numel(parameters) - parameter_mapping{1, 2*k-1} = parameters{k}; - parameter_mapping{1, 2*k} = strcat(pulse_name, delim, parameters{k}); - end - parameter_mapping = struct(parameter_mapping{:}); - - if set_pulse_identifier - mapped_pulse_template = py.qctoolkit.pulses.MappingPT( ... - pyargs( ... - 'template', pulse_template, ... - 'identifier', pulse_name, ... - 'parameter_mapping', parameter_mapping, ... - 'allow_partial_parameter_mapping', true ... - ) ... - ); - else - mapped_pulse_template = py.qctoolkit.pulses.MappingPT( ... - pyargs( ... - 'template', pulse_template, ... - 'parameter_mapping', parameter_mapping, ... - 'allow_partial_parameter_mapping', true ... - ) ... - ); - end \ No newline at end of file diff --git a/MATLAB/+qc/pulse_seq.m b/MATLAB/+qc/pulse_seq.m deleted file mode 100644 index d5b206af2..000000000 --- a/MATLAB/+qc/pulse_seq.m +++ /dev/null @@ -1,142 +0,0 @@ -function [pulse, args] = pulse_seq(pulses, varargin) -% PULSE_SEQ Summary -% Dynamically sequence qctoolkit pulses -% -% Note: All SequencePTs in this function are wrapped in a RepetitionPT -% if wrap_in_repetition_pt is true since this might lead to a more efficien -% efficient upload on the Tabor AWG according to Simon Humpohl. However, if -% the upload aborts with the error -% "The algorithm is not smart enough to make sequence tables shorter" -% you can set wrap_in_repetition_pt to false to possibly reduce the -% sequence table length. This will increase the AWG memory requirement. -% -% --- Outputs ------------------------------------------------------------- -% pulse : Sequenced pulse template -% args : Struct of all input arguments, including pulses -% -% --- Inputs -------------------------------------------------------------- -% pulses : Cell of pulse identifiers or pulse templates in sequence order -% varargin : Name-value pairs or struct -% -% ------------------------------------------------------------------------- -% (c) 2018/06 Pascal Cerfontaine (cerfontaine@physik.rwth-aachen.de) - global plsdata - - defaultArgs = struct( ... - 'repetitions', {num2cell(ones(1, numel(pulses)))}, ... % Repetition for each pulse - 'wrap_in_repetition_pt', true, ... % Wrap each SequencePT in a RepetitionPT with counter 1 - 'outerRepetition', 1, ... % Repetition for entire pulse, set to NaN to use a RepetitionPT with on repetition. - 'fill_time_min', NaN, ... % If not NaN, add fill_time = py.sympy.Max(fill_time, args.fill_time_min). - 'fill_param', '', ... % Not empty: Automatically add fill_pulse to achieve total time given by this parameter. - ... % 'auto': Determine fill time automatically for efficient upload on Tabor AWG - 'fill_pulse_param', 'wait___t', ... % Name of pulse parameter to use for total fill time - 'fill_pulse', 'wait', ... % Pulse template or identifier of pulse to use for filling (added to beginning of pulse sequence) - 'measurements', [], ... % Empty: Do not define any additional readout. - ... % Otherwise: Argument #1 to pyargs('measurements', #1) of SequencePT without fill - 'prefix', '' , ... % Prefix to add to each pulse parameters - 'identifier', '' , ... % Empty: Do not add an identifier - ... % Otherwise: Name of the final pulse - 'sampleRate', plsdata.awg.sampleRate, ... % In SI units (Sa/s), - 'minSamples', plsdata.awg.minSamples, ... % Minimum number of samples for fill pulse - 'sampleQuantum', plsdata.awg.sampleQuantum ... % Sample increments for fill pulse - ); - args = util.parse_varargin(varargin, defaultArgs); - args.pulses = pulses; - args.sampleRate = args.sampleRate/1e9; % in GSa/s - - wrap_in_repetition_pt = args.wrap_in_repetition_pt; - function pt = wrap(pt) - if wrap_in_repetition_pt - pt = py.qctoolkit.pulses.RepetitionPT(pt, 1); - end - end - - % Load and repeat pulses - for k = 1:numel(pulses) - if ischar(pulses{k}) - pulses{k} = qc.load_pulse(pulses{k}); - end - - if any(args.repetitions{k} ~= 1) - pulses{k} = py.qctoolkit.pulses.RepetitionPT(pulses{k}, args.repetitions{k}); - else - pulses{k} = wrap(pulses{k}); - end - end - - % Sequence pulses - if ~isempty(args.measurements) - pulse = wrap(py.qctoolkit.pulses.SequencePT(pulses{:}, pyargs('measurements', args.measurements))); - else - pulse = wrap(py.qctoolkit.pulses.SequencePT(pulses{:})); - end - - % Add fill if fill_param not empty - if ~isempty(args.fill_param) - duration = py.getattr(pulse, 'duration'); - if qc.is_instantiated_pulse(args.fill_pulse) - fill_pulse = args.fill_pulse; - else - fill_pulse = qc.load_pulse(args.fill_pulse); - end - - minDuration = py.sympy.sympify(args.minSamples/args.sampleRate, pyargs('rational', py.True)); - durationQuantum = py.sympy.sympify(args.sampleQuantum/args.sampleRate, pyargs('rational', py.True)); - if strcmp(args.fill_param, 'auto') - fill_time = ... - py.sympy.Max( py.sympy.ceiling(duration.sympified_expression/durationQuantum)*durationQuantum, minDuration ) ... - - duration.sympified_expression; - else - fill_time = py.sympy.Add(py.sympy.sympify(args.fill_param), -duration.sympified_expression); - end - - if ~any(isnan(args.fill_time_min)) - fill_time = py.sympy.Max(fill_time, args.fill_time_min); - end - - fill_pulse = py.qctoolkit.pulses.MappingPT( ... - pyargs( ... - 'template', fill_pulse, ... - 'parameter_mapping', qc.dict(args.fill_pulse_param, fill_time), ... - 'allow_partial_parameter_mapping', true ... - ) ... - ); - pulse = wrap(py.qctoolkit.pulses.SequencePT(fill_pulse, pulse)); - end - - % Add prefix to all pulse parameters (if not empty) - if ~isempty(args.prefix) - parameters = qc.get_pulse_params(pulse); - parameter_mapping = cell(1, 2*numel(parameters)); - for k = 1:numel(parameters) - parameter_mapping{1, 2*k-1} = parameters{k}; - parameter_mapping{1, 2*k} = strcat(args.prefix, parameters{k}); - end - - pulse = py.qctoolkit.pulses.MappingPT( ... - pyargs( ... - 'template', pulse, ... - 'parameter_mapping', qc.dict(parameter_mapping{:}), ... - 'allow_partial_parameter_mapping', true ... - ) ... - ); - end - - if any(isnan(args.outerRepetition)) - outerRepetition = 1; - else - outerRepetition = args.outerRepetition; - end - - % Add pulse identifier if identifier not empty - if ~isempty(args.identifier) - pulse = py.qctoolkit.pulses.RepetitionPT( ... - pulse, outerRepetition, ... - pyargs('identifier', args.identifier) ... - ); - elseif args.outerRepetition > 1 - pulse = py.qctoolkit.pulses.RepetitionPT( ... - pulse, args.outerRepetition); - end - -end \ No newline at end of file diff --git a/MATLAB/+qc/pulse_to_struct.m b/MATLAB/+qc/pulse_to_struct.m deleted file mode 100644 index 6d952fd61..000000000 --- a/MATLAB/+qc/pulse_to_struct.m +++ /dev/null @@ -1,15 +0,0 @@ -function pulseStruct = pulse_to_struct(pulseTemplate) - - backend = py.qctoolkit.serialization.DictBackend(); - serializer = py.qctoolkit.serialization.Serializer(backend); - - serializer.serialize(pulseTemplate); - pulseStruct = util.py.py2mat(backend.storage); - - if ~isfield(pulseStruct, 'main') - pulseStruct.main = char(pulseTemplate.identifier); - end - - - - \ No newline at end of file diff --git a/MATLAB/+qc/qcToolkitTestSetupUploadProgram.m b/MATLAB/+qc/qcToolkitTestSetupUploadProgram.m deleted file mode 100644 index 44b3a34b1..000000000 --- a/MATLAB/+qc/qcToolkitTestSetupUploadProgram.m +++ /dev/null @@ -1,67 +0,0 @@ -% set upload pulse -% upload_pulse = 'dnp_wait_dbz_4chan'; -% upload_pulse = 'decay_j_fid_4chan'; -% upload_pulse = 's_pumping_AB_4chan'; -% upload_pulse = 'pumping_s_stp'; -% upload_pulse = 'pumping_s'; -upload_scan = 'dnp_decay_dbz_fid_4chan'; -% upload_scan = 'line'; -% upload_scan = 'lead'; -% upload_scan = 'tl'; - - - - -%% Just an example with the Tabor AWG simulator -% awgctrl('default except offsets') -plsdata.awg.inst.send_cmd(':OUTP:COUP:ALL HV'); -args = tunedata.run{1}.(upload_scan).opts(1).scan.configfn(4).args; -args{end}.window_mapping.A = py.None; -args{end}.window_mapping.B = py.None; -args{end}.window_mapping.DBZFID_A = py.None; -args{end}.window_mapping.DBZFID_B = py.None; -args{end}.operations = {}; -feval(args{2:end}); - - -%% test scan to test if why one can not upload pump pulses created with qctk concatenate -myname = 'dnp_ABCD_4chan' - - -window_mapping = struct('A', py.None, 'B', py.None); - -myargs = struct('program_name', myname, ... - 'pulse_template', myname, ... - 'channel_mapping', struct('W', 'TABOR_A', 'X', 'TABOR_B', 'Y', 'TABOR_C', 'Z', 'TABOR_D', 'MTrig', 'TABOR_A_MARKER', 'M1', 'TABOR_B_MARKER', 'M2', 'TABOR_C_MARKER'), ... - 'parameters_and_dicts', {{'common', 'common_d12', 'common_d34', 'common_d12 d12', 'common_d34 d34'}}, ... - 'window_mapping', window_mapping ... - ); - -plsdata.awg.inst.send_cmd(':OUTP:COUP:ALL HV'); -myinput = {'awg_program', @qc.awg_program, 'add', myargs}; -feval(myinput{2:end}); - - -%% reset AWG -awgctrl('default except offsets'); - -%% check out sequ table wait_4chan -entries = qc.get_sequence_table('wait_4chan', false) -entries{1}{end} -entries{1}{end-1} -entries{1}{end-2} -entries{1}{end-5} - - -%% check seq table j_fid_4chan -entries = qc.get_sequence_table('j_fid_4chan', false) -entries{1}{end} -entries{1}{end-1} -entries{1}{end-2} -entries{1}{end-5} - -%% -entries = qc.get_sequence_table('wait_4chan', true) -disp(entries{1}{1}); -disp(entries{2}{1}); -% disp(entries{3}{1}); \ No newline at end of file diff --git a/MATLAB/+qc/qctoolkitTestSetup.m b/MATLAB/+qc/qctoolkitTestSetup.m deleted file mode 100644 index 32226a141..000000000 --- a/MATLAB/+qc/qctoolkitTestSetup.m +++ /dev/null @@ -1,94 +0,0 @@ -%% --- Test setup without AWG and Alazar (only qctoolkit) ----------------- -quPulsePath = fileparts(which('qc.qctoolkitTestSetup')); -load([quPulsePath '\personalPaths.mat']); -global plsdata -plsdata = struct( ... - 'path', personalPathsStruct.pulses_repo, ... - 'awg', struct('inst', [], 'hardwareSetup', [], 'sampleRate', 2e9, 'currentProgam', '', 'registeredPrograms', struct(), 'defaultChannelMapping', struct(), 'defaultWindowMapping', struct(), 'defaultParametersAndDicts', {{}}, 'defaultAddMarker', {{}}), ... - 'dict', struct('cache', [], 'path', personalPathsStruct.dicts_repo), ... - 'qc', struct('figId', 801), ... - 'daq', struct('inst', [], 'defaultOperations', {{}}, 'reuseOperations', false, 'operationsExternallyModified', false) ... - ); -plsdata.daq.instSmName = 'ATS9440Python'; -plsdata.qc.backend = py.qctoolkit.serialization.FilesystemBackend(plsdata.path); -plsdata.qc.serializer = py.qctoolkit.serialization.Serializer(plsdata.qc.backend); -% ------------------------------------------------------------------------- - -%% --- Test setup replicating the Triton 200 measurement setup ------------ -% Does not replicate Alazar functionality as there is no simulator -% Need the triton_200 repo on the path (for awgctrl) - -% Path for Triton 200 backups -loadPath = personalPathsStruct.loadPath; -pulsePath = plsdata.path; -dictPath = plsdata.dict.path; -tunePath = personalPathsStruct.tunePath; - -% Loading -try - copyfile(fullfile(loadPath, 'smdata_recent.mat'), fullfile(tunePath, 'smdata_recent.mat')); -catch err - warning(err.getReport()); -end -load(fullfile(tunePath, 'smdata_recent.mat')); -info = dir(fullfile(tunePath, 'smdata_recent.mat')); -fprintf('Loaded smdata from %s\n', datestr(info.datenum)); -try - copyfile(fullfile(loadPath, 'tunedata_recent.mat'), fullfile(tunePath, 'tunedata_recent.mat')); -% copyfile(fullfile(loadPath, 'tunedata_2018_06_25_21_08.mat'), fullfile(tunePath, 'tunedata_recent.mat')); -catch err - warning(err.getReport()); -end -load(fullfile(tunePath, 'tunedata_recent.mat')); -info = dir(fullfile(tunePath, 'tunedata_recent.mat')); -fprintf('Loaded tunedata from %s\n', datestr(info.datenum)); - -try - copyfile(fullfile(loadPath, 'plsdata_recent.mat'), fullfile(tunePath, 'plsdata_recent.mat')); -catch err - warning(err.getReport()); -end -load(fullfile(tunePath, 'plsdata_recent.mat')); -info = dir(fullfile(tunePath, 'plsdata_recent.mat')); -fprintf('Loaded plsdata from %s\n', datestr(info.datenum)); - -global tunedata -global plsdata -tunedata.run{tunedata.runIndex}.opts.loadFile = personalPathsStruct.loadPath; -import tune.tune - -plsdata.path = pulsePath; - -% Alazar dummy instrument (simulator not implemented yet) -smdata.inst(sminstlookup(plsdata.daq.instSmName)).data.address = 'simulator'; -plsdata.daq.inst = py.qctoolkit.hardware.dacs.alazar.AlazarCard([]); - -% Setup AWG -% Turns on AWG for short time but turns it off again -% Initializes hardware setup -% Can also be used for deleting all programs/resetting but then also need to setup Alazar again, i.e. the cell above and the three cells below ) -plsdata.awg.hardwareSetup = []; -qc.setup_tabor_awg('realAWG', false, 'simulateAWG', true, 'taborDriverPath', personalPathsStruct.taborDriverPath); - -% AWG default settings -awgctrl('default'); -plsdata.awg.inst.send_cmd(':OUTP:COUP:ALL HV'); - -% Alazar -% Execute after setting up the AWG since needs hardware setup initialized -% Need to test whether need to restart Matlab if execute -% qc.setup_alazar_measurements twice -qc.setup_alazar_measurements('nQubits', 2, 'nMeasPerQubit', 4, 'disp', true); - -% Qctoolkit -plsdata.qc.backend = py.qctoolkit.serialization.FilesystemBackend(pulsePath); -plsdata.qc.serializer = py.qctoolkit.serialization.Serializer(plsdata.qc.backend); -plsdata.dict.path = dictPath; - -% Tune -tunedata.run{tunedata.runIndex}.opts.path = tunePath; - - -import tune.tune -disp('done'); -% ------------------------------------------------------------------------- \ No newline at end of file diff --git a/MATLAB/+qc/qctoolkit_programs.m b/MATLAB/+qc/qctoolkit_programs.m deleted file mode 100644 index fe6b634fc..000000000 --- a/MATLAB/+qc/qctoolkit_programs.m +++ /dev/null @@ -1,55 +0,0 @@ -%% Charge scan - -%% 4 CHANs -pulseLocation = 'C:\Users\lablocal\Documents\PYTHON\qc-toolkit-pulses'; -charge_scan_pulse = qctoolkit.load_pulse('general_charge_scan', pulseLocation); - -charge_scan_pulse_params = struct( ... - 'N_y', 10, ... - 't_wait', 0, ... % ns - 'y_stop', 1, ... - 'x_stop', 1, ... - 'x_start', -1, ... - 'N_x', 10, ... - 't_meas', 192, ... - 'y_start', -1, ... - 'W_fast', 0, ... - 'W_slow', 1, ... - 'X_fast', 1, ... - 'X_slow', 0, ... - 'Y_fast', 0, ... - 'Y_slow', 1, ... - 'Z_fast', 0, ... - 'Z_slow', 1, ... - 'rep_count', 1 ... - ); -plsdata.daq.inst.config.totalRecordSize = int64(0); % needed -plsdata.daq.inst.config.aimedBufferSize = int64(2^24); -plsdata.daq.inst.card.reset -plsdata.daq.inst.update_settings = py.True; - -%% -qc.plot_pulse(charge_scan_pulse, charge_scan_pulse_params) - -%% 4 CHANs -operations = {... - py.atsaverage.operations.Downsample('DS_A', 'A'),... - py.atsaverage.operations.Downsample('DS_B', 'B')... - ...py.atsaverage.operations.Downsample('DS_C', 'alazarC'),... - ...py.atsaverage.operations.Downsample('DS_D', 'alazarD'),... - }; - -plsdata.daq.inst.register_operations('general_charge_scan', py.list(operations)) - -%% 4 CHANs upload and arm -qc.arm_program('general_charge_scan', chargeScanPulseParams, ... - 'channel_mapping', struct('W', 'TABOR_A','X', 'TABOR_B','Y', 'TABOR_C', 'Z', 'TABOR_D', 'marker', 'TABOR_A_MARKER'), ... - 'window_mapping', struct('A', 'A', 'B', 'B'),... - 'update', true) - -%% Same as above if program already uploaded -qc.arm_program('general_charge_scan'); - -%% Start program -awgctrl('run'); - diff --git a/MATLAB/+qc/readme.txt b/MATLAB/+qc/readme.txt deleted file mode 100644 index 10dddd2d3..000000000 --- a/MATLAB/+qc/readme.txt +++ /dev/null @@ -1,8 +0,0 @@ -Test setup ------------------------------------------------------------------------------ -Use qctoolkitTestSetup - - -Naming convention ------------------------------------------------------------------------------ -Since qctoolkit uses underscores in variable names (instead of camel case) this package uses underscores. Some functions also use camel case for all variables which do not map directly to qctoolkit variables (otherwise it uses the qctoolkit variable and thus underscores). diff --git a/MATLAB/+qc/save_dict.m b/MATLAB/+qc/save_dict.m deleted file mode 100644 index 771c8751b..000000000 --- a/MATLAB/+qc/save_dict.m +++ /dev/null @@ -1,18 +0,0 @@ -function save_dict(dict_struct) - global plsdata - delim = '___'; - - if isstruct(dict_struct) - if ~isfield(dict_struct, 'global') - dict_struct.global = struct(); - end - dict_struct = qc.array2list(dict_struct); - text = py.json.dumps(dict_struct, pyargs('indent', int8(4), 'sort_keys', true)); - text = char(text); - fileId = fopen(fullfile(plsdata.dict.path, [dict_struct.(strcat('dict', delim, 'name')) '.json']), 'w'); - fprintf(fileId, '%s', text); - fclose(fileId); - else - error('Saving of dictionary failed since no struct was passed\n'); - end -end \ No newline at end of file diff --git a/MATLAB/+qc/save_pulse.m b/MATLAB/+qc/save_pulse.m deleted file mode 100644 index 4cafdd4ef..000000000 --- a/MATLAB/+qc/save_pulse.m +++ /dev/null @@ -1,30 +0,0 @@ -function [ file_written ] = save_pulse( pulse_template, overwrite ) - - global plsdata - - if nargin < 2 || isempty(overwrite) - overwrite = true; - end - - file_written = false; - - if py.operator.contains(plsdata.qc.pulse_storage, pulse_template.identifier) - if overwrite - py.operator.delitem(plsdata.qc.pulse_storage, pulse_template.identifier); - else - warning('Did not write file as it exists and overwrite == false'); - return; - end - end - - try - plsdata.qc.pulse_storage{pulse_template.identifier} = pulse_template; - file_written = true; -% fprintf('File(s) written\n'); - catch err - warning(err.getReport()); - end -end - - - diff --git a/MATLAB/+qc/set_alazar_buffer_strategy.m b/MATLAB/+qc/set_alazar_buffer_strategy.m deleted file mode 100644 index 921275e62..000000000 --- a/MATLAB/+qc/set_alazar_buffer_strategy.m +++ /dev/null @@ -1,58 +0,0 @@ -function set_alazar_buffer_strategy(strategy, varargin) - % SET_ALAZAR_BUFFER_STRATEGY sets the strategy to determine the buffer - % size the alazar card uses. - % strategy = - % {'force_buffer_size'|'avoid_single_buffer'|'one_buffer_per_window'|'none'} - % - % strategy = 'none' - % Uses default from python - % - % strategy = 'force_buffer_size' - % Requires the argument 'target_size' - % - % strategy = 'avoid_single_buffer' - % Optional argument 'target_size' - % - % strategy = 'one_buffer_per_window' - % Uses the gcd of all measurement window periods. Usefull for charge - % scans with lots of averaging - - import py.qupulse.hardware.dacs.alazar.AvoidSingleBufferAcquisition - import py.qupulse.hardware.dacs.alazar.OneBufferPerWindow - import py.qupulse.hardware.dacs.alazar.ForceBufferSize - - - global plsdata - - switch lower(strategy) - case 'none' - py_strategy = py.None; - - case 'force_buffer_size' - args = util.parse_varargin(varargin{:}); - if ~isfield(args, 'target_size') - error('qc:set_alazar_buffer_strategy:missing','The buffer strategy "force_buffer_size" requires "target_size".') - end - py_strategy = ForceBufferSize(struct2pyargs(args)); - - - case 'avoid_single_buffer' - default_args = struct('target_size', py.int(2^22)); - args = util.parse_varargin(default_args, varargin{:}); - py_strategy = AvoidSingleBufferAcquisition(ForceBufferSize(struct2pyargs(args))); - - case 'one_buffer_per_window' - py_strategy = py.qupulse.hardware.dacs.alazar.OneBufferPerWindow(); - - otherwise - error('qc:set_alazar_buffer_strategy:unknown', 'Unknown buffer strategy "%s"', strategy); - end - - plsdata.daq.inst.buffer_strategy = py_strategy; - fprintf('Set buffer strategy to %s.\n', char(py.repr(py_strategy))); -end - -function py_args = struct2pyargs(s) - c = [fieldnames(s), struct2cell(s)]'; - py_args = pyargs(c{:}); -end \ No newline at end of file diff --git a/MATLAB/+qc/set_pumping_at_awg.m b/MATLAB/+qc/set_pumping_at_awg.m deleted file mode 100644 index 5d8e7674b..000000000 --- a/MATLAB/+qc/set_pumping_at_awg.m +++ /dev/null @@ -1,108 +0,0 @@ -function set_pumping_at_awg(pumpingConfig, varargin) - -global plsdata - -defaultArgs = struct(... - 'programName', plsdata.awg.currentProgam, ... - 'turnOffAWG', true, ... - 'speedUp', false ... - ); -args = util.parse_varargin(varargin, defaultArgs); - -if ~args.speedUp - seqTable = qc.get_sequence_table(args.programName, false); -else - seqTable = qc.get_sequence_table(args.programName, false, {'AB', 'CD'}, false, true); -end -seqTableCheck = true; -report = ''; -pumpSubTab = seqTable{1}{end}; - - - - - -%---------------- some checks --------------------------------------------- -if ~args.speedUp - - %check if there are six entries for the three pumping types for each qubit - if length(pumpSubTab) < 6 - seqTableCheck = false; - report = ' -- There are not six waveforms at the end of the sequence table! They might be put together or not uploaded or not at the end of the sequence table. -- '; - end - - %test if every waveform is different/has a different - if seqTableCheck - for i = 0:5 - for j = 0:5 - if (i~=j) && (pumpSubTab{end-i}{2} == pumpSubTab{end-j}{2}) - report = ' -- Not all waveforms for pumping (that are assumed to be different) are different to each other! -- '; - seqTableCheck = false; - end - end - end - end - - %test if both channel pairs have the same pumping sequence table part - for i = 1:6 - if seqTableCheck && ~isequal(seqTable{1}{end}{end-i+1}, seqTable{2}{end}{end-i+1}) - report = ' -- Not the same pumping configuration on both channel pairs of the AWG! -- '; - seqTableCheck = false; - end - end - -end - - -%------------- reading out the pumping configuration ---------------------- - -if ~seqTableCheck - warning(report); -else - if ~args.speedUp - seqTable{1}{end}{end-5}{1} = pumpingConfig.n_s_AB; - seqTable{1}{end}{end-4}{1} = pumpingConfig.n_t_AB; - seqTable{1}{end}{end-3}{1} = pumpingConfig.n_cs_AB; - seqTable{1}{end}{end-2}{1} = pumpingConfig.n_s_CD; - seqTable{1}{end}{end-1}{1} = pumpingConfig.n_t_CD; - seqTable{1}{end}{end-0}{1} = pumpingConfig.n_cs_CD; - seqTable{2}{end}{end-5}{1} = pumpingConfig.n_s_AB; - seqTable{2}{end}{end-4}{1} = pumpingConfig.n_t_AB; - seqTable{2}{end}{end-3}{1} = pumpingConfig.n_cs_AB; - seqTable{2}{end}{end-2}{1} = pumpingConfig.n_s_CD; - seqTable{2}{end}{end-1}{1} = pumpingConfig.n_t_CD; - seqTable{2}{end}{end-0}{1} = pumpingConfig.n_cs_CD; - else - seqTable{1}{end} = py.list(seqTable{1}{end}); - seqTable{2}{end} = py.list(seqTable{1}{end}); - for i = 0:5 - seqTable{1}{end}{end-i} = py.list(seqTable{1}{end}{end-i}); - seqTable{2}{end}{end-i} = py.list(seqTable{2}{end}{end-i}); - end - seqTable{1}{end}{end-5}{1} = py.int(pumpingConfig.n_s_AB); - seqTable{1}{end}{end-4}{1} = py.int(pumpingConfig.n_t_AB); - seqTable{1}{end}{end-3}{1} = py.int(pumpingConfig.n_cs_AB); - seqTable{1}{end}{end-2}{1} = py.int(pumpingConfig.n_s_CD); - seqTable{1}{end}{end-1}{1} = py.int(pumpingConfig.n_t_CD); - seqTable{1}{end}{end-0}{1} = py.int(pumpingConfig.n_cs_CD); - seqTable{2}{end}{end-5}{1} = py.int(pumpingConfig.n_s_AB); - seqTable{2}{end}{end-4}{1} = py.int(pumpingConfig.n_t_AB); - seqTable{2}{end}{end-3}{1} = py.int(pumpingConfig.n_cs_AB); - seqTable{2}{end}{end-2}{1} = py.int(pumpingConfig.n_s_CD); - seqTable{2}{end}{end-1}{1} = py.int(pumpingConfig.n_t_CD); - seqTable{2}{end}{end-0}{1} = py.int(pumpingConfig.n_cs_CD); - end -end - -if args.speedUp - qc.set_sequence_table(args.programName, seqTable, false, {'AB', 'CD'}, false, true); -else - qc.set_sequence_table(args.programName, seqTable, false); -end -qc.change_armed_program(args.programName, args.turnOffAWG); - -if args.turnOffAWG - disp('turned AWG off'); -end - -end \ No newline at end of file diff --git a/MATLAB/+qc/set_sequence_table.m b/MATLAB/+qc/set_sequence_table.m deleted file mode 100644 index 2b064cc18..000000000 --- a/MATLAB/+qc/set_sequence_table.m +++ /dev/null @@ -1,96 +0,0 @@ -function set_sequence_table(program_name, seq_table, advanced_seq_table_flag, awg_channel_pair_identifiers, verbosity, input_python_list) -% SET_SEQUENCE_TABLE Manually override sequence table of program on Tabor AWG -% -% This only changes the sequence table in the associated Tabor channel -% pairs in qctoolkit. In order to actually update the sequence table on the -% AWG, you still need to run qc.change_armed_program(program_name, ...). -% -% --- Inputs -------------------------------------------------------------- -% program_name : Program name for which sequence table is set -% seq_table : Cell of sequence table to set on each -% Tabor channel pair. Empty elements will -% not be set. -% advanced_seq_table_flag : Set advanced sequence table if true. -% Default is false. -% awg_channel_pair_identifiers : Some substring in the channel pair -% identifiers to be matched. Sequence tables -% are sorted in the same order as channel -% pair identifiers substrings passed in this -% variable. Default is {'AB', 'CD'}. -% verbosity : Print sequence table to command line. -% Default is 0. -% ------------------------------------------------------------------------- -% (c) 2018/06 Pascal Cerfontaine (cerfontaine@physik.rwth-aachen.de) - - global plsdata - hws = plsdata.awg.hardwareSetup; - - - if nargin < 3 || isempty(advanced_seq_table_flag) - advanced_seq_table_flag = false; - end - if nargin < 4 || isempty(awg_channel_pair_identifiers) - awg_channel_pair_identifiers = {'AB', 'CD'}; - end - if nargin < 5 || isempty(verbosity) - verbosity = 0; - end - if nargin <6 || isempty(input_python_list) - input_python_list = false; - end - - if ~input_python_list - seq_table = int_typecast(seq_table); - end - - known_awgs = util.py.py2mat(hws.known_awgs); - sort_indices = cellfun(@(x)(find( cellfun(@(y)(~isempty(strfind(char(x.identifier), y))), awg_channel_pair_identifiers) )), known_awgs); - known_awgs = known_awgs(sort_indices); - - assert(numel(seq_table) == length(known_awgs), 'Sequence table needs to be a cell with an element for each of the %i channel pairs.', length(known_awgs)); - - for k = 1:numel(seq_table) - known_programs{k} = util.py.py2mat(py.getattr(known_awgs{k}, '_known_programs')); - - if isfield(known_programs{k}, program_name) && ~isempty(seq_table{k}) - if input_python_list - if advanced_seq_table_flag - known_awgs{k}.set_program_advanced_sequence_table(program_name, seq_table{k}); - % known_awgs{k}.set_program_advanced_sequence_table(program_name, seq_table{k}); - else - known_awgs{k}.set_program_sequence_table(program_name, seq_table{k}); % Since it has to be a list inside a list, but this list if list is only trivial if advanced seq table is trivial, otherwiese each entry can be called by advanced seq table - % known_awgs{k}.set_program_sequence_table(program_name,seq_table{k}); - end - else - if advanced_seq_table_flag - known_awgs{k}.set_program_advanced_sequence_table(program_name, py.list(seq_table{k})); - % known_awgs{k}.set_program_advanced_sequence_table(program_name, seq_table{k}); - else - known_awgs{k}.set_program_sequence_table(program_name, py.list(seq_table{k})); % Since it has to be a list inside a list, but this list if list is only trivial if advanced seq table is trivial, otherwiese each entry can be called by advanced seq table - % known_awgs{k}.set_program_sequence_table(program_name,seq_table{k}); - end - end - - end - end - - if verbosity > 0 - qc.get_sequence_table(program_name, advanced_seq_table_flag, awg_channel_pair_identifiers, verbosity); - end - -end - - -function out = int_typecast(in) - - if iscell(in) - out = {}; - for k = 1:numel(in) - out{k} = int_typecast(in{k}); - end - elseif ~isempty(in) - out = int64(in); - end - -end - \ No newline at end of file diff --git a/MATLAB/+qc/setup_alazar_measurements.m b/MATLAB/+qc/setup_alazar_measurements.m deleted file mode 100644 index 1642a72b9..000000000 --- a/MATLAB/+qc/setup_alazar_measurements.m +++ /dev/null @@ -1,140 +0,0 @@ -function [mask_prototypes, measurement_map, txt] = setup_alazar_measurements(varargin) % - % This function assumes the the first nQubits Alazar channels are hooked - % up to qubits, the rest are auxiliary channels. - % - % Overview over mapping of measurements - % ----------------------------------------------------------------------- - % measurement name defined in pulse - % >>> window mapping >>> - % measurement name defined in hardware setup (1st argument of set_measurement) - % >>> set_measurement >>> - % measurement mask (2nd argument of set_measurement, 1st argument of register_mask_for_channel) - % >>> register_mask_for_channel >>> - % alazar harware channel (2nd argument of register_mask_for_channel - % ----------------------------------------------------------------------- - % - % Example manual configuration - % ----------------------------------------------------------------------- - % import py.qctoolkit.hardware.setup.MeasurementMask - % hws = plsdata.awg.hardwareSetup; - % daq = plsdata.daq.inst; - % any name, give as 2nd arg in window_mapping alazar mask name - % hws.set_measurement('A', MeasurementMask(plsdata.daq.inst, 'A')); - % hws.set_measurement('B', MeasurementMask(plsdata.daq.inst, 'B')); - % hws.set_measurement('C', MeasurementMask(plsdata.daq.inst, 'C')); - % hws.set_measurement('D', MeasurementMask(plsdata.daq.inst, 'D')); - % hws.set_measurement('A_B', MeasurementMask(plsdata.daq.inst, 'A')); - % hws.set_measurement('A_B', MeasurementMask(plsdata.daq.inst, 'B')); - % - % alazar mask name, real alazar hardware channel - % daq.register_mask_for_channel('A', uint64(0)); - % daq.register_mask_for_channel('B', uint64(1)); - % daq.register_mask_for_channel('C', uint64(2)); - % daq.register_mask_for_channel('D', uint64(3)); - % ----------------------------------------------------------------------- - - global plsdata - hws = plsdata.awg.hardwareSetup; - daq = plsdata.daq.inst; - - defaultArgs = struct( ... - 'disp', true, ... - 'nMeasPerQubit', 2, ... - 'nQubits', 2 ... - ); - 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); - 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); - end - end - - for a = 1:(nAlazarChannels-nQubits) - for m = 1:nMeasPerQubit - % qubitIndex, measIndex, hwChannel, auxFlag1 - add_meas_and_mask(a, m, a-1, true); - end - end - - if args.nQubits > nAlazarChannels - warning('More than %i qubits not implemented at the moment since Alazar has only %i channels.', nAlazarChannels, nAlazarChannels); - end - - if args.nQubits > 2 - warning('Simultaneous measurements for more than 2 qubits not implemented at the moment.'); - end - if q == 2 - for m = 1:nMeasPerQubit - % Q1 Q2 qubitIndex, measIndex, hwChannel, auxFlag1, secondQubitIndex, secondHwChannel, auxFlag2 - add_meas_and_mask(1, m, 2, false, 2, 3 , false); - % A1 A2 qubitIndex, measIndex, hwChannel, auxFlag1, secondQubitIndex, secondHwChannel, auxFlag2 - add_meas_and_mask(1, m, 0, true, 2, 1 , true); - - % 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 - 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 - add_meas_and_mask(2, m, 3, false, 2, 1 , true); - end - end - - [mask_prototypes, measurement_map, txt] = qc.get_alazar_measurements('disp', args.disp); - -end - - -function add_meas_and_mask(qubitIndex, measIndex, hwChannel, auxFlag1, secondQubitIndex, secondHwChannel, auxFlag2) - global plsdata - - if nargin < 5 - secondQubitIndex = []; - end - - if nargin < 7 - auxFlag2 = false; - end - - if auxFlag1 - name = 'Aux'; - else - name = 'Qubit'; - end - - if auxFlag2 - name2 = 'Aux'; - else - name2 = 'Qubit'; - end - - if ~isempty(secondQubitIndex) - measName = sprintf('%s_%i_%s_%i_Meas_%i', name, qubitIndex, name2, secondQubitIndex, measIndex); - maskName = sprintf('%s_%i_%s_%i_Meas_%i_Mask_%i', name, qubitIndex, name2, secondQubitIndex, measIndex, 1); - maskName2 = sprintf('%s_%i_%s_%i_Meas_%i_Mask_%i', name, qubitIndex, name2, secondQubitIndex, measIndex, 2); - else - measName = sprintf('%s_%i_Meas_%i', name, qubitIndex, measIndex); - maskName = sprintf('%s_%i_Meas_%i_Mask_%i', name, qubitIndex, measIndex, 1); - end - - plsdata.awg.hardwareSetup.set_measurement(measName, py.qctoolkit.hardware.setup.MeasurementMask(plsdata.daq.inst, maskName), ~isempty(secondQubitIndex)); - plsdata.daq.inst.register_mask_for_channel(maskName, uint64(hwChannel)); - - if ~isempty(secondQubitIndex) - plsdata.awg.hardwareSetup.set_measurement(measName, py.qctoolkit.hardware.setup.MeasurementMask(plsdata.daq.inst, maskName2), true); - plsdata.daq.inst.register_mask_for_channel(maskName2, uint64(secondHwChannel)); - end -end - - diff --git a/MATLAB/+qc/setup_tabor_awg.m b/MATLAB/+qc/setup_tabor_awg.m deleted file mode 100644 index 7fb498f2c..000000000 --- a/MATLAB/+qc/setup_tabor_awg.m +++ /dev/null @@ -1,129 +0,0 @@ -function setup_tabor_awg(varargin) - - global smdata - global plsdata - - defaultArgs = struct( ... - 'realAWG', true, ... - '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 - 'simulateAWG', true, ... - 'smChannels', {{'RFA', 'RFB', 'RFC', 'RFD'}}, ... - 'taborName', 'TaborAWG2184C', ... - 'globalTransformation', [], ... - 'ip', '169.254.40.2', ... %IP's: Triton 200: 169.254.40.2 Triton 400: 169.254.40.55 - 'dcMode', false, ... - 'maxPulseWait', 60, ... % Maximum waiting time in s in qc.awg_program before arming DAQ again - 'taborDriverPath', 'C:\Users\lablocal\Documents\PYTHON\TaborDriver\' ... - ); - 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 = 192; - plsdata.awg.sampleQuantum = 16; - plsdata.awg.globalTransformation = args.globalTransformation; - - for k = 1:numel(args.smChannels) - smChannel = args.smChannels(k); - if ~(smdata.channels(smchanlookup(smChannel)).instchan(1) == sminstlookup(args.taborName)) - error('Channel %s does not belong to %s\n', smChannel, args.taborName); - end - smdata.channels(smchanlookup(smChannel)).rangeramp(end) = 1/args.sampleVoltPerAwgVolt(k); - end - - % Reload qctoolkit tabor AWG integration - qctoolkit_tabor = py.importlib.reload(py.importlib.import_module('qctoolkit.hardware.awgs.tabor')); - - % Start simulator - if args.simulateAWG - if py.pytabor.open_session('127.0.0.1') == py.None - dos([fullfile(args.taborDriverPath, 'WX2184C.exe') ' /switch-on /gui-in-tray&']) - - while py.pytabor.open_session('127.0.0.1') == py.None - pause(1); - disp('Waiting for Simulator to start...'); - end - disp('Simulator started'); - end - end - - if args.realAWG && ~args.simulateAWG - % Only real instrument - smdata.inst(sminstlookup(args.taborName)).data.tawg = qctoolkit_tabor.TaborAWGRepresentation(['TCPIP::' args.ip '::5025::SOCKET'], pyargs('reset', py.True)); - smdata.inst(sminstlookup(args.taborName)).data.tawg.paranoia_level = int64(2); - elseif args.realAWG && args.simulateAWG - % Simulator and real instrument - smdata.inst(sminstlookup(args.taborName)).data.tawg = qctoolkit_tabor.TaborAWGRepresentation(['TCPIP::' args.ip '::5025::SOCKET'], pyargs('reset', py.True, 'mirror_addresses', {'127.0.0.1'})); - elseif ~args.realAWG && args.simulateAWG - % Just simulator - smdata.inst(sminstlookup(args.taborName)).data.tawg = qctoolkit_tabor.TaborAWGRepresentation('TCPIP::127.0.0.1::5025::SOCKET', pyargs('reset', py.True)); - end - - plsdata.awg.inst = smdata.inst(sminstlookup(args.taborName)).data.tawg; - if args.realAWG && exist('awgctrl.m', 'file') - awgctrl('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 - - if args.realAWG || args.simulateAWG - % PlaybackChannels can take more than two values (analog channels) - plsdata.awg.hardwareSetup.set_channel('TABOR_A', ... - py.qctoolkit.hardware.setup.PlaybackChannel(plsdata.awg.inst.channel_pair_AB, int64(0), multiply{1})); - plsdata.awg.hardwareSetup.set_channel('TABOR_B', ... - py.qctoolkit.hardware.setup.PlaybackChannel(plsdata.awg.inst.channel_pair_AB, int64(1), multiply{2})); - plsdata.awg.hardwareSetup.set_channel('TABOR_C', ... - py.qctoolkit.hardware.setup.PlaybackChannel(plsdata.awg.inst.channel_pair_CD, int64(0), multiply{3})); - plsdata.awg.hardwareSetup.set_channel('TABOR_D', ... - py.qctoolkit.hardware.setup.PlaybackChannel(plsdata.awg.inst.channel_pair_CD, int64(1), multiply{4})); - - plsdata.awg.hardwareSetup.set_channel('TABOR_AB', ... - py.qctoolkit.hardware.setup.PlaybackChannel(plsdata.awg.inst.channel_pair_AB, int64(0), multiply{1}), py.True); - plsdata.awg.hardwareSetup.set_channel('TABOR_AB', ... - py.qctoolkit.hardware.setup.PlaybackChannel(plsdata.awg.inst.channel_pair_AB, int64(1), multiply{2}), py.True); - - - plsdata.awg.hardwareSetup.set_channel('TABOR_AC', ... - py.qctoolkit.hardware.setup.PlaybackChannel(plsdata.awg.inst.channel_pair_AB, int64(0), multiply{1}), py.True); - plsdata.awg.hardwareSetup.set_channel('TABOR_AC', ... - py.qctoolkit.hardware.setup.PlaybackChannel(plsdata.awg.inst.channel_pair_CD, int64(0), multiply{3}), py.True); - - plsdata.awg.hardwareSetup.set_channel('TABOR_AD', ... - py.qctoolkit.hardware.setup.PlaybackChannel(plsdata.awg.inst.channel_pair_AB, int64(0), multiply{1}), py.True); - plsdata.awg.hardwareSetup.set_channel('TABOR_AD', ... - py.qctoolkit.hardware.setup.PlaybackChannel(plsdata.awg.inst.channel_pair_CD, int64(1), multiply{4}), py.True); - - plsdata.awg.hardwareSetup.set_channel('TABOR_BC', ... - py.qctoolkit.hardware.setup.PlaybackChannel(plsdata.awg.inst.channel_pair_AB, int64(1), multiply{2}), py.True); - plsdata.awg.hardwareSetup.set_channel('TABOR_BC', ... - py.qctoolkit.hardware.setup.PlaybackChannel(plsdata.awg.inst.channel_pair_CD, int64(0), multiply{3}), py.True); - - plsdata.awg.hardwareSetup.set_channel('TABOR_BD', ... - py.qctoolkit.hardware.setup.PlaybackChannel(plsdata.awg.inst.channel_pair_AB, int64(1), multiply{2}), py.True); - plsdata.awg.hardwareSetup.set_channel('TABOR_BD', ... - py.qctoolkit.hardware.setup.PlaybackChannel(plsdata.awg.inst.channel_pair_CD, int64(1), multiply{4}), py.True); - - plsdata.awg.hardwareSetup.set_channel('TABOR_CD', ... - py.qctoolkit.hardware.setup.PlaybackChannel(plsdata.awg.inst.channel_pair_CD, int64(0), multiply{3}), py.True); - plsdata.awg.hardwareSetup.set_channel('TABOR_CD', ... - py.qctoolkit.hardware.setup.PlaybackChannel(plsdata.awg.inst.channel_pair_CD, int64(1), multiply{4}), py.True); - - - % MarkerChannel can only take on two values (digital channels) - plsdata.awg.hardwareSetup.set_channel('TABOR_A_MARKER', ... - py.qctoolkit.hardware.setup.MarkerChannel(plsdata.awg.inst.channel_pair_AB, int64(0))); - plsdata.awg.hardwareSetup.set_channel('TABOR_B_MARKER', ... - py.qctoolkit.hardware.setup.MarkerChannel(plsdata.awg.inst.channel_pair_AB, int64(1))); - plsdata.awg.hardwareSetup.set_channel('TABOR_C_MARKER', ... - py.qctoolkit.hardware.setup.MarkerChannel(plsdata.awg.inst.channel_pair_CD, int64(0))); - plsdata.awg.hardwareSetup.set_channel('TABOR_D_MARKER', ... - py.qctoolkit.hardware.setup.MarkerChannel(plsdata.awg.inst.channel_pair_CD, int64(1))); - end \ No newline at end of file diff --git a/MATLAB/+qc/strrep.m b/MATLAB/+qc/strrep.m deleted file mode 100644 index 318f3d6f9..000000000 --- a/MATLAB/+qc/strrep.m +++ /dev/null @@ -1,15 +0,0 @@ -function varargout = strrep(varargin) - % Multiple string replacements: - % varargin{k} is a cell which should look like - % {cell of original strings, search string, replacement string, search string, replacement string, ...} - % - % If n arguments are received, n outputs are returned. - - for k = 1:numel(varargin) - varargout{k} = varargin{k}{1}; - for l = 2:2:numel(varargin{k}) - varargout{k} = cellfun(@(x)(strrep(x, varargin{k}{l}, varargin{k}{l+1})), varargout{k}, 'UniformOutput', false); - end - end - -end \ No newline at end of file diff --git a/MATLAB/+qc/struct_to_pulse.m b/MATLAB/+qc/struct_to_pulse.m deleted file mode 100644 index 9214f766a..000000000 --- a/MATLAB/+qc/struct_to_pulse.m +++ /dev/null @@ -1,15 +0,0 @@ -function pulseTemplate = struct_to_pulse(pulseStruct) - - backend = py.qctoolkit.serialization.DictBackend(); - serializer = py.qctoolkit.serialization.Serializer(backend); - - if startsWith(pulseStruct.main, '{') - pulseName = 'main'; - else - pulseName = pulseStruct.main; - end - - backend.storage.update(pulseStruct) - - pulseTemplate = serializer.deserialize(pulseName); - % plsStruct = util.py.py2mat(backend.storage) \ No newline at end of file diff --git a/MATLAB/+qc/to_transformation.m b/MATLAB/+qc/to_transformation.m deleted file mode 100644 index 3f694146a..000000000 --- a/MATLAB/+qc/to_transformation.m +++ /dev/null @@ -1,18 +0,0 @@ -function transformation = to_transformation(mat_trafo) - - trafo_module = py.importlib.import_module('qctoolkit._program.transformation'); - - if istable(mat_trafo) - assert(size(mat_trafo, 1) == size(mat_trafo, 2)); - if isempty(mat_trafo.Properties.RowNames) - mat_trafo.Properties.RowNames = mat_trafo.Properties.VariableNames; - end - - data = util.py.mat2py(mat_trafo{:,:}); - - transformation = trafo_module.LinearTransformation(data, mat_trafo.Properties.RowNames', mat_trafo.Properties.VariableNames); - elseif isempty(mat_trafo) - transformation = py.None; - else - error('invalid trafo type'); - end \ No newline at end of file diff --git a/MATLAB/+qc/workaround_4chan_program_errors.m b/MATLAB/+qc/workaround_4chan_program_errors.m deleted file mode 100644 index d2907b2f3..000000000 --- a/MATLAB/+qc/workaround_4chan_program_errors.m +++ /dev/null @@ -1,51 +0,0 @@ -function workaround_4chan_program_errors(a) - % For some 4 channel programs, running another 2 channel program which - % has marker channels on both AWGs beforehand, leads to erroneous voltage - % outputs, even though the (advanced) sequence tables stored by qctoolkit - % do not change. This is true even if qctoolkit is forced to reupload - % the sequence tables of the 4 channel program. - % - % To repdroduce this error reset the AWG, then run - % EITHER - % 1) tune('resp'): correct output - % 2) tune('lead', 2): correct output - % 3) tune('resp'): erroneous output - if omitted, 5) gives erroneous output - % 4) tune('line', 1): correct output - % 5) tune('resp'): correct output - % OR - % 1) tune('lead', 2): correct output - % 2) tune('comp'): correct output - % 3) tune('resp'): erroneous output - % - % I (Pascal) found out this can be circumvented by arming the erroneous - % program and then arming the idle program manually. Next, the erroneous - % program can be run and now yields the correct result. - % - % This bug has been fixed now by adding the following lines in tabor.py - % self.device.send_cmd('SEQ:DEL:ALL') - % self._sequencer_tables = [] - % self.device.send_cmd('ASEQ:DEL') - % self._advanced_sequence_table = [] - - warning('No longer needed since bug has been fixed'); - - % global plsdata - % - % if ~strcmp(plsdata.awg.currentProgam, a.program_name) && (~isfield(a, 'arm_global_for_workaround_4chan_program_errors')) - % tic - % - % hws = plsdata.awg.hardwareSetup; - % known_awgs = util.py.py2mat(hws.known_awgs); - % - % for k = 1:numel(known_awgs) - % if any(cellfun(@(x)(strcmp(x, a.program_name)), fieldnames(util.py.py2mat(py.getattr(known_awgs{k}, '_known_programs'))))) - % known_awgs{k}.change_armed_program(a.program_name); - % end - % end - % - % for k = 1:numel(known_awgs) - % known_awgs{k}.change_armed_program(py.None); - % end - % - % fprintf('qc.workaround_4chan_program_errors executed...took %.0fs\n', toc); - % end \ No newline at end of file diff --git a/MATLAB/+qc/workaround_alazar_single_buffer_acquisition.m b/MATLAB/+qc/workaround_alazar_single_buffer_acquisition.m deleted file mode 100644 index 0f9aaa2bb..000000000 --- a/MATLAB/+qc/workaround_alazar_single_buffer_acquisition.m +++ /dev/null @@ -1,23 +0,0 @@ -function workaround_alazar_single_buffer_acquisition() - % the alazar acquisition might fail if there is just a single buffer - % the functionality to work around that was moved to the attribute - % 'buffer_strategy' of the qupulse driver object - - % Some workaround for a workaround - ask Simon - global plsdata - plsdata.daq.inst.config.totalRecordSize = int64(0); -% plsdata.daq.inst.config.aimedBufferSize = int64(2^24); - - -if true - fprintf('qc.workaround_alazar_single_buffer_acquisition decidete to try one buffer per measurement window\n'); - plsdata.daq.inst.buffer_strategy = py.qupulse.hardware.dacs.alazar.OneBufferPerWindow(); -else - % use default behaviour - plsdata.daq.inst.buffer_strategy = py.None; -end - - plsdata.daq.inst.card.reset - plsdata.daq.inst.update_settings = py.True; - - fprintf('qc.workaround_alazar_single_buffer_acquisition executed\n'); \ No newline at end of file diff --git a/MATLAB/+qctoolkit/arm_pulse.m b/MATLAB/+qctoolkit/arm_pulse.m deleted file mode 100644 index 8c7e94af5..000000000 --- a/MATLAB/+qctoolkit/arm_pulse.m +++ /dev/null @@ -1,60 +0,0 @@ -function arm_pulse(pulse_name, hardware_setup, parameters, pulse_location, varargin) - -default_args = struct('channel_mapping', py.None, ... - 'window_mapping', py.None,... - 'update', false,... - 'add_marker', {{}}); - -args = util.parse_varargin(varargin, default_args); -if ~iscell(args.add_marker) - args.add_marker = {args.add_marker}; -end - -if py.list(hardware_setup.registered_programs.keys()).count(pulse_name) == 0 || args.update - -%% LOAD PULSE - -backend = py.qctoolkit.serialization.FilesystemBackend(pulse_location); - -serializer = py.qctoolkit.serialization.Serializer(backend); - -pulse_template = serializer.deserialize(pulse_name); - -%% ADD MARKER -if ~isempty(args.add_marker) - - marker_pulse = py.qctoolkit.pulses.PointPT({{0, 1},... - {pulse_template.duration, 1}}, args.add_marker); - pulse_template = py.qctoolkit.pulses.AtomicMultiChannelPT(pulse_template, marker_pulse); - - for ii = 1:numel(args.add_marker) - args.channel_mapping.(args.add_marker{ii}) = args.add_marker{ii}; - end - -end - - -%% INSTANTIATE PULSE (plug in parameters) - -sequencer = py.qctoolkit.pulses.Sequencer(); - -kwargs = pyargs('parameters', parameters,... - 'channel_mapping', args.channel_mapping,... - 'window_mapping', args.window_mapping); - -sequencer.push(pulse_template, kwargs) - -instantiated_pulse = sequencer.build(); - -%% LOAD PROGRAM TO AWG -hardware_setup.register_program(pulse_name, instantiated_pulse, pyargs('update', args.update)); - -end - -hardware_setup.arm_program(pulse_name); - -%% debug -% alazar = util.py.py2mat(py.getattr(hardware_setup, '_measurement_map')).A{1}.dac -% prog = util.py.py2mat(py.getattr(alazar, '_registered_programs')).charge_scan -% - diff --git a/MATLAB/+qctoolkit/convert_qctoolkit.m b/MATLAB/+qctoolkit/convert_qctoolkit.m deleted file mode 100644 index 2af5787a5..000000000 --- a/MATLAB/+qctoolkit/convert_qctoolkit.m +++ /dev/null @@ -1,34 +0,0 @@ -function pulse_group = convert_qctoolkit(qct_output) -% pulse_group = convert_qctoolkit(qct_output) -% -% Registers pulses and converts pulse group data obtained from qupulse. -% -% qct_output: The output tuple of the -% PulseControlInterface.create_pulse_group() method. - -qct_pulses = qct_output{2}; - -% Convert Python dicts of pulses to pulse control waveform pulse structs -% and register them using plsreg. Remember index in pulse database. -pulse_indices = zeros(size(qct_pulses, 2)); -for i = 1:size(qct_pulses, 2) - pulse = struct(qct_pulses{i}); - pulse.name = arrayfun(@char, pulse.name); - pulse.data = struct(pulse.data); - pulse.data.marker = cell2mat(cell(pulse.data.marker)); - pulse.data.wf = cell2mat(cell(pulse.data.wf)); - pulse_indices(i) = plsreg(pulse); -end - -% Convert Python dict of pulse group to pulse control struct. -% Replace pulse indices in pulse_group.pulses with the indices of the -% pulses in the pulse database (plsdata). -pulse_group = struct(qct_output{1}); -pulse_group.chan = double(pulse_group.chan); -pulse_group.name = arrayfun(@char, pulse_group.name); -pulse_group.ctrl = arrayfun(@char, pulse_group.ctrl); -pulse_group.nrep = cellfun(@double, cell(pulse_group.nrep)); -pulse_group.pulses = cellfun(@double, cell(pulse_group.pulses)); -for i = 1:size(pulse_group.pulses, 2) - pulse_group.pulses(i) = pulse_indices(pulse_group.pulses(i) + 1); -end \ No newline at end of file diff --git a/MATLAB/+qctoolkit/example_scan_no_alazar.m b/MATLAB/+qctoolkit/example_scan_no_alazar.m deleted file mode 100644 index 695577a45..000000000 --- a/MATLAB/+qctoolkit/example_scan_no_alazar.m +++ /dev/null @@ -1,128 +0,0 @@ -% The alazar card in THIS example is not controled with qctoolkit and all -% measurement windows defined in the pulses are ignored -function scan = example_scan_no_alazar(hardware_setup, tawg) - -global smdata; - -%tawg.send_cmd(':INST:SEL 1'); -%tawg.send_cmd(':SOUR:MARK:SEL 1; :SOUR:MARK:VOLT:HIGH 1.2'); -%tawg.send_cmd(':SOUR:MARK:SEL 1; :SOUR:MARK:STAT OFF'); - - -%set_marker = @() tawg.send_cmd(':ENAB; :TRIG; :SOUR:MARK:SOUR WAVE; :SOUR:MARK:SEL 1; :SOUR:MARK:STAT ON'); -%reset_marker = @() tawg.send_cmd(':SOUR:MARK:SEL 1; :SOUR:MARK:STAT OFF'); - - -% use pulse from example files -qctoolkit_location = what('+qctoolkit'); -pulse_name = 'table_template'; -pulse_location = fullfile(qctoolkit_location.path,... - '..', '..', 'doc', 'source', 'examples', 'serialized_pulses'); - - -% create struct with parameters -parameters.va = 0; -parameters.vb = 0.5; -parameters.ta = 192; -parameters.tb = 4*19200 - 192; -parameters.tend = parameters.tb + 192; - -pulse_length = parameters.tend / tawg.sample_rate(uint64(1)); - -% we want to play the channel 'A' of the pulse on the channel 'TABOR_A' of -% the hardware_setup. -channel_mapping.A = 'TABOR_A'; - -% For a pulse with measurement windows we can rename them here -% window_mapping.meas_in_pulse = 'meas_name' - - -%% Configure data acquisition with alazar card -sm_alazar_channel = 'ATS1'; -sm_alazar_instrument = smchaninst(sm_alazar_channel); -sm_alazar_instrument = sm_alazar_instrument(1); - - -alazar_config = sm_setups.common.AlazarDefaultSettings(); - -trigger_range = 1; -trigger_level = 0.01; - -alazar_config.trigger_settings.source_1 = 'A'; -alazar_config.trigger_settings.level_1 = uint8(128 + 127* (trigger_level / trigger_range)); -alazar_config.trigger_settings.slope_1 = 'positive'; - -switch alazar_config.clock_settings.samplerate - case 'rate_100MSPS' - alazar_sample_rate = 100e6; - otherwise - error('invalid sample rate (changing the sample rate possibly breaks clock sync)'); -end - -alazar_downsampling = 1; - -masks = {}; -masks{1}.type = 'Periodic Mask'; -masks{1}.begin = 0; -masks{1}.end = alazar_downsampling; -masks{1}.period = alazar_downsampling; -masks{1}.channel = 'A'; - -alazar_config.total_record_size = pulse_length * alazar_sample_rate; -if abs(alazar_config.total_record_size - round(alazar_config.total_record_size)) > 1e-10 - error('total record size is no integer'); -end -alazar_config.total_record_size = int64(round(alazar_config.total_record_size)); -data_points_per_pulse = alazar_config.total_record_size / alazar_downsampling; - -operations = {}; -operations{1}.type = 'DS';% downsampling -operations{1}.mask = 1; - -alazar_config.masks = masks; -alazar_config.operations = operations; - - -scan.configfn(1).fn = @smaconfigwrap; -scan.configfn(1).args = {smdata.inst(sm_alazar_instrument).cntrlfn [sm_alazar_instrument 0 99] [] [] alazar_config}; % upload config ? - -scan.configfn(2).fn = @smaconfigwrap; -scan.configfn(2).args = {smdata.inst(sm_alazar_instrument).cntrlfn,[sm_alazar_instrument 0 5]}; % write/commit config - -% upload pulse to AWG -scan.configfn(3).fn = @smaconfigwrap; -scan.configfn(3).args = {@qctoolkit.arm_pulse,... - pulse_name, hardware_setup, parameters, pulse_location... - 'channel_mapping', channel_mapping,... - 'update', true}; - -scan.loops(1).setchan = []; -scan.loops(1).npoints = data_points_per_pulse; -scan.loops(1).rng = []; -scan.loops(1).ramptime = 0; % = sample rate * mask.period - -scan.loops(1).trigfn.fn = @(awg) awg.send_cmd(':TRIG'); -scan.loops(1).trigfn.args = {tawg}; - -scan.loops(2).setchan = []; -scan.loops(2).getchan = {sm_alazar_channel}; % read out buffer -scan.loops(2).npoints = 2; -scan.loops(2).ramptime = []; -scan.loops(2).rng = 1:2; - -scan.disp(1).loop = 2; -scan.disp(1).channel = 1; -scan.disp(1).dim = 1; -scan.disp(2).loop = 2; -scan.disp(2).channel = 1; -scan.disp(2).dim = 2; - - -% arm Alazar before each scan loop(1) -scan.loops(2).prefn(1).fn = @smaconfigwrap; -scan.loops(2).prefn(1).args = {smdata.inst(sm_alazar_instrument).cntrlfn,[sm_alazar_instrument 0 4]}; - -% arm AWG before each scan loop(1) (not really necessary) -scan.loops(2).prefn(2).fn = @smaconfigwrap; -scan.loops(2).prefn(2).args = {@qctoolkit.arm_pulse, pulse_name, hardware_setup}; - diff --git a/MATLAB/+qctoolkit/get_pulse_duration.m b/MATLAB/+qctoolkit/get_pulse_duration.m deleted file mode 100644 index 4568f4b7c..000000000 --- a/MATLAB/+qctoolkit/get_pulse_duration.m +++ /dev/null @@ -1,17 +0,0 @@ -function pulse_length = get_pulse_duration(pulse, parameters) - -parameter_kwargs = cell2namevalpairs(fieldnames(parameters), struct2cell(parameters)); -pulse_length = pulse.duration.evaluate_numeric(pyargs(parameter_kwargs{:}))*1e-9; - - - - -% delete.. -function cellarr = cell2namevalpairs(fieldnames, values) - cellarr={}; - for ii = 1:numel(fieldnames) - cellarr{end+1}=fieldnames{ii}; - cellarr{end+1}=values{ii}; - end - - \ No newline at end of file diff --git a/MATLAB/+qctoolkit/get_pulse_parameters.m b/MATLAB/+qctoolkit/get_pulse_parameters.m deleted file mode 100644 index 31bd17713..000000000 --- a/MATLAB/+qctoolkit/get_pulse_parameters.m +++ /dev/null @@ -1,6 +0,0 @@ -function pulse_parameters = get_pulse_parameters(varargin) - - pulse_template = qctoolkit.load_pulse(varargin{:}); - - pulse_parameters = util.py.py2mat(pulse_template.parameter_names); - \ No newline at end of file diff --git a/MATLAB/+qctoolkit/instantiate_pulse.m b/MATLAB/+qctoolkit/instantiate_pulse.m deleted file mode 100644 index 5d1c25240..000000000 --- a/MATLAB/+qctoolkit/instantiate_pulse.m +++ /dev/null @@ -1,18 +0,0 @@ -%% INSTANTIATE PULSE (plug in parameters) -function instantiated_pulse = instantiate_pulse(pulse_template, parameters, varargin) - -default_args = struct(... - 'channel_mapping', py.None,... - 'window_mapping', py.None); - -args = util.parse_varargin(varargin, default_args); - -sequencer = py.qctoolkit.pulses.Sequencer(); - -kwargs = pyargs('parameters', parameters,... - 'channel_mapping', args.channel_mapping,... - 'window_mapping', args.window_mapping); - -sequencer.push(pulse_template, kwargs) - -instantiated_pulse = sequencer.build(); \ No newline at end of file diff --git a/MATLAB/+qctoolkit/load_pulse.m b/MATLAB/+qctoolkit/load_pulse.m deleted file mode 100644 index 4beb2f7e4..000000000 --- a/MATLAB/+qctoolkit/load_pulse.m +++ /dev/null @@ -1,7 +0,0 @@ -function pulse_template = load_pulse(pulse_name, pulse_location) - -backend = py.qctoolkit.serialization.FilesystemBackend(pulse_location); - -serializer = py.qctoolkit.serialization.Serializer(backend); - -pulse_template = serializer.deserialize(pulse_name); \ No newline at end of file diff --git a/MATLAB/+qctoolkit/plot_pulse.m b/MATLAB/+qctoolkit/plot_pulse.m deleted file mode 100644 index 296aea689..000000000 --- a/MATLAB/+qctoolkit/plot_pulse.m +++ /dev/null @@ -1,30 +0,0 @@ -function plot_pulse(pulse, parameters, npoints) - %% -sequencer = py.qctoolkit.pulses.Sequencer(); - -kwargs = pyargs('parameters', parameters); - -sequencer.push(pulse, kwargs); - -instantiated_pulse = sequencer.build(); - -pulse_duration_in_s = qctoolkit.get_pulse_duration(pulse, parameters); -pulse_duration_in_ns = pulse_duration_in_s * 1e9; -%% -if nargin < 3 - npoints = 100; -end -%% -sample_rate = npoints / pulse_duration_in_ns; -%% -data = util.py.py2mat(py.qctoolkit.pulses.plotting.render(instantiated_pulse, pyargs('sample_rate', sample_rate))); - -t = data{1}; -figure; -hold on - -for chan_name=fieldnames(data{2})' - plot(t, data{2}.(chan_name{1})); -end - -legend(fieldnames(data{2})'); \ No newline at end of file diff --git a/MATLAB/+qctoolkit/plot_tabor_pulse.m b/MATLAB/+qctoolkit/plot_tabor_pulse.m deleted file mode 100644 index 20da1dd53..000000000 --- a/MATLAB/+qctoolkit/plot_tabor_pulse.m +++ /dev/null @@ -1,23 +0,0 @@ -function plot_current_pulse(awg) - -program = awg.read_complete_program(); - -wfs = util.py.py2mat(program.get_waveforms()); -reps = util.py.py2mat(program.get_repetitions()); -n_wfs = numel(wfs); - -f = figure; - - - -tabgroup = uitabgroup(mainfig, 'Position', [.05 .1 .9 .8]); - -for k = 1:n_wfs - tab(k)=uitab(tabgroup,'Title', sprintf('Wf_%i', k)); - - axes('parent',tab(k)) - - plot(wfs{k}); - - legend(sprintf('%i times', reps(k))); -end \ No newline at end of file diff --git a/MATLAB/+qctoolkit/qctoolkitTestSetup.m b/MATLAB/+qctoolkit/qctoolkitTestSetup.m deleted file mode 100644 index 1ba8ab65d..000000000 --- a/MATLAB/+qctoolkit/qctoolkitTestSetup.m +++ /dev/null @@ -1,86 +0,0 @@ -%% Setup -global plsdata -plsdata = struct( ... - 'path', 'Y:\Cerfontaine\Code\qc-tookit-pulses', ... - 'awg', struct('inst', [], 'hardwareSetup', []), ... - 'daq', struct('inst', []) ... - ); - - -%% Repetition 4 Channel charge pulse -% Wwritten by F. Wangelik and P. Cerfontaine, 23.02.2018 - - -% Define table template for each iteration/step/measurement -part_pulse = py.qctoolkit.pulses.TablePT( ... - struct( ... - 'W', py.list({ {'(t_wait+t_meas)/sample_rate', 'W_fast*(x_start + i_x*x_step) + W_slow*(y_start + i_y*y_step)'} }), ... - 'X', py.list({ {'(t_wait+t_meas)/sample_rate', 'X_fast*(x_start + i_x*x_step) + X_slow*(y_start + i_y*y_step)'} }), ... - 'Y', py.list({ {'(t_wait+t_meas)/sample_rate', 'Y_fast*(x_start + i_x*x_step) + Y_slow*(y_start + i_y*y_step)'} }), ... - 'Z', py.list({ {'(t_wait+t_meas)/sample_rate', 'Z_fast*(x_start + i_x*x_step) + Z_slow*(y_start + i_y*y_step)'} }), ... - 'marker', py.list({ {'(t_wait+t_meas)/sample_rate', 1} }) ... - ) ... - ); - -first_pulse = py.qctoolkit.pulses.TablePT( ... - pyargs( ... - 'entries', ... - struct( ... - 'W', py.list({ {'(t_wait+t_meas)/sample_rate', 'W_fast*(x_start + i_x*x_step) + W_slow*(y_start + i_y*y_step)'} }), ... - 'X', py.list({ {'(t_wait+t_meas)/sample_rate', 'X_fast*(x_start + i_x*x_step) + X_slow*(y_start + i_y*y_step)'} }), ... - 'Y', py.list({ {'(t_wait+t_meas)/sample_rate', 'Y_fast*(x_start + i_x*x_step) + Y_slow*(y_start + i_y*y_step)'} }), ... - 'Z', py.list({ {'(t_wait+t_meas)/sample_rate', 'Z_fast*(x_start + i_x*x_step) + Z_slow*(y_start + i_y*y_step)'} }), ... - 'marker', py.list({ {'(t_wait+t_meas)/sample_rate', 1} }) ... - ), ... - 'measurements', ... - py.list({ {'A', 't_wait/sample_rate', '(t_meas/sample_rate)*meas_time_multiplier'}, ... - {'B', 't_wait/sample_rate', '(t_meas/sample_rate)*meas_time_multiplier'} } ... - ) ... - ) ... - ); - -rep_pulse = py.qctoolkit.pulses.repetition_pulse_template.RepetitionPulseTemplate(part_pulse, 'meas_time_multiplier-1'); -meas_pulse = py.qctoolkit.pulses.SequencePT(first_pulse, rep_pulse); - -% Create loop templates for both iterations -x_loop = py.qctoolkit.pulses.loop_pulse_template.ForLoopPulseTemplate(meas_pulse, 'i_x', 'N_x'); -y_loop = py.qctoolkit.pulses.loop_pulse_template.ForLoopPulseTemplate(x_loop, 'i_y', 'N_y'); - -% Start parameter mapping via mapping template -general_charge_scan = py.qctoolkit.pulses.MappingPT( ... - pyargs( ... - 'template', y_loop, ... - 'identifier', 'charge_scan', ... - 'parameter_mapping', struct('x_step', '(x_stop-x_start)/N_x', ... - 'y_step', '(y_stop-y_start)/N_y'), ... - 'allow_partial_parameter_mapping', true ... - ) ... - ); - -general_charge_scan = py.qctoolkit.pulses.repetition_pulse_template.RepetitionPulseTemplate( ... - pyargs( ... - 'body', general_charge_scan, ... - 'repetition_count', 'rep_count', ... - 'identifier', 'general_charge_scan' ... - ) ... - ); - -%% -qctoolkit.plot_pulse(general_charge_scan, struct('x_start', -1, 'x_stop', 1, 'N_x', 10, 't_meas', 1, ... - 'W_fast', 1, 'W_slow', 0, ... - 'X_fast', 1, 'X_slow', 0, ... - 'Y_fast', 0, 'Y_slow', 1, ... - 'Z_fast', 0, 'Z_slow', 1, ... - 'y_start', -1, 'y_stop', 1, 'N_y', 10, 't_wait', 0, 'sample_rate', 2.3, 'meas_time_multiplier', 2, ... - 'rep_count', 2), 100) - - -% from qctoolkit.serialization import FilesystemBackend, Serializer - -%% -backend = py.qctoolkit.serialization.FilesystemBackend(plsdata.path); -serializer = py.qctoolkit.serialization.Serializer(backend); - - -serializer.serialize(pyargs('serializable', general_charge_scan, 'overwrite', true)) - From fec7c4c28fd3b9eb3492929d6f11aae4942707c9 Mon Sep 17 00:00:00 2001 From: Simon Humpohl Date: Thu, 23 Jan 2025 14:30:08 +0100 Subject: [PATCH 06/10] Change newspiece and add short section to README --- README.md | 5 ++++- changes.d/841.removal | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6268c2eda..c90e4c00e 100644 --- a/README.md +++ b/README.md @@ -23,10 +23,13 @@ The current feature list is as follows: - Hardware model representation - High-level pulse to hardware configuration and waveform translation routines - Hardware drivers for Tabor Electronics, Tektronix and Zurich Instruments AWGs and AlazarTech Digitizers -- MATLAB interface to access qupulse functionality Pending changes are tracked in the `changes.d` subdirectory and published in [`RELEASE_NOTES.rst`](RELEASE_NOTES.rst) on release using the tool `towncrier`. +### Removed features + +The previous name of this package was qctoolkit. It was renamed in 2017 to highlight the pulse focus. The backward compatible alias was removed after the 0.9 release. Furthermore, this repository had a MATLAB interface for a longer time which was removed at the same time. + ## Installation qupulse is available on [PyPi](https://pypi.org/project/qupulse/) and the latest release can be installed by executing: ```sh diff --git a/changes.d/841.removal b/changes.d/841.removal index 9ae7d7acf..fe39edbe9 100644 --- a/changes.d/841.removal +++ b/changes.d/841.removal @@ -1 +1 @@ -Remove qctoolkit alias for qupulse. \ No newline at end of file +Remove MATLAB code and the qctoolkit alias for qupulse. From aac77c25242f7fec99494786e111de6e00f95a39 Mon Sep 17 00:00:00 2001 From: Simon Humpohl Date: Thu, 23 Jan 2025 14:34:56 +0100 Subject: [PATCH 07/10] Remove MATLAB mentions --- .gitignore | 1 - README.md | 2 +- coverage.ini | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 8ebe24db7..d7536efc2 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,6 @@ dist/* doc/source/examples/.ipynb_checkpoints/* **.asv *.orig -MATLAB/+qc/personalPaths.mat /doc/source/_autosummary/* .idea/ .mypy_cache/* diff --git a/README.md b/README.md index c90e4c00e..f6ba1fd65 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ The data acquisition backend for AlazarTech cards needs a package that unfortuna You can find documentation on how to use this library on [readthedocs](https://qupulse.readthedocs.io/en/latest/) and [IPython notebooks with examples in this repo](doc/source/examples). You can build it locally with `hatch run docs:html`. ### Folder Structure -The repository primarily consists of the folders `qupulse` (toolkit core code) and `tests` (toolkit core tests). Additional parts of the project reside in `MATLAB` (MATLAB interface) and `doc` (configuration and source files to build documentation) +The repository primarily consists of the folders `qupulse` (source code), `tests` and `doc`. `qupulse` contains the entire Python source code of the project and is further partitioned the following packages of related modules diff --git a/coverage.ini b/coverage.ini index f92469606..aeac2c624 100644 --- a/coverage.ini +++ b/coverage.ini @@ -3,4 +3,3 @@ branch = True omit = *main.py *__init__.py - */qcmatlab/manager.py From 8f175b17dd9754ee8d84e2bde06961e8531024ab Mon Sep 17 00:00:00 2001 From: Simon Humpohl Date: Thu, 23 Jan 2025 14:35:09 +0100 Subject: [PATCH 08/10] Fox outdated README facts --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f6ba1fd65..eef617c4c 100644 --- a/README.md +++ b/README.md @@ -57,12 +57,13 @@ You can find documentation on how to use this library on [readthedocs](https://q ### Folder Structure The repository primarily consists of the folders `qupulse` (source code), `tests` and `doc`. -`qupulse` contains the entire Python source code of the project and is further partitioned the following packages of related modules +`qupulse` contains the entire Python source code of the project and is further partitioned the following packages of related packages - `pulses` which contains all modules related to pulse representation. - `hardware` containing classes for hardware representation as well as hardware drivers - `utils` containing miscellaneous utility modules or wrapping code for external libraries -- `_program` contains general and hardware specific representations of instantiated (parameter free) pulses. It is private because there is no stability guarantee. +- `program` contains general and hardware specific representations of instantiated (parameter free) pulses. +- `expression` contains the expression interface used by qupulse. Contents of `tests` mirror the structure of `qupulse`. For every `` somewhere in `qupulse` there should exist a `Tests.py` in the corresponding subdirectory of `tests`. From 885aac108ec687b494a1c69f8b37a1574ded38a4 Mon Sep 17 00:00:00 2001 From: Simon Humpohl Date: Thu, 23 Jan 2025 15:00:10 +0100 Subject: [PATCH 09/10] Implement automatic qctoolkit renaming with legacy deserialization Reverts "Remove qctoolkit serialization tests" because they still test fallback code This reverts commit 62a1be490fdb678acc37f85e998a8caa5e9c6492. --- qupulse/serialization.py | 10 +++++++++- tests/serialization_tests.py | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/qupulse/serialization.py b/qupulse/serialization.py index 3cadee40f..ed0619d73 100644 --- a/qupulse/serialization.py +++ b/qupulse/serialization.py @@ -770,7 +770,15 @@ def deserialize(self, representation: Union[str, Dict[str, Any]]) -> Serializabl else: repr_ = dict(representation) - module_name, class_name = repr_['type'].rsplit('.', 1) + package_name, *module_path, class_name = repr_['type'].split('.') + + # the qctoolkit alias was removed. We hack in the new name here directly + if package_name == 'qctoolkit': + package_name = 'qupulse' + module_path.insert(0, package_name) + + module_name = '.'.join(module_path) + module = __import__(module_name, fromlist=[class_name]) class_ = getattr(module, class_name) diff --git a/tests/serialization_tests.py b/tests/serialization_tests.py index 0a5d62181..1e635c497 100644 --- a/tests/serialization_tests.py +++ b/tests/serialization_tests.py @@ -726,6 +726,20 @@ def test_auto_import(self): finder['qupulse.pulses.table_pulse_template.TablePulseTemplate'] import_module.assert_not_called() + def test_qctoolkit_import(self): + def my_callable(): + pass + + finder = DeserializationCallbackFinder() + + finder['qupulse.asd'] = my_callable + self.assertIs(finder['qctoolkit.asd'], my_callable) + + finder.qctoolkit_alias = False + with self.assertRaises(KeyError): + # This was a ModuleNotFoundError before the qctoolkit alias package was removed + finder['qctoolkit.asd'] + class SerializableMetaTests(unittest.TestCase): def test_native_deserializable(self): From 694dbbd64f047f57e4f2f40f2f3a598ca866f821 Mon Sep 17 00:00:00 2001 From: Simon Humpohl Date: Thu, 23 Jan 2025 15:30:15 +0100 Subject: [PATCH 10/10] Snarky comment --- tests/serialization_tests.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/serialization_tests.py b/tests/serialization_tests.py index 1e635c497..a49ae7e6c 100644 --- a/tests/serialization_tests.py +++ b/tests/serialization_tests.py @@ -736,8 +736,7 @@ def my_callable(): self.assertIs(finder['qctoolkit.asd'], my_callable) finder.qctoolkit_alias = False - with self.assertRaises(KeyError): - # This was a ModuleNotFoundError before the qctoolkit alias package was removed + with self.assertRaises(ModuleNotFoundError): finder['qctoolkit.asd']