def rebuild_symmetries( self, values, axis_name, axis_index, is_oneperiod=False, is_antiperiod=False, is_smallestperiod=False, ): """Reconstructs the field of a Data object taking symmetries into account Parameters ---------- self: Data a Data object values: ndarray ndarray of a field axes_list: list a list of RequestedAxis objects Returns ------- ndarray of the reconstructed field """ # Rebuild symmetries if is_smallestperiod: return values elif is_antiperiod: if axis_name in self.symmetries.keys(): if "antiperiod" in self.symmetries.get(axis_name): return values else: raise AxisError("ERROR: axis has no antiperiodicity") else: raise AxisError("ERROR: axis has no antiperiodicity") elif is_oneperiod: if axis_name in self.symmetries: if "antiperiod" in self.symmetries.get(axis_name): nper = self.symmetries.get(axis_name)["antiperiod"] self.symmetries.get(axis_name)["antiperiod"] = 2 values = rebuild_symmetries_fct(values, axis_index, self.symmetries.get(axis_name)) self.symmetries.get(axis_name)["antiperiod"] = nper return values else: return values else: return values else: if axis_name in self.symmetries: values = rebuild_symmetries_fct(values, axis_index, self.symmetries.get(axis_name)) return values else: return values
def comp_axes(self, axes_list): """Completes the RequestedAxis objects in axes_list. Parameters ---------- self: Data a Data object axes_list: list a list of RequestedAxis objects Returns ------- list of RequestedAxis objects """ # Check if the requested axis is defined in the Data object for axis_requested in axes_list: axis_name = axis_requested.name for index, axis in enumerate(self.axes): if axis.name == axis_name: axis_requested.index = index axis_requested.corr_name = axis_name if axis_requested.index is None: # Check if requested axis is in correspondance dicts if axis_name in axes_dict.keys(): for index, axis in enumerate(self.axes): if axis.name == axes_dict[axis_name][0]: axis_requested.corr_name = axes_dict[axis_name][0] axis_requested.operation = axes_dict[axis_name][ 0] + "_to_" + axis_name axis_requested.transform = axes_dict[axis_name][1] axis_requested.index = index if axis_requested.index is None: raise AxisError("ERROR: Requested axis [" + axis_name + "] is not available") elif axis_name in rev_axes_dict.keys(): for index, axis in enumerate(self.axes): if axis.name == rev_axes_dict[axis_name][0]: axis_requested.corr_name = rev_axes_dict[axis_name][0] axis_requested.operation = rev_axes_dict[axis_name][ 0] + "_to_" + axis_name axis_requested.transform = rev_axes_dict[axis_name][1] axis_requested.index = index if axis_requested.index is None: raise AxisError("ERROR: Requested axis [" + axis_name + "] is not available") else: # Axis does not exist and is ignored axes_list.remove(axis_requested) # Extract the requested axes (symmetries + unit) for axis_requested in axes_list: axis_requested.get_axis(self.axes[axis_requested.index], self.normalizations) return axes_list
def get_length(self, is_oneperiod=False, is_antiperiod=False): """Returns the length of the axis taking symmetries into account. Parameters ---------- self: DataLinspace a DataLinspace object is_oneperiod: bool return values on a single period is_antiperiod: bool return values on a semi period (only for antiperiodic signals) Returns ------- Length of axis """ if self.number is None: N = (self.final - self.initial + self.step) / self.step else: N = self.number # Rebuild symmetries if is_antiperiod: if self.name in self.symmetries: if "antiperiod" in self.symmetries.get(self.name): return N else: raise AxisError("ERROR: axis has no antiperiodicity") else: raise AxisError("ERROR: axis has no antiperiodicity") elif is_oneperiod: if self.name in self.symmetries: if "antiperiod" in self.symmetries.get(self.name): return N * 2 elif "period" in self.symmetries.get(self.name): return N else: raise AxisError("ERROR: unknown periodicity") else: return N else: if self.name in self.symmetries: if "antiperiod" in self.symmetries.get(self.name): return N * self.symmetries.get(self.name)["antiperiod"] elif "period" in self.symmetries.get(self.name): return N * self.symmetries.get(self.name)["period"] else: raise AxisError("ERROR: unknown periodicity") else: return N
def freq_to_time(self): """Performs the inverse Fourier Transform and stores the resulting field in a DataTime object. Parameters ---------- self : DataFreq a DataFreq object Returns ------- a DataTime object """ axes_str = [axis.name for axis in self.axes] axes_str = ["time" if axis_name == "freqs" else axis_name for axis_name in axes_str] axes_str = ["angle" if axis_name == "wavenumber" else axis_name for axis_name in axes_str] if axes_str == [axis.name for axis in self.axes]: raise AxisError( "ERROR: No available axis is compatible with fft (should be time or angle)" ) else: results = self.get_along(*axes_str) values = results.pop(self.symbol) Axes = [] for axis in results.keys(): Axes.append(Data1D(name=axis, values=results[axis])) return DataTime( name=self.name, unit=self.unit, symbol=self.symbol, axes=Axes, values=values, )
def get_length(self, is_oneperiod=False, is_antiperiod=False): """Returns the length of the axis taking symmetries into account. Parameters ---------- self: Data1D a Data1D object is_oneperiod: bool return values on a single period is_antiperiod: bool return values on a semi period (only for antiperiodic signals) Returns ------- Length of axis """ N = len(self.values) # Rebuild symmetries if is_antiperiod: if "antiperiod" in self.symmetries: return N else: raise AxisError("ERROR: axis has no antiperiodicity") elif is_oneperiod: if "antiperiod" in self.symmetries: return N * 2 elif "period" in self.symmetries: return N else: return N else: if "antiperiod" in self.symmetries: return N * self.symmetries["antiperiod"] elif "period" in self.symmetries: return N * self.symmetries["period"] else: return N
def get_values( self, unit="SI", is_oneperiod=False, is_antiperiod=False, is_smallestperiod=False ): """Returns the vector 'axis' by rebuilding the linspace, symmetries and unit included. Parameters ---------- self: DataLinspace a DataLinspace object unit: str requested unit is_oneperiod: bool return values on a single period is_antiperiod: bool return values on a semi period (only for antiperiodic signals) Returns ------- Vector of axis values """ initial = self.initial if self.number == None: final = self.final number = (final - initial + self.step) / self.step elif self.final == None: number = self.number final = self.initial + (number - 1) * self.step else: number = self.number final = self.final values = linspace(initial, final, int(number), endpoint=self.include_endpoint) # Unit conversion if unit != "SI" and unit != self.unit: values = convert(values, self.unit, unit) # Ignore symmetries if fft axis if self.name == "freqs" or self.name == "wavenumber": is_smallestperiod = True # Rebuild symmetries if is_smallestperiod: return values elif is_antiperiod: if "antiperiod" in self.symmetries: return values else: raise AxisError("ERROR: axis has no antiperiodicity") elif is_oneperiod: if "antiperiod" in self.symmetries: nper = self.symmetries["antiperiod"] self.symmetries["antiperiod"] = 2 values = rebuild_symmetries_axis(values, self.symmetries) self.symmetries["antiperiod"] = nper return values elif "period" in self.symmetries: return values else: return values else: values = rebuild_symmetries_axis(values, self.symmetries) return values
def get_harm_rad_along(self, N_harm, *args, unit="SI", is_norm=False, axis_data=[]): """Returns the ndarray of the radial component of the field, using conversions and symmetries if needed. Parameters ---------- self: Data a Data object *args: list of strings List of axes requested by the user, their units and values (optional) unit: str Unit requested by the user ("SI" by default) is_norm: bool Boolean indicating if the field must be normalized (False by default) axis_data: list list of ndarray corresponding to user-input data Returns ------- list of 1Darray of axes values, ndarray of field values """ if len(args) == 1 and type(args[0]) == tuple: args = args[0] # if called from another script with *args if "radial" in self.components.keys(): return_dict = self.components["radial"].get_harmonics( N_harm, args, unit=unit, is_norm=is_norm, axis_data=axis_data) return_dict[self.symbol + "_r"] = return_dict.pop( self.components["radial"].symbol) elif "x" in self.components.keys() and "y" in self.components.keys(): # Extract from DataND resultx = self.components["x"].get_harmonics(N_harm, args, unit=unit, is_norm=is_norm, axis_data=axis_data) resulty = self.components["y"].get_harmonics(N_harm, args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_x = resultx[self.components["x"].symbol] field_y = resulty[self.components["y"].symbol] x = resultx["x"] y = resultx["y"] # Convert to cylindrical coordinates (r, phi) = xy_to_rphi(x, y) (field_r, field_t) = cart2pol(field_x, field_y, phi) return_dict = dict(resultx) del return_dict[self.components["x"].symbol] return_dict[self.symbol + "_r"] = field_r else: raise AxisError("radial or x,y components necessary") return return_dict
def rebuild_symmetries_axis(values, symmetries): """Reconstructs the field of a Data object taking symmetries into account Parameters ---------- values: ndarray ndarray of a the axis values symmetries: dict Dictionary of the symmetries along the axis Returns ------- ndarray of the reconstructed axis """ values_new = values if "period" in symmetries.keys(): for i in range(symmetries.get("period") - 1): if len(values) == 1: if "delta" in symmetries.keys(): values_new = append(values_new, values_new[-1] + symmetries["delta"]) else: raise AxisError( "ERROR: must provide delta for symmetries with one sample" ) else: values_new = concatenate(( values_new, values + (values_new[-1] - values_new[-2]) + values_new[-1], )) elif "antiperiod" in symmetries.keys(): for i in range(symmetries.get("antiperiod") - 1): if len(values) == 1: if "delta" in symmetries.keys(): values_new = append(values_new, values_new[-1] + symmetries["delta"]) else: raise AxisError( "ERROR: must provide delta for symmetries with one sample" ) else: values_new = concatenate(( values_new, values + (values_new[-1] - values_new[-2]) + values_new[-1], )) return values_new
def get_values(self, unit="SI", is_oneperiod=False, is_antiperiod=False, is_smallestperiod=False): """Returns the vector 'axis' taking symmetries into account. Parameters ---------- self: Data1D a Data1D object unit: str requested unit is_oneperiod: bool return values on a single period is_antiperiod: bool return values on a semi period (only for antiperiodic signals) Returns ------- Vector of axis values """ values = self.values # Unit conversion if unit != "SI" and unit != self.unit: values = convert(values, self.unit, unit) # Ignore symmetries if fft axis if self.name == "freqs" or self.name == "wavenumber": is_smallestperiod = True # Rebuild symmetries if is_smallestperiod: return values elif is_antiperiod: if "antiperiod" in self.symmetries: return values else: raise AxisError("ERROR: axis has no antiperiodicity") elif is_oneperiod: if "antiperiod" in self.symmetries: nper = self.symmetries["antiperiod"] self.symmetries["antiperiod"] = 2 values = rebuild_symmetries_axis(values, self.symmetries) self.symmetries["antiperiod"] = nper return values elif "period" in self.symmetries: return values else: return values else: values = rebuild_symmetries_axis(values, self.symmetries) return values
def get_axis_periodic(self, Nper, is_antiperiod=False): """Returns the vector 'axis' taking symmetries into account. Parameters ---------- self: DataLinspace a DataLinspace object Nper: int number of periods is_antiperiod: bool return values on a semi period (only for antiperiodic signals) Returns ------- New DataLinspace """ # Dynamic import to avoid loop module = __import__("SciDataTool.Classes.DataLinspace", fromlist=["DataLinspace"]) DataLinspace = getattr(module, "DataLinspace") values = self.get_values() N = self.get_length() if N % Nper != 0: raise AxisError( "ERROR: length of axis is not divisible by the number of periods") values_per = values[:int(N / Nper)] if is_antiperiod: sym = "antiperiod" else: sym = "period" New_axis = DataLinspace( initial=self.initial, final=values_per[-1], number=int(N / Nper), include_endpoint=True, name=self.name, unit=self.unit, symmetries={sym: Nper}, normalizations=self.normalizations, is_components=self.is_components, symbol=self.symbol, ) return New_axis
def get_harm_ax_along(self, N_harm, *args, unit="SI", is_norm=False, axis_data=[]): """Returns the ndarray of the axial (z) component of the field, using conversions and symmetries if needed. Parameters ---------- self: Data a Data object *args: list of strings List of axes requested by the user, their units and values (optional) unit: str Unit requested by the user ("SI" by default) is_norm: bool Boolean indicating if the field must be normalized (False by default) axis_data: list list of ndarray corresponding to user-input data Returns ------- list of 1Darray of axes values, ndarray of field values """ if len(args) == 1 and type(args[0]) == tuple: args = args[0] # if called from another script with *args if "axial" in self.components.keys(): return_dict = self.components["axial"].get_harmonics( N_harm, args, unit=unit, is_norm=is_norm, axis_data=axis_data) return_dict[self.symbol + "_z"] = return_dict.pop( self.components["axial"].symbol) elif "z" in self.components.keys(): return_dict = self.components["z"].get_harmonics(N_harm, args, unit=unit, is_norm=is_norm, axis_data=axis_data) return_dict[self.symbol + "_z"] = return_dict.pop( self.components["z"].symbol) else: raise AxisError("axial or z component necessary") return return_dict
def get_axis_periodic(self, Nper, is_antiperiod=False): """Returns the vector 'axis' taking symmetries into account. Parameters ---------- self: Data1D a Data1D object Nper: int number of periods is_antiperiod: bool return values on a semi period (only for antiperiodic signals) Returns ------- New Data1D """ # Dynamic import to avoid loop module = __import__("SciDataTool.Classes.Data1D", fromlist=["Data1D"]) Data1D = getattr(module, "Data1D") values = self.values N = self.get_length() if N % Nper != 0: raise AxisError( "ERROR: length of axis is not divisible by the number of periods") values_per = values[:int(N / Nper)] if is_antiperiod: sym = "antiperiod" else: sym = "period" New_axis = Data1D( values=values_per, name=self.name, unit=self.unit, symmetries={self.name: { sym: Nper }}, is_components=self.is_components, symbol=self.symbol, ) return New_axis
def freq_to_time(self): """Performs the inverse Fourier Transform and stores the resulting field in a DataTime object. Parameters ---------- self : DataFreq a DataFreq object Returns ------- a DataTime object """ # Dynamic import to avoid loop module = __import__("SciDataTool.Classes.DataTime", fromlist=["DataTime"]) DataTime = getattr(module, "DataTime") axes_str = [] for i, axis in enumerate(self.axes): if axis.is_components: axis_str = axis.name + str(list(range(len(axis.values)))) elif axis.name == "freqs": axis_str = "time" elif axis.name == "wavenumber": axis_str = "angle" else: axis_str = axis.name axes_str.append(axis_str) if axes_str == [axis.name for axis in self.axes]: raise AxisError( "ERROR: No available axis is compatible with fft (should be time or angle)" ) else: results = self.get_along(*axes_str) values = results.pop(self.symbol) Axes = [] for axis in self.axes: if axis.is_components: # components axis name = axis.name is_components = True axis_values = axis.values unit = "SI" elif axis.name == "freqs": name = "time" is_components = False axis_values = results["time"] unit = "s" elif axis.name == "wavenumber": name = "angle" is_components = False axis_values = results["angle"] unit = "rad" else: name = axis.name is_components = False axis_values = results[axis.name] unit = axis.unit Axes.append( Data1D( name=name, unit=unit, values=axis_values, is_components=is_components, ) ) return DataTime( name=self.name, unit=self.unit, symbol=self.symbol, axes=Axes, values=values, )
def rebuild_symmetries( self, values, axes_list, ): """Reconstructs the field of a Data object taking symmetries into account Parameters ---------- self: Data a Data object values: ndarray ndarray of a field axes_list: list a list of RequestedAxis objects Returns ------- ndarray of the reconstructed field """ for axis in axes_list: if axis.transform != "fft" and axis.extension in [ "whole", "interval", "oneperiod", "antiperiod", "smallestperiod", ]: if axis.extension == "smallestperiod": is_smallestperiod = True is_oneperiod = False is_antiperiod = False elif axis.extension == "antiperiod": is_smallestperiod = False is_oneperiod = False is_antiperiod = True elif axis.extension == "oneperiod": is_smallestperiod = False is_oneperiod = True is_antiperiod = False else: is_smallestperiod = False is_oneperiod = False is_antiperiod = False # Rebuild symmetries axis_symmetries = self.axes[axis.index].symmetries if is_smallestperiod: return values elif is_antiperiod: if "antiperiod" in axis_symmetries: return values else: raise AxisError("ERROR: axis has no antiperiodicity") elif is_oneperiod: if "antiperiod" in axis_symmetries: nper = axis_symmetries["antiperiod"] axis_symmetries["antiperiod"] = 2 values = rebuild_symmetries_fct( values, axis.index, axis_symmetries ) axis_symmetries["antiperiod"] = nper return values else: return values else: values = rebuild_symmetries_fct( values, axis.index, axis_symmetries ) return values else: return values
def get_axis(self, axis, is_real): """Computes the vector 'axis' in the unit required, using conversions and symmetries if needed. Parameters ---------- self: RequestedAxis a RequestedAxis object axis: Axis an Axis object """ if self.operation is not None: module = import_module("SciDataTool.Functions.conversions") func = getattr(module, self.operation) # Conversion function if isinstance(axis, DataPattern): self.is_pattern = True self.rebuild_indices = axis.rebuild_indices self.is_step = axis.is_step is_components = getattr(axis, "is_components", False) if is_components: values = axis.get_values() if not self.extension in ["sum", "rss", "mean", "rms", "integrate"]: self.extension = "list" if self.indices is not None: self.values = values[self.indices] else: self.values = values else: if self.extension == "pattern": if not self.is_pattern: raise AxisError( "ERROR: [pattern] cannot be called with non DataPattern axis" ) else: is_smallestperiod = True is_oneperiod = False is_antiperiod = False self.extension = "smallestperiod" elif self.extension == "smallestperiod": if isinstance(axis, DataPattern): raise AxisError( "ERROR: [smallestperiod] cannot be called with DataPattern axis" ) else: is_smallestperiod = True is_oneperiod = False is_antiperiod = False elif self.extension == "antiperiod": if isinstance(axis, DataPattern): raise AxisError( "ERROR: [antiperiod] cannot be called with DataPattern axis" ) else: is_smallestperiod = False is_oneperiod = False is_antiperiod = True elif self.extension == "oneperiod" or self.transform == "fft": if isinstance(axis, DataPattern): raise AxisError( "ERROR: [oneperiod] cannot be called with DataPattern axis" ) else: is_smallestperiod = False is_oneperiod = True is_antiperiod = False elif self.extension in ["sum", "rss", "mean", "rms", "integrate"]: is_smallestperiod = False is_oneperiod = False is_antiperiod = False # Ignore symmetries if fft axis elif self.name == "freqs" or self.name == "wavenumber": is_smallestperiod = True is_oneperiod = False is_antiperiod = False else: if self.input_data is not None and not self.is_step: # Check if symmetries need to be reconstructed to match input_data if self.operation is not None: axis_values = func( axis.get_values(is_smallestperiod=True, ), is_real=is_real, ) else: axis_values = axis.get_values(is_smallestperiod=True, ) if min(self.input_data) >= min(axis_values) and max( self.input_data) <= max(axis_values): is_smallestperiod = True is_oneperiod = False is_antiperiod = False else: if self.operation is not None: axis_values = func( axis.get_values(is_oneperiod=True, ), is_real=is_real, ) else: axis_values = axis.get_values(is_oneperiod=True, ) if min(self.input_data) >= min(axis_values) and max( self.input_data) <= max(axis_values): is_smallestperiod = False is_oneperiod = True is_antiperiod = False self.extension = "oneperiod" else: is_smallestperiod = False is_oneperiod = False is_antiperiod = False if not self.is_pattern: self.extension = "interval" elif self.transform == "ifft": # Ignore symmetries in ifft case is_smallestperiod = True is_oneperiod = False is_antiperiod = False else: is_smallestperiod = False is_oneperiod = False is_antiperiod = False # Get original values of the axis if self.operation is not None: values = array( func( axis.get_values( is_oneperiod=is_oneperiod, is_antiperiod=is_antiperiod, is_smallestperiod=is_smallestperiod, ), is_real=is_real, )) # Store original values self.corr_values = array( axis.get_values( is_oneperiod=is_oneperiod, is_antiperiod=is_antiperiod, is_smallestperiod=is_smallestperiod, )) else: values = array( axis.get_values( is_oneperiod=is_oneperiod, is_antiperiod=is_antiperiod, is_smallestperiod=is_smallestperiod, )) # Unit conversions and normalizations unit = self.unit if unit == self.corr_unit or unit == "SI": pass elif unit in axis.normalizations: if axis.normalizations.get(unit) == "indices": values = array([i for i in range(len(values))]) elif isinstance(axis.normalizations.get(unit), ndarray): values = axis.normalizations.get(unit) else: values = values / axis.normalizations.get(unit) else: values = convert(values, self.corr_unit, unit) # Rebuild symmetries in fft case if self.transform == "fft": if "period" in axis.symmetries: if axis.name != "time": values = values * axis.symmetries["period"] elif "antiperiod" in axis.symmetries: if axis.name != "time": values = values * axis.symmetries["antiperiod"] / 2 # Rebuild symmetries in ifft case if self.transform == "ifft": # if "antiperiod" in axis.symmetries: # axis.symmetries["antiperiod"] = int(axis.symmetries["antiperiod"]/2) if (self.extension != "smallestperiod" and self.extension != "oneperiod" and self.extension != "antiperiod"): values = rebuild_symmetries_axis(values, axis.symmetries) # if "period" in axis.symmetries: # if axis.name != "freqs": # values = values * axis.symmetries["period"] # elif "antiperiod" in axis.symmetries: # if axis.name != "freqs": # values = values * axis.symmetries["antiperiod"] / 2 # Interpolate axis with input data if self.input_data is None: self.values = values else: # if self.is_step: # values = values[axis.rebuild_indices] if len(self.input_data) == 2 and self.extension != "axis_data": indices = [ i for i, x in enumerate(values) if x >= self.input_data[0] and x <= self.input_data[-1] ] if self.indices is None: self.indices = indices else: indices_new = [] for i in self.indices: if i in indices: indices_new.append(i) self.indices = indices_new self.input_data = None else: self.values = values if self.indices is not None: self.values = values[self.indices] if self.extension in ["sum", "rss", "mean", "rms", "integrate"]: self.indices = None
def read_input_strings(args, axis_data): """Reads the string input into the "get_along" methods to define the axes Parameters ---------- args: list list of string describing the requested axes axis_data: ndarray user-input values for the axes Returns ------- list of axes data (axes_list) """ axes_list = [] for axis_str in args: unit = "SI" values = None indices = None input_data = None # Detect unit if "{" in axis_str: elems = axis_str.split("{") unit = elems[1].strip("}") axis_str = elems[0] # Detect sum if "sum" in axis_str: elems = axis_str.split("=sum") name = elems[0] extension = "sum" elif "oneperiod" in axis_str: elems = axis_str.split("[") name = elems[0] extension = "oneperiod" elif "antiperiod" in axis_str: elems = axis_str.split("[") name = elems[0] extension = "antiperiod" elif "smallestperiod" in axis_str: elems = axis_str.split("[") name = elems[0] extension = "smallestperiod" # Detect axis_data input elif "axis_data" in axis_str: elems = axis_str.split("=axis_data") name = elems[0] extension = "interval" try: input_data = axis_data[int(elems[1]) - 1] except: try: input_data = axis_data[0] except: raise AxisError("ERROR: Absence of axis_data") # Detect interval elif "=[" in axis_str: elems = axis_str.split("=[") elems2 = elems[1].split(",") init_str = elems2[0] interval_init = eval(init_str) final_str = elems2[1].strip("]") interval_final = eval(final_str) name = elems[0] extension = "interval" input_data = [interval_init, interval_final] # Detect single value elif "=" in axis_str: elems = axis_str.split("=") name = elems[0] extension = "single" input_data = [eval(elems[1])] # Detect index input... elif "[" in axis_str: elems = axis_str.split("[") ind_str = elems[1].strip("]") name = elems[0] # Range of indices if ":" in ind_str: elems2 = ind_str.split(":") extension = "interval" indices = [ i for i in range(int(elems2[0]), int(elems2[1]) + 1) ] # List of indices elif "," in ind_str: extension = "list" indices = ind_str.split(",") # Single index else: extension = "single" indices = [int(ind_str)] # Whole axis else: name = axis_str extension = "whole" # RequestedAxis object creation axis = RequestedAxis( name=name, unit=unit, extension=extension, values=values, indices=indices, input_data=input_data, ) axes_list.append(axis) return axes_list
def get_harmonics(self, N_harm, *args, unit="SI", is_norm=False, is_flat=False): """Returns the complex Fourier Transform of the field, using conversions and symmetries if needed. Parameters ---------- self: Data a Data object N_harm: int Number of largest harmonics to be extracted args: list Axes names, ranges and units unit: str Unit demanded by the user ("SI" by default) is_norm: bool Boolean indicating if the field must be normalized (False by default) is_flat: bool Boolean if the output data remains flattened (for 2D cases) Returns ------- list of 1Darray of axes values, ndarray of magnitude of FT """ # Read the axes input in args if len(args) == 1 and type(args[0]) == tuple: args = args[0] # if called from another script with *args axes_list = read_input_strings(args, []) # Extract the requested axes (symmetries + unit) for axis_requested in axes_list: if axis_requested[3] == "values": # Get original values of the axis axis_requested.append( self.get_FT_axis(axis_requested[0] + axis_requested[1])) # Interpolate axis with input data if str(axis_requested[4]) == "whole": axis_requested[4] = axis_requested[5] axis_requested[5] = "whole" else: axis_requested[4] = get_common_base(axis_requested[5], axis_requested[4]) # Change fft name for the slices of the field if axis_requested[0] == "freqs": axis_requested[0] = "time" elif axis_requested[0] == "wavenumber": axis_requested[0] = "angle" # Check if the requested axis is defined in the Data object for axis_requested in axes_list: axis_name = axis_requested[0] is_match = False for index, axis in enumerate(self.axes): if axis.name == axis_name: is_match = True if not is_match: axes_list.remove(axis_requested) # Rebuild symmetries of field if axis is extracted values = self.values for index, axis in enumerate(self.axes): for axis_requested in axes_list: if axis.name in self.symmetries.keys( ) and axis.name == axis_requested[0]: values = rebuild_symmetries(values, index, self.symmetries.get(axis.name)) # Extract the slices of the field (single values) for index, axis in enumerate(self.axes): is_match = False for axis_requested in axes_list: if axis.name == axis_requested[0]: is_match = True if axis_requested[3] == "indices" and axis_requested[ 2] == "single": values = take(values, axis_requested[4], axis=index) if not is_match: # Axis was not specified -> take slice at the first value values = take(values, [0], axis=index) # Interpolate over axis values (single values) for index, axis in enumerate(self.axes): for axis_requested in axes_list: if (axis.name == axis_requested[0] and axis_requested[3] == "values" and axis_requested[2] == "single"): values = apply_along_axis( get_interpolation, index, values, axis_requested[5], axis_requested[4], ) # Perform Fourier Transform values = np_abs(comp_fft(values)) # Extract slices again (intervals) for index, axis in enumerate(self.axes): for axis_requested in axes_list: if axis.name == axis_requested[0]: if axis_requested[2] == "indices" and axis_requested[ 2] == "interval": values = take(values, axis_requested[4], axis=index) # Interpolate over axis values again (intervals) for index, axis in enumerate(self.axes): for axis_requested in axes_list: if axis.name == axis_requested[0]: if axis_requested[3] == "values" and axis_requested[ 2] == "interval": values = apply_along_axis( get_interpolation, index, values, axis_requested[5], axis_requested[4], ) # Eliminate dimensions=1 values = squeeze(values) # Test if data is 1D or 2D if len(values.shape) > 2: raise AxisError("ERROR: only 1D or 2D implemented") else: # Convert into right unit if unit == self.unit or unit == "SI": if is_norm: try: values = values / self.normalizations.get("ref") except: raise NormError( "ERROR: Reference value not specified for normalization" ) elif unit == "dB": ref_value = 1.0 if "ref" in self.normalizations.keys(): ref_value *= self.normalizations.get("ref") values = to_dB(values, self.unit, ref_value) elif unit == "dBA": ref_value = 1.0 is_match = False if "ref" in self.normalizations.keys(): ref_value *= self.normalizations.get("ref") for axis_requested in axes_list: if axis_requested[0] == "time": is_match = True values = to_dBA(values, axis_requested[4], self.unit, ref_value) if not is_match: raise UnitError( "ERROR: dBA conversion only available for fft with frequencies" ) elif unit in self.normalizations: values = values / self.normalizations.get(unit) else: values = convert(values, self.unit, unit) # 1D case if len(values.shape) == 1: for axis_requested in axes_list: if axis_requested[2] == "interval": axis_values = axis_requested[4] indices = argsort(negative(values)) indices = indices[:N_harm] axis_values = axis_values[indices] values = values[indices] return [axis_values, values] # 2D case else: for axis_requested in axes_list: if axis_requested[0] == "angle": r = axis_requested[4] elif axis_requested[0] == "time": f = axis_requested[4] # Flatten the data values_flat = values.flatten() R, F = meshgrid(r, f) f = F.flatten() r = R.flatten() # Get the N_harm largest peaks indices = argsort(negative(values_flat)) indices = indices[:N_harm] values = values_flat[indices] f = f[indices] r = r[indices] if len(values.shape) == 2 and not is_flat: f.reshape((N_harm, N_harm)) r.reshape((N_harm, N_harm)) values.reshape((N_harm, N_harm)) return [f, r, values]
def get_mag_xyz_along(self, *args, unit="SI", is_norm=False, axis_data=[]): """Returns the list of the cartesian (comp_x,comp_y,comp_z) components of the field, using conversions and symmetries if needed. Parameters ---------- self: Data a Data object *args: list of strings List of axes requested by the user, their units and values (optional) unit: str Unit requested by the user ("SI" by default) is_norm: bool Boolean indicating if the field must be normalized (False by default) axis_data: list list of ndarray corresponding to user-input data Returns ------- list of 1Darray of axes values, ndarray of field values """ if len(args) == 1 and type(args[0]) == tuple: args = args[0] # if called from another script with *args if "radial" in self.components.keys( ) and "circumferential" in self.components.keys(): # Extract from DataND resultr = self.components["radial"].get_magnitude_along( args, unit=unit, is_norm=is_norm, axis_data=axis_data) resultphi = self.components["circumferential"].get_magnitude_along( args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_r = resultr[self.components["radial"].symbol] field_c = resultphi[self.components["circumferential"].symbol] shape = field_r.shape phi = resultr["phi"] # Convert to cylindrical coordinates (field_x, field_y) = pol2cart(field_r, field_c, phi) if "axial" in self.components.keys(): resultz = self.components["axial"].get_magnitude_along( args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_z = resultz[self.components["axial"].symbol] elif "comp_z" in self.components.keys(): resultz = self.components["comp_z"].get_magnitude_along( args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_z = resultz[self.components["comp_z"].symbol] else: field_z = zeros(shape) return_dict = dict(resultr) del return_dict[self.components["radial"].symbol] elif "comp_x" in self.components.keys(): resultx = self.components["comp_x"].get_magnitude_along( args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_x = resultx[self.components["comp_x"].symbol] shape = field_x.shape if "comp_y" in self.components.keys(): resulty = self.components["comp_y"].get_magnitude_along( args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_y = resulty[self.components["comp_y"].symbol] else: field_y = zeros(shape) if "axial" in self.components.keys(): resultz = self.components["axial"].get_magnitude_along( args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_z = resultz[self.components["axial"].symbol] elif "comp_z" in self.components.keys(): resultz = self.components["comp_z"].get_magnitude_along( args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_z = resultz[self.components["comp_z"].symbol] else: field_z = zeros(shape) return_dict = dict(resultx) del return_dict[self.components["comp_x"].symbol] elif "comp_y" in self.components.keys(): resulty = self.components["comp_y"].get_magnitude_along( args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_y = resultphi[self.components["comp_y"].symbol] shape = field_y.shape if "comp_x" in self.components.keys(): resultx = self.components["comp_x"].get_magnitude_along( args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_x = resultphi[self.components["comp_x"].symbol] else: field_x = zeros(shape) if "axial" in self.components.keys(): resultz = self.components["axial"].get_magnitude_along( args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_z = resultz[self.components["axial"].symbol] elif "comp_z" in self.components.keys(): resultz = self.components["comp_z"].get_magnitude_along( args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_z = resultz[self.components["comp_z"].symbol] else: field_z = zeros(shape) return_dict = dict(resulty) del return_dict[self.components["comp_y"].symbol] elif "axial" in self.components.keys(): resultz = self.components["axial"].get_magnitude_along( args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_z = resultz[self.components["axial"].symbol] shape = field_z.shape field_x = zeros(shape) field_y = zeros(shape) return_dict = dict(resultz) del return_dict[self.components["axial"].symbol] elif "comp_z" in self.components.keys(): resultz = self.components["comp_z"].get_magnitude_along( args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_z = resultz[self.components["comp_z"].symbol] shape = field_z.shape field_x = zeros(shape) field_y = zeros(shape) return_dict = resultz del return_dict[self.components["comp_z"].symbol] else: raise AxisError( "Vector_field object is empty (should contain at least radial, circumferential, axial, x, y or z" ) return_dict["comp_x"] = field_x return_dict["comp_y"] = field_y return_dict["comp_z"] = field_z return return_dict
def time_to_freq(self): """Performs the Fourier Transform and stores the resulting field in a DataFreq object. Parameters ---------- self : DataTime a DataTime object Returns ------- a DataFreq object """ # Dynamic import to avoid loop module = __import__("SciDataTool.Classes.DataFreq", fromlist=["DataFreq"]) DataFreq = getattr(module, "DataFreq") axes_str = [] for i, axis in enumerate(self.axes): if axis.is_components: axis_str = axis.name + str(list(range(len(axis.values)))) elif axis.name == "time": axis_str = "freqs" elif axis.name == "angle": axis_str = "wavenumber" else: axis_str = axis.name axes_str.append(axis_str) if axes_str == [axis.name for axis in self.axes]: raise AxisError( "ERROR: No available axis is compatible with fft (should be time or angle)" ) else: results = self.get_along(*axes_str) values = results.pop(self.symbol) Axes = [] for axis in self.axes: if axis.is_components: # components axis name = axis.name is_components = True axis_values = axis.values unit = "SI" elif axis.name == "time": name = "freqs" is_components = False axis_values = results["freqs"] unit = "Hz" elif axis.name == "angle": name = "wavenumber" is_components = False axis_values = results["wavenumber"] unit = "dimless" else: name = axis.name is_components = False axis_values = results[axis.name] unit = axis.unit if "antiperiod" in axis.symmetries: symmetries = {"period": int(axis.symmetries["antiperiod"] / 2)} else: symmetries = axis.symmetries.copy() Axes.append( Data1D( name=name, unit=unit, values=axis_values, is_components=is_components, symmetries=symmetries, normalizations=axis.normalizations.copy(), )) return DataFreq( name=self.name, unit=self.unit, symbol=self.symbol, axes=Axes, values=values, is_real=self.is_real, )
def read_input_strings(args, axis_data): """Reads the string input into the "get_along" methods to define the axes Parameters ---------- args: list list of string describing the requested axes axis_data: ndarray user-input values for the axes Returns ------- list of axes data (axes_list) """ axes_list = [] for axis_str in args: unit = "SI" values = None indices = None input_data = None # Detect unit if "{" in axis_str: elems = axis_str.split("{") unit = elems[1].strip("}") axis_str = elems[0] # Detect normalization if "->" in axis_str: elems = axis_str.split("->") if "=" in elems[1]: unit = elems[1].split("=")[0] elif "[" in elems[1]: unit = elems[1].split("[")[0] elif ">" in elems[1]: unit = elems[1].split("[")[0] elif "<" in elems[1]: unit = elems[1].split("[")[0] else: unit = elems[1] name = elems[0] axis_str = axis_str.replace("->" + unit, "") # Detect rms sum if "rss" in axis_str: elems = axis_str.split("=rss") name = elems[0] extension = "rss" # Detect sum elif "sum" in axis_str: elems = axis_str.split("=sum") name = elems[0] extension = "sum" # Detect rms mean elif "rms" in axis_str: elems = axis_str.split("=rms") name = elems[0] extension = "rms" # Detect mean elif "mean" in axis_str: elems = axis_str.split("=mean") name = elems[0] extension = "mean" # Detect integrate elif "integrate" in axis_str: elems = axis_str.split("=integrate") name = elems[0] extension = "integrate" # Detect periods elif "oneperiod" in axis_str: elems = axis_str.split("[") name = elems[0] extension = "oneperiod" elif "antiperiod" in axis_str: elems = axis_str.split("[") name = elems[0] extension = "antiperiod" elif "smallestperiod" in axis_str: elems = axis_str.split("[") name = elems[0] extension = "smallestperiod" # Detect pattern elif "pattern" in axis_str: elems = axis_str.split("[") name = elems[0] extension = "pattern" # Detect axis_data input elif "axis_data" in axis_str: elems = axis_str.split("=axis_data") name = elems[0] extension = "axis_data" try: input_data = axis_data[name] except: raise AxisError("ERROR: No axis_data provided") # Detect above elif ">" in axis_str: elems = axis_str.split(">") init_str = elems[1] interval_init = eval(init_str) interval_final = inf name = elems[0] extension = "interval" input_data = [interval_init, interval_final] # Detect below elif "<" in axis_str: elems = axis_str.split("<") init_str = elems[1] interval_init = -inf interval_final = eval(init_str) name = elems[0] extension = "interval" input_data = [interval_init, interval_final] # Detect interval elif "=[" in axis_str: elems = axis_str.split("=[") elems2 = elems[1].split(",") if len(elems2) > 2: extension = "list" name = elems[0] input_data = [eval(elem.strip("]")) for elem in elems2] else: init_str = elems2[0] interval_init = eval(init_str) final_str = elems2[1].strip("]") interval_final = eval(final_str) name = elems[0] extension = "interval" input_data = [interval_init, interval_final] # Detect single value elif "=" in axis_str: elems = axis_str.split("=") name = elems[0] extension = "single" input_data = [eval(elems[1])] # Detect index input... elif "[" in axis_str: elems = axis_str.split("[") ind_str = elems[1].strip("]") name = elems[0] # Range of indices if ":" in ind_str: elems2 = ind_str.split(":") extension = "interval" indices = [i for i in range(int(elems2[0]), int(elems2[1]))] # List of indices elif "," in ind_str: extension = "list" indices = [int(x) for x in ind_str.split(",")] # Single index else: extension = "single" indices = [int(ind_str)] # Whole axis else: name = axis_str extension = "whole" # Detect 1/nth octave band if "oct" in unit: noct = int(unit.split("oct")[0].split("/")[1]) unit = "SI" else: noct = None # RequestedAxis object creation axis = RequestedAxis( name=name, unit=unit, extension=extension, values=values, indices=indices, input_data=input_data, noct=noct, ) axes_list.append(axis) return axes_list
def freq_to_time(self): """Performs the inverse Fourier Transform and stores the resulting field in a DataTime object. Parameters ---------- self : DataFreq a DataFreq object Returns ------- a DataTime object """ # Dynamic import to avoid loop module = __import__("SciDataTool.Classes.DataTime", fromlist=["DataTime"]) DataTime = getattr(module, "DataTime") module = __import__("SciDataTool.Classes.DataPattern", fromlist=["DataPattern"]) DataPattern = getattr(module, "DataPattern") axes_str = [] for i, axis in enumerate(self.axes): if axis.is_components: axis_str = axis.name + str(list(range(len(axis.values)))) elif axis.name == "freqs": axis_str = "time[smallestperiod]" elif axis.name == "wavenumber": axis_str = "angle[smallestperiod]" elif isinstance(axis, DataPattern): axis_str = axis.name + "[pattern]" else: axis_str = axis.name + "[smallestperiod]" axes_str.append(axis_str) if axes_str == [axis.name for axis in self.axes]: raise AxisError( "ERROR: No available axis is compatible with fft (should be time or angle)" ) else: results = self.get_along(*axes_str) values = results.pop(self.symbol) Axes = [] for axis in self.axes: if axis.name == "freqs": axis_new = Data1D( name="time", is_components=False, values=results["time"], unit="s", symmetries=axis.symmetries.copy(), normalizations=axis.normalizations.copy(), ) elif axis.name == "wavenumber": axis_new = Data1D( name="angle", is_components=False, values=results["angle"], unit="rad", symmetries=axis.symmetries.copy(), normalizations=axis.normalizations.copy(), ) else: axis_new = axis.copy() Axes.append(axis_new) return DataTime( name=self.name, unit=self.unit, symbol=self.symbol, axes=Axes, values=values, is_real=self.is_real, normalizations=self.normalizations.copy(), )
def get_harm_rphiz_along(self, N_harm, *args, unit="SI", is_norm=False, axis_data=[]): """Returns the list of the cylindrical (r,phi,z) components of the field, using conversions and symmetries if needed. Parameters ---------- self: Data a Data object *args: list of strings List of axes requested by the user, their units and values (optional) unit: str Unit requested by the user ("SI" by default) is_norm: bool Boolean indicating if the field must be normalized (False by default) axis_data: list list of ndarray corresponding to user-input data Returns ------- list of 1Darray of axes values, ndarray of field values """ if len(args) == 1 and type(args[0]) == tuple: args = args[0] # if called from another script with *args if "x" in self.components.keys() and "y" in self.components.keys(): # Extract from DataND resultx = self.components["x"].get_harmonics(N_harm, args, unit=unit, is_norm=is_norm, axis_data=axis_data) resulty = self.components["y"].get_harmonics(N_harm, args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_x = resultx[self.components["x"].symbol] field_y = resulty[self.components["y"].symbol] shape = field_x.shape x = resultx["x"] y = resultx["y"] # Convert to cylindrical coordinates (r, phi) = xy_to_rphi(x, y) (field_r, field_t) = cart2pol(field_x, field_y, phi) if "axial" in self.components.keys(): resultz = self.components["axial"].get_harmonics( N_harm, args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_z = resultz[self.components["axial"].symbol] elif "z" in self.components.keys(): resultz = self.components["z"].get_harmonics(N_harm, args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_z = resultz[self.components["z"].symbol] else: field_z = zeros(shape) return_dict = dict(resultx) del return_dict[self.components["x"].symbol] elif "radial" in self.components.keys(): resultr = self.components["radial"].get_harmonics(N_harm, args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_r = resultr[self.components["radial"].symbol] shape = field_r.shape if "tangential" in self.components.keys(): resultphi = self.components["tangential"].get_harmonics( N_harm, args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_t = resultphi[self.components["tangential"].symbol] else: field_t = zeros(shape) if "axial" in self.components.keys(): resultz = self.components["axial"].get_harmonics( N_harm, args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_z = resultz[self.components["axial"].symbol] elif "z" in self.components.keys(): resultz = self.components["z"].get_harmonics(N_harm, args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_z = resultz[self.components["z"].symbol] else: field_z = zeros(shape) return_dict = dict(resultr) del return_dict[self.components["radial"].symbol] elif "tangential" in self.components.keys(): resultphi = self.components["tangential"].get_harmonics( N_harm, args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_t = resultphi[self.components["tangential"].symbol] shape = field_t.shape if "radial" in self.components.keys(): resultr = self.components["radial"].get_harmonics( N_harm, args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_r = resultphi[self.components["radial"].symbol] else: field_r = zeros(shape) if "axial" in self.components.keys(): resultz = self.components["axial"].get_harmonics( N_harm, args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_z = resultz[self.components["axial"].symbol] elif "z" in self.components.keys(): resultz = self.components["z"].get_harmonics(N_harm, args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_z = resultz[self.components["z"].symbol] else: field_z = zeros(shape) return_dict = dict(resultphi) del return_dict[self.components["tangential"].symbol] elif "axial" in self.components.keys(): resultz = self.components["axial"].get_harmonics(N_harm, args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_z = resultz[self.components["axial"].symbol] shape = field_z.shape field_r = zeros(shape) field_t = zeros(shape) return_dict = dict(resultz) del return_dict[self.components["axial"].symbol] elif "z" in self.components.keys(): resultz = self.components["z"].get_harmonics(N_harm, args, unit=unit, is_norm=is_norm, axis_data=axis_data) field_z = resultz[self.components["z"].symbol] shape = field_z.shape field_r = zeros(shape) field_t = zeros(shape) return_dict = resultz del return_dict[self.components["z"].symbol] else: raise AxisError( "Vector_field object is empty (should contain at least radial, tangential, axial, x, y or z" ) return_dict[self.symbol + "_r"] = field_r return_dict[self.symbol + "_t"] = field_t return_dict[self.symbol + "_z"] = field_z return return_dict