From b005b483d69ac0e27022c1aac8f65b4eeb190128 Mon Sep 17 00:00:00 2001 From: Sander de Snoo <59472150+sldesnoo-Delft@users.noreply.github.com> Date: Mon, 10 Oct 2022 11:36:24 +0200 Subject: [PATCH] Cleanup and minor performance improvements in looping args --- pulse_lib/segments/conditional_segment.py | 4 +- pulse_lib/segments/segment_container.py | 13 +- .../utility/data_handling_functions.py | 176 ++++++++++-------- pulse_lib/segments/utility/looping.py | 2 +- 4 files changed, 105 insertions(+), 90 deletions(-) diff --git a/pulse_lib/segments/conditional_segment.py b/pulse_lib/segments/conditional_segment.py index 0d988a20..cb991a73 100644 --- a/pulse_lib/segments/conditional_segment.py +++ b/pulse_lib/segments/conditional_segment.py @@ -150,9 +150,9 @@ class conditional_segment: for ch in branch.channels: branch[ch].reset_time(loop_obj, False) - def extend_dim(self, shape=None, ref = False): + def extend_dim(self, shape=None): for branch in self.branches: - branch.extend_dim(shape, ref) + branch.extend_dim(shape) def enter_rendering_mode(self): self.reset_time(False) diff --git a/pulse_lib/segments/segment_container.py b/pulse_lib/segments/segment_container.py index 250b4dc7..40eee22a 100644 --- a/pulse_lib/segments/segment_container.py +++ b/pulse_lib/segments/segment_container.py @@ -332,7 +332,7 @@ class segment_container(): Args: extend_only (bool) : will just extend the time in the segment and not reset it if set to true [do not use when composing wavoforms...]. - Allings all segments togeter and sets the input time to 0, + Alligns all segments togeter and sets the input time to 0, e.g. , chan1 : waveform until 70 ns chan2 : waveform until 140ns @@ -370,12 +370,11 @@ class segment_container(): ''' return getattr(self, channel).get_segment(index, sample_rate, ref_channel_states) - def extend_dim(self, shape=None, ref = False): + def extend_dim(self, shape=None): ''' extend the dimensions of the waveform to a given shape. Args: shape (tuple) : shape of the new waveform - ref (bool) : put to True if you want to extend the dimension by using pointers instead of making full copies. If referencing is True, a pre-render will already be performed to make sure nothing is rendered double. ''' if shape is None: @@ -384,15 +383,15 @@ class segment_container(): for i in self.channels: if self.render_mode == False: - getattr(self, i).data = update_dimension(getattr(self, i).data, shape, ref) + getattr(self, i).data = update_dimension(getattr(self, i).data, shape) if getattr(self, i).type == 'render' and self.render_mode == True: - getattr(self, i)._pulse_data_all = update_dimension(getattr(self, i)._pulse_data_all, shape, ref) + getattr(self, i)._pulse_data_all = update_dimension(getattr(self, i)._pulse_data_all, shape) if self.render_mode == False: - self._software_markers.data = update_dimension(self._software_markers.data, shape, ref) + self._software_markers.data = update_dimension(self._software_markers.data, shape) else: - self._software_markers._pulse_data_all = update_dimension(self._software_markers.pulse_data_all, shape, ref) + self._software_markers._pulse_data_all = update_dimension(self._software_markers.pulse_data_all, shape) def wait(self, time, channels=None, reset_time=False): ''' diff --git a/pulse_lib/segments/utility/data_handling_functions.py b/pulse_lib/segments/utility/data_handling_functions.py index 145bf56f..773bb867 100644 --- a/pulse_lib/segments/utility/data_handling_functions.py +++ b/pulse_lib/segments/utility/data_handling_functions.py @@ -44,13 +44,12 @@ def find_common_dimension(dim_1, dim_2): return tuple(dim_comb[::-1]) -def update_dimension(data, new_dimension_info, use_ref = False): +def update_dimension(data, new_dimension_info): ''' update dimension of the data object to the one specified in new dimension_info Args: data (np.ndarray[dtype = object]) : numpy object that contains all the segment data of every iteration. new_dimension_info (list/np.ndarray) : list of the new dimensions of the array - use_ref (bool) : use pointer to copy, or take full copy (False is full copy) Returns: data (np.ndarray[dtype = object]) : same as input data, but with new_dimension_info. ''' @@ -59,30 +58,29 @@ def update_dimension(data, new_dimension_info, use_ref = False): for i in range(len(new_dimension_info)): if data.ndim < i+1: - data = _add_dimensions(data, new_dimension_info[-i-1:], use_ref) + data = _add_dimensions(data, new_dimension_info[-i-1:]) elif list(data.shape)[-i -1] != new_dimension_info[-i -1]: shape = list(data.shape) shape[-i-1] = new_dimension_info[-i-1] - data = _extend_dimensions(data, shape, use_ref) + data = _extend_dimensions(data, shape, -i-1) return data -def _add_dimensions(data, shape, use_ref): +def _add_dimensions(data, shape): """ Function that can be used to add and extra dimension of an array object. A seperate function is needed since we want to make a copy and not a reference. Note that only one dimension can be extended! Args: data (np.ndarray[dtype = object]) : numpy object that contains all the segment data of every iteration. shape (list/np.ndarray) : list of the new dimensions of the array - use_ref (bool) : use pointer to copy, or take full copy (False is full copy) """ new_data = data_container(shape = shape) for i in range(shape[0]): - new_data[i] = cpy_numpy_shallow(data, use_ref) + new_data[i] = cpy_numpy_shallow(data) return new_data -def _extend_dimensions(data, shape, use_ref): +def _extend_dimensions(data, shape, new_axis): ''' Extends the dimensions of a existing array object. This is useful if one would have first defined sweep axis 2 without defining axis 1. In this case axis 1 is implicitly made, only harbouring 1 element. @@ -91,67 +89,46 @@ def _extend_dimensions(data, shape, use_ref): Args: data (np.ndarray[dtype = object]) : numpy object that contains all the segment data of every iteration. shape (list/np.ndarray) : list of the new dimensions of the array (should have the same lenght as the dimension of the data!) - use_ref (bool) : use pointer to copy, or take full copy (False is full copy) + axis (int): the axis added in shape ''' - new_data = data_container(shape = shape) - for i in range(len(shape)): - if data.shape[i] != shape[i]: - if i == 0: - for j in range(len(new_data)): - new_data[j] = cpy_numpy_shallow(data, use_ref) - else: - new_data = new_data.swapaxes(i, 0) - data = data.swapaxes(i, 0) + new_data = data_container(shape=shape) - for j in range(len(new_data)): - new_data[j] = cpy_numpy_shallow(data, use_ref) + if new_axis == 0: + for j in range(len(new_data)): + new_data[j] = cpy_numpy_shallow(data) + else: + new_data = new_data.swapaxes(new_axis, 0) + data = data.swapaxes(new_axis, 0) - new_data = new_data.swapaxes(i, 0) + for j in range(len(new_data)): + new_data[j] = cpy_numpy_shallow(data) + new_data = new_data.swapaxes(new_axis, 0) return new_data -def cpy_numpy_shallow(data, use_ref): +def cpy_numpy_shallow(data): ''' Makes a shallow copy of an numpy object array. Args: data : data element - use_ref (bool) : use reference to copy ''' + if type(data) != data_container: + return copy(data) - if use_ref == True: - if type(data) != data_container: - return data - - if data.shape == (1,): - return data[0] + if data.shape == (1,): + return data[0].__copy__() - shape = data.shape - data_flat = data.flatten() - new_arr = np.empty(data_flat.shape, dtype=object) + shape = data.shape + data_flat = data.flatten() + new_arr = np.empty(data_flat.shape, dtype=object) - for i in range(len(new_arr)): - new_arr[i] = data_flat[i] - - new_arr = new_arr.reshape(shape) - - else: - if type(data) != data_container: - return copy(data) + for i in range(len(new_arr)): + new_arr[i] = copy.copy(data_flat[i]) - if data.shape == (1,): - return data[0].__copy__() - - shape = data.shape - data_flat = data.flatten() - new_arr = np.empty(data_flat.shape, dtype=object) - - for i in range(len(new_arr)): - new_arr[i] = copy.copy(data_flat[i]) - - new_arr = new_arr.reshape(shape) + new_arr = new_arr.reshape(shape) return new_arr @@ -166,7 +143,7 @@ def _get_loop_info(lp, index): setpnt.append(setpnt_single) info = { - 'nth_arg': index, + 'arg_index': index, 'shape' : lp.shape, 'len': len(lp), 'axis': lp.axis, @@ -193,17 +170,21 @@ def loop_controller(func): loop_info_args = [] loop_info_kwargs = [] - for i in range(1,len(args)): - if isinstance(args[i], loop_obj): - loop_info_args.append(_get_loop_info(args[i], i)) + for i,arg in enumerate(args): + if isinstance(arg, loop_obj): + loop_info_args.append(_get_loop_info(arg, i)) - for key in kwargs.keys(): - if isinstance(kwargs[key], loop_obj): - loop_info_kwargs.append(_get_loop_info(kwargs[key], key)) + for key,kwarg in kwargs.items(): + if isinstance(kwarg, loop_obj): + loop_info_kwargs.append(_get_loop_info(kwarg, key)) + orig_data = obj.data for lp in loop_info_args: for i in range(len(lp['axis'])-1,-1,-1): - new_dim, axis = get_new_dim_loop(obj.data.shape, lp['axis'][i], lp['shape'][i]) + data_shape = obj.data.shape + lp_axis = lp['axis'][i] + lp_length = lp['shape'][i] + new_dim, axis = get_new_dim_loop(data_shape, lp_axis, lp_length) lp['axis'][i] = axis obj.data = update_dimension(obj.data, new_dim) @@ -211,7 +192,6 @@ def loop_controller(func): lp['setpnt'][i].axis = axis obj._setpoints += lp['setpnt'][i] - # todo update : (not used atm, but just to be generaric.) for lp in loop_info_kwargs: for i in range(len(lp['axis'])-1,-1,-1): new_dim, axis = get_new_dim_loop(obj.data.shape, lp['axis'][i], lp['shape'][i]) @@ -221,11 +201,15 @@ def loop_controller(func): if lp['setpnt'] is not None: lp['setpnt'][i].axis = axis obj._setpoints += lp['setpnt'][i] -# new_dim = get_new_dim_loop(obj.data.shape, lp) -# obj.data = update_dimension(obj.data, new_dim) - loop_over_data(func, obj.data, args, loop_info_args, kwargs, loop_info_kwargs) + if orig_data is not obj.data: + print(f'data {obj.name} change {orig_data.shape} -> {obj.data.shape}') + obj_data = obj.data + if len(loop_info_args) > 0 or len(loop_info_kwargs) > 0: + loop_over_data_lp(func, obj_data, args, loop_info_args, kwargs, loop_info_kwargs) + else: + loop_over_data(func, obj_data, args, kwargs) return wrapper @@ -239,7 +223,7 @@ def loop_controller_post_processing(func): * extend data format to the right shape (simple python list used). * loop over the data and add called function - loop controller that works on the pulse_data_all object. This acts just before rendering. When rendering is done, all the actions of this looper are done. + loop controller that works on the *pulse_data_all* object. This acts just before rendering. When rendering is done, all the actions of this looper are done. ''' @wraps(func) def wrapper(*args, **kwargs): @@ -270,16 +254,19 @@ def loop_controller_post_processing(func): for lp in loop_info_kwargs: new_dim = get_new_dim_loop(obj.pulse_data_all.shape, lp) obj.pulse_data_all = update_dimension(obj.pulse_data_all, new_dim) - loop_over_data(func, obj.pulse_data_all, args, loop_info_args, kwargs, loop_info_kwargs) + obj_data = obj.pulse_data_all + if len(loop_info_args) > 0 or len(loop_info_kwargs) > 0: + loop_over_data_lp(func, obj_data, args, loop_info_args, kwargs, loop_info_kwargs) + else: + loop_over_data(func, obj_data, args, kwargs) return wrapper - -def loop_over_data(func, data, args, args_info, kwargs, kwargs_info): +def loop_over_data_lp(func, data, args, args_info, kwargs, kwargs_info): ''' - recursive function to apply the + Recursive function to apply the func to data with looping args Args: func : function to execute @@ -288,34 +275,63 @@ def loop_over_data(func, data, args, args_info, kwargs, kwargs_info): args_info : argument info is provided (e.g. axis updates) kwargs : kwargs provided kwarfs_info : same as args_info - loop_dimension - - - Returns: - None ''' - shape = list(data.shape) + shape = data.shape n_dim = len(shape) # copy the input --> we will fill in the arrays - args_cpy = list(copy.copy(args)) - kwargs_cpy = copy.copy(kwargs) + # only copy when there are loops + if len(args_info) > 0: + # copy to new list + args_cpy = list(args) + else: + args_cpy = args + if len(kwargs_info) > 0: + kwargs_cpy = kwargs.copy() + else: + kwargs_cpy = kwargs for i in range(shape[0]): for arg in args_info: if n_dim-1 in arg['axis']: - args_cpy[arg['nth_arg']] = args[arg['nth_arg']][i] + index = arg['arg_index'] + args_cpy[index] = args[index][i] for kwarg in kwargs_info: if n_dim-1 in kwarg['axis']: - kwargs_cpy[kwarg['nth_arg']] = kwargs[kwarg['nth_arg']][i] + index = kwarg['arg_index'] + kwargs_cpy[index] = kwargs[index][i] if n_dim == 1: # we are at the lowest level of the loop. - args_cpy[0].data_tmp = data[i] + args[0].data_tmp = data[i] data[i] = func(*args_cpy, **kwargs_cpy) else: # clean up args, kwargs - loop_over_data(func, data[i], args_cpy, args_info, kwargs_cpy, kwargs_info) + loop_over_data_lp(func, data[i], args_cpy, args_info, kwargs_cpy, kwargs_info) + + +def loop_over_data(func, data, args, kwargs): + ''' + Recursive function to apply func to data + + Args: + func : function to execute + data : data of the segment + args: arugments that are provided + kwargs : kwargs provided + ''' + shape = data.shape + n_dim = len(shape) + + for i in range(shape[0]): + + if n_dim == 1: + # we are at the lowest level of the loop. + args[0].data_tmp = data[i] + data[i] = func(*args, **kwargs) + else: + loop_over_data(func, data[i], args, kwargs) + def get_new_dim_loop(current_dim, axis, shape): ''' diff --git a/pulse_lib/segments/utility/looping.py b/pulse_lib/segments/utility/looping.py index 5e18bf57..34fb1c1d 100644 --- a/pulse_lib/segments/utility/looping.py +++ b/pulse_lib/segments/utility/looping.py @@ -62,7 +62,7 @@ class loop_obj(): if len(data.shape) == 1: self.setvals = (self.data, ) else: - raise ValueError ('Multidimensional setpoints cannot be inferred from input.') + raise ValueError ('Multidimensional setpoints cannot be inferred from input.') else: self.setvals = tuple() if isinstance(setvals,list) or isinstance(setvals, np.ndarray): -- GitLab