def circular_magnetic_field_cart(x, y, propagation_function, tangential_axis='z'): """ This makes circular magnetic fields, in a way, fields without any divergence. However, this measures not the rotational vector, but tangential vector. The tangential axis is the axis of which is the axis of rotation for the field. Assume that the positive direction is pointing to the user. """ # Type check x = valid.validate_float_array(x) y = valid.validate_float_array(y) # Convert to a polar system for tangential vector. r_subaxis = np.hypot(x, y) phi_subaxis = np.arctan2(y, x) # Calculate the magnitude of the tangential vector. B_t = propagation_function(r_subaxis) # The vector is tangent to a circle made by r, thus the angle is related to # phi, but is not phi. B_angle = phi_subaxis + np.pi / 2 # Calculate the components of the magnetic field vector based on the # magnitude and the angle. B_x = B_t * np.cos(B_angle) B_y = B_t * np.sin(B_angle) # Return return B_x, B_y
def circular_magnetic_field_cyln( r, phi, z, propagation_function, ): """ This makes circular magnetic fields, in a way, fields without any divergence. However, this measures not the tangential vector, but rotational vector. """ # Type check r = valid.validate_float_array(r, deep_validate=True, greater_than=0) phi = valid.validate_float_array(phi, deep_validate=True, greater_than=0, less_than=2 * np.pi) z = valid.validate_float_array(z) # Because of its invariantness in phi and z, also, only the r value # matters in this function. B_r = np.zeros_like(r) B_phi = propagation_function(r) B_z = np.zeros_like(r) # Return return B_r, B_phi, B_z
def multigaussian_function(x_input, center_array, std_dev_array, height_array, gaussian_count=1): """ Equation for a multigaussian, where the arrays are parallel, denoting the properties of each gaussian. Assuming all of the gaussians are linearly combined. """ # Type check gaussian_count = valid.validate_int_value(gaussian_count, greater_than=0) x_input = valid.validate_float_array(x_input) center_array = valid.validate_float_array( center_array, size=gaussian_count) std_dev_array = valid.validate_float_array(std_dev_array, size=gaussian_count, deep_validate=True, greater_than=0) height_array = valid.validate_float_array( height_array, size=gaussian_count) # Define initial variables. n_datapoints = len(x_input) y_output_array = np.zeros(n_datapoints) # Loop and sum over all gaussian values. for gaussiandex in range(gaussian_count): y_output_array += gaussian_function(x_input, center_array[gaussiandex], std_dev_array[gaussiandex], height_array[gaussiandex]) return np.array(y_output_array, dtype=float)
def generate_noisy_gaussian(center, std_dev, height, x_domain, noise_domain, n_datapoints): """ Generate a gaussian with some aspect of noise. Input: center = central x value std_dev = standard deviation of the function height = height (y-off set) of the function noise_range = uniform random distribution of noise from perfect gauss function x_range = absolute domain of the gaussian function n_datapoints = total number of input datapoints of gaussian function Output: x_values,y_values x_values = the x-axial array of the gaussian function within the domain y_values = the y-axial array of the gaussian function within the domain """ # Type check. center = valid.validate_float_value(center) std_dev = valid.validate_float_value(std_dev, greater_than=0) height = valid.validate_float_value(height) x_domain = valid.validate_float_array(x_domain, shape=(2,), size=2) noise_domain = valid.validate_float_array(noise_domain, shape=(2,), size=2) n_datapoints = valid.validate_int_value(n_datapoints, greater_than=0) # Generate the gaussian function and map to an output with the input # parameters. x_values, y_values = generate_gaussian(center, std_dev, height, x_domain=x_domain, n_datapoints=n_datapoints) # Imbue the gaussian with random noise. y_values = misc.generate_noise(y_values, noise_domain, distribution='uniform') return x_values, y_values
def bessel_function_2nd(x_input, order): """ This is a wrapper function around scipy's bessel function of the second kind, given any real order value. This wrapper also includes input verification. Input: x_input = The x value input of the bessel function. order = The value(s) of orders wanting to be computed. center = The x displacement from center (i.e. the new center) height = The y displacement from center (i.e. the new height offset) Output: y_output = The y value(s) from the bessel function. """ # Type check. x_input = valid.validate_float_array(x_input) if isinstance(order, (float, int)): order = valid.validate_float_value(order) else: order = valid.validate_float_array(order, deep_validate=True) # Compute the value(s) of the bessel_function y_output = sp_spcl.yv(order, x_input) return np.array(y_output, dtype=float)
def bessel_function_1st(x_input, order): """ This is a wrapper function around scipy's bessel function of the first kind, given any real order value. This wrapper also includes input verification. Input: x_input = The x value input of the bessel function. order = The value(s) of orders wanting to be computed. Output: y_output = The y value(s) from the bessel function. """ # Type check. x_input = valid.validate_float_array(x_input) if isinstance(order, (float, int)): order = valid.validate_float_value(order) else: order = valid.validate_float_array(order, deep_validate=True) # Compute the value(s) of the bessel_function y_output = sp_spcl.jv(order, x_input) return np.array(y_output, dtype=float)
def Ewer_Basu__B_z(r, z, h, k_array, disk_radius, uniform_B0): """ This implements equation 46 of Ewertiwski & Basu 2013. The k_array (values of k) determine the number of summation terms that will be computed. """ # Type check r = valid.validate_float_array(r) z = valid.validate_float_array(z) k_array = valid.validate_float_array(k_array, deep_validate=True) disk_radius = valid.validate_float_value(disk_radius, greater_than=0) uniform_B0 = valid.validate_float_value(uniform_B0) # Shorthand for the squareroot of the eigenvalues. Account for 0 indexing. def evsq(m): return np.sqrt(Ewer_Basu__eigenvalues(m + 1, disk_radius)) # Shorthand for bessel function of order 1. def bess0(x): return sp_spcl.jv(0, x) Bfeild_z = 0 for kdex, k_value in enumerate(k_array): # Dividing the equation into smaller chunks for readability. coefficient = k_value * evsq(kdex) * bess0(evsq(kdex) * r) # Left and right erfc functions of the equation, respectively. plus_erfc = sp_spcl.erfc((0.5 * evsq(kdex) * h) + z / h) minus_erfc = sp_spcl.erfc((0.5 * evsq(kdex) * h) - z / h) # Exponent values pos_exp = np.exp(evsq(kdex) * z) neg_exp = np.exp(-evsq(kdex) * z) Bfeild_z = (Bfeild_z + (coefficient * (plus_erfc * pos_exp + minus_erfc * neg_exp))) return Bfeild_z + uniform_B0
def hourglass_magnetic_field_cyln( r, phi, z, # Cylindrical cords h, k_array, disk_radius, uniform_B0): """ This function retruns the magnitude of an hourglass magnetic field in cylindrical cords given a location in cylindrical cords. """ # Type check r = valid.validate_float_array(r, greater_than=0) phi = valid.validate_float_array(phi, deep_validate=True, greater_than=0, less_than=2 * np.pi) z = valid.validate_float_array(z) h = valid.validate_float_value(h) k_array = valid.validate_float_array(k_array, deep_validate=True) disk_radius = valid.validate_float_value(disk_radius, greater_than=0) uniform_B0 = valid.validate_float_value(uniform_B0) # The phi component of the field is agnostic as the values of the magnetic # field is independent of phi, see equation 3 and 4 in Ewertiwski & Basu # 2013 B_r = Ewer_Basu__B_r(r, z, h, k_array, disk_radius) B_phi = 0 B_z = Ewer_Basu__B_z(r, z, h, k_array, disk_radius, uniform_B0) return B_r, B_phi, B_z
def fit_bessel_function_2nd(x_points, y_points, order_guess=None, order_bounds=None): """ This function returns the order of a Bessel function of the second kind that fits the data points according to a least squares fitting algorithm. Input: x_points = The x values of the points to fit. y_points = The y values of the points to fit. order_guess = A starting point for order guessing. order_bounds = The min and max values the order can be. Output: fit_order = The value of the order of the fit bessel function. """ # The total number of points, useful. n_datapoints = len(x_points) n_datapoints = valid.validate_int_value(n_datapoints, greater_than=0) if (n_datapoints <= 1): raise InputError('It does not make sense to fit one or less points.' ' --Kyubey') # Type check x_points = valid.validate_float_array(x_points, size=n_datapoints) y_points = valid.validate_float_array(y_points, size=n_datapoints) if (order_guess is not None): order_guess = valid.validate_float_value(order_guess) else: order_guess = 1 if (order_bounds is not None): order_bounds = valid.validate_float_array(order_bounds, shape=(2, ), size=2) else: order_bounds = (-np.inf, np.inf) # Function fitting, Scipy's module is likely the better method to go. fit_parameters = sp_opt.curve_fit(bessel_function_2nd, x_points, y_points, p0=order_guess, bounds=order_bounds) # Split the fitted order and covariance array. fit_order = float(fit_parameters[0]) covariance = float(fit_parameters[1]) return fit_order, covariance
def _detect_gaussians_and_mask(x_values, y_values, # Arbitrary fft_cutoff, prominence, prom_height_ratio, gauss_toler, *args, **kwargs): """ This function detects for possible locations of gaussians using arbitrary fft methods. After detected, they are masked. """ # Type check x_values = valid.validate_float_array(x_values) y_values = valid.validate_float_array(y_values) n_datapoints = len(x_values) # Sort sort_index = np.argsort(x_values) x_values = x_values[sort_index] y_values = y_values[sort_index] # Perform a fft and cut the first and last percents of values. y_fft = np.fft.fft(y_values) y_fft[int(n_datapoints*fft_cutoff):-int(n_datapoints*fft_cutoff)] = 0 # Revert the fft transform y_ifft = np.fft.ifft(y_fft) # Find and estimate x values of gaussian peaks. peak_index = sp_sig.find_peaks(y_ifft, prominence=prominence)[0] center_guesses = x_values[peak_index] # Determine Gaussian bounds using half peak width as a weak # approximation for FWHF peak_widths = sp_sig.peak_widths(np.abs(y_ifft), peaks=peak_index, rel_height=prom_height_ratio) peak_lower_bounds = np.array(np.floor(peak_widths[2]), dtype=int) peak_upper_bounds = np.array(np.ceil(peak_widths[3]), dtype=int) # Mask the entire set within the gaussian bounds. True passes. passed = np.ones(n_datapoints, dtype=bool) for lowerdex, upperdex in zip(peak_lower_bounds, peak_upper_bounds): passed[lowerdex:upperdex] = False # Also mask those less than some tolerance. passed[np.where(y_ifft < gauss_toler)] = False # Return only the valid values via the valid index. return x_values[np.where(passed)], y_values[np.where(passed)]
def generate_noisy_bessel_1st(order, x_domain, noise_domain, n_datapoints, distribution='uniform'): """ This function generates a noisy bessel function of the first kind given a real order. Input: order = The real order of the bessel function x_domain = The range of x_values that should be plotted. noise_domain = The domain the noise is allowed to spread around. n_datapoints = The number of data points that is desired. distribution = The method of noise distribution. Output: y_output = The values of the function after noise. """ # Type check. order = valid.validate_float_value(order) x_domain = valid.validate_float_array(x_domain, shape=(2, ), size=2) noise_domain = valid.validate_float_array(noise_domain, shape=(2, ), size=2) distribution = valid.validate_string(distribution) # Generate the input values. Make sure the first element is the lower # element. x_domain = np.sort(x_domain) x_input = np.random.uniform(x_domain[0], x_domain[-1], n_datapoints) # Generate values for the bessel function. y_output = bessel_function_1st(x_input, order) # Imbue the values with noise. y_output = misc.generate_noise(y_output, noise_domain, distribution=distribution) # Sort the values for ease of plotting and computation. sort_index = np.argsort(x_input) x_input = x_input[sort_index] y_output = y_output[sort_index] return np.array(x_input, dtype=float), np.array(y_output, dtype=float)
def cloud_line_integral(field_function, cloud_equation, view_line_point, box_width, view_line_deltas=(1, 0, 0), n_guesses=100): """ This function computes the total summation of the line integrals given a field function that a single sightline passes through, given the boundary that only the section of the line within a cloud would be computed as it is the upper and lower bounds for the integral(s). """ # Type check field_function = valid.validate_function_call(field_function, n_parameters=3) cloud_equation = valid.validate_function_call(cloud_equation, n_parameters=3) view_line_point = valid.validate_float_array(view_line_point, shape=(3, )) box_width = valid.validate_float_value(box_width, greater_than=0) view_line_deltas = valid.validate_tuple(view_line_deltas, length=3) n_guesses = valid.validate_int_value(n_guesses, greater_than=0) # Integrating function. Parameterize the field function to integrate over # the curve given by the sightline. # Define the sightline parametric equations. def x_param(t): return view_line_deltas[0] * t + view_line_point[0] def y_param(t): return view_line_deltas[1] * t + view_line_point[1] def z_param(t): return view_line_deltas[2] * t + view_line_point[2] # Assume that the user's function accepts x,y,z in that order. def parameterized_field_equation(t): return field_function(x_param(t), y_param(t), z_param(t)) # Determine the lower and upper bounds of the parameterized functional # integrations. lower_bounds,upper_bounds = \ line_integral_boundaries(view_line_point,cloud_equation,box_width, view_line_deltas,n_guesses) # The total integrated number. integrated_value = 0 error = [] # Error array for lowerdex, upperdex in zip(lower_bounds, upper_bounds): integration = sp.integrate.quad(parameterized_field_equation, lowerdex, upperdex) integrated_value += integration[0] error.append(integration[1]) # Errors add in quadrature. error = np.sqrt(np.dot(error, error)) return integrated_value, error
def _bessel_mask(x_values, y_values, order, bess_mask_toler, *args, **kwargs): """ This masks values from a bessel function. """ # Type check x_values = valid.validate_float_array(x_values) y_values = valid.validate_float_array(y_values) # Once there is the fitted order, mask all the bessel function values # from the points. bessel_x = copy.deepcopy(x_values) bessel_y = bessfit.bessel_function_1st(bessel_x, fitted_order) # Mask the entire set within the gaussian bounds. True passes. passed_index = np.where(np.abs(y_values - bessel_y) > bess_mask_toler) # Return only the valid values via the valid index. return x_values[passed_index], y_values[passed_index]
def generate_noisy_dual_dimension_gaussian(centers, std_devs, height, n_datapoints, x_domain, y_domain, noise_domain, dimensions=2): """ This generates a noisy 2D gaussian. """ # Type check dimensions = valid.validate_int_value(dimensions, greater_than=0) centers = valid.validate_float_array(centers, shape=(dimensions, ), size=dimensions) std_devs = valid.validate_float_array(std_devs, shape=(dimensions, ), size=dimensions, deep_validate=True, greater_than=0) height = valid.validate_float_value(height) n_datapoints = valid.validate_int_value(n_datapoints, greater_than=0) x_domain = valid.validate_float_array(x_domain, shape=(2, ), size=2) y_domain = valid.validate_float_array(y_domain, shape=(2, ), size=2) noise_domain = valid.validate_float_array(noise_domain, shape=(2, ), size=2) # Generate the 2D gaussian. points = generate_dual_dimension_gaussian(centers, std_devs, height, n_datapoints, x_domain, y_domain) # Imbue the z points (2 index) with noise. points[2] = misc.generate_noise(points[2], noise_domain) return points
def generate_dual_dimension_gaussian(centers, std_devs, height, n_datapoints, x_domain, y_domain, dimensions=2): """ This generates random points for a 2D dimensional gaussian. """ # Type check dimensions = valid.validate_int_value(dimensions, greater_than=0) centers = valid.validate_float_array(centers, shape=(dimensions, ), size=dimensions) std_devs = valid.validate_float_array(std_devs, shape=(dimensions, ), size=dimensions, deep_validate=True, greater_than=0) height = valid.validate_float_value(height) n_datapoints = valid.validate_int_value(n_datapoints, greater_than=0) x_domain = valid.validate_float_array(x_domain, shape=(2, ), size=2) y_domain = valid.validate_float_array(y_domain, shape=(2, ), size=2) # Generate x and y points at random. x_values = np.random.uniform(x_domain[0], x_domain[-1], size=n_datapoints) y_values = np.random.uniform(y_domain[0], y_domain[-1], size=n_datapoints) # Compile into a parallel pair of (x,y) input_points = np.append([x_values], [y_values], axis=0) # Compute the z_values, desire only the output points. z_values, output_points = dual_dimensional_gauss_equation( input_points, centers, std_devs, height, dimensions) return output_points
def hourglass_magnetic_field_cart( x, y, z, # Cartesian cords h, k_array, disk_radius, uniform_B0): """ This function retruns the magnitude of an hourglass magnetic field in cartesian cords given a location in cartesian cords. """ # Type check x = valid.validate_float_array(x) y = valid.validate_float_array(y) z = valid.validate_float_array(z) h = valid.validate_float_value(h) k_array = valid.validate_float_array(k_array, deep_validate=True) disk_radius = valid.validate_float_value(disk_radius, greater_than=0) uniform_B0 = valid.validate_float_value(uniform_B0) # Convert to cylindrical cords. r = np.hypot(x, y) phi = np.arctan2(y, x) z = z # Find the values of the magnetic field. B_r, B_phi, B_z = \ hourglass_magnetic_field_cyln(r, phi, z, h, k_array, disk_radius, uniform_B0) # Convert to cartesian. B_x = B_r * np.cos(phi) B_y = B_r * np.sin(phi) B_z = B_z # Return cartesian return B_x, B_y, B_z
def Stokes_parameter_polarization_angle(Q, U): """ This function returns an angle of polarization in radians based on the values of two stoke parameters. The angle is signed. """ # Type check Q = valid.validate_float_array(Q) U = valid.validate_float_value(U) # Based off of Wikipedia and computational testing angle = 0.5 * np.arctan2(U, Q) return angle
def dual_dimensional_gauss_equation(input_points, center, std_dev, height, dimensions): """ This function generates gaussians of multiple dimensions/variables given the center's coordinates and the covariance matrix. """ try: n_datapoints = len(input_points[0]) except: input_points = valid.validate_float_array(input_points) n_datapoints = len(input_points[0]) # Validate, dimensions must go first. dimensions = valid.validate_int_value(dimensions, greater_than=0) input_points = valid.validate_float_array(input_points, shape=(2, n_datapoints)) center = valid.validate_float_array(center, shape=(dimensions, ), size=dimensions) std_dev = valid.validate_float_array(std_dev, shape=(dimensions, ), size=dimensions, deep_validate=True, greater_than=0) height = valid.validate_float_value(height) # For two dimensions. normalization_term = 1 / (2 * np.pi * std_dev[0] * std_dev[1]) exp_x_term = (input_points[0] - center[0])**2 / (2 * std_dev[0]**2) exp_y_term = (input_points[1] - center[1])**2 / (2 * std_dev[1]**2) z_points = normalization_term * np.exp(-(exp_x_term + exp_y_term)) + height output_points = np.append(input_points, np.array([z_points]), axis=0) return z_points, output_points
def gaussian_function(x_input, center, std_dev, height): """ Equation for a single gaussian. Input: x_input = x-values to be input into the gaussian function. """ # Type check. x_input = valid.validate_float_array(x_input) center = valid.validate_float_value(center) # Standard deviations can't be negative. std_dev = valid.validate_float_value(std_dev, greater_than=0) height = valid.validate_float_value(height) # Use the equation of a gaussian from Wikipedia: y_output = (((1 / (std_dev * np.sqrt(2 * np.pi))) * np.exp(-0.5 * ((x_input - center)/std_dev)**2)) + height) return np.array(y_output, dtype=float)
def generate_multigaussian(center_list, std_dev_list, height_list, x_domain, gaussian_count=None, n_datapoints=None): """ Generates a multigaussian arrangement of datapoints. """ # Assume the center list is the highest priority (but check the # std_dev) for double checking the gaussian count. if (gaussian_count is None): gaussian_count = len(center_list) # Double check with std_dev if ((gaussian_count != len(std_dev_list)) or (len(std_dev_list) != len(center_list))): raise InputError('The number of gaussians to generate is not ' 'known, nor can it be accurately derived from ' 'the inputs given. --Kyubey') # Type check gaussian_count = valid.validate_int_value(gaussian_count, greater_than=0) center_list = valid.validate_float_array(center_list, size=gaussian_count) std_dev_list = valid.validate_float_array(std_dev_list, size=gaussian_count, deep_validate=True, greater_than=0) height_list = valid.validate_float_array(height_list, size=gaussian_count) x_domain = valid.validate_float_array(x_domain, shape=(2,), size=2) n_datapoints = valid.validate_int_value(n_datapoints) # Initial parameters. x_values = np.random.uniform(x_domain[0], x_domain[-1], size=n_datapoints) y_values = [] # Compile the parameters into a concentric list for the usage of the # envelope function. parameters = [] for gaussiandex in range(gaussian_count): temp_parameter_dict = {'center': center_list[gaussiandex], 'std_dev': std_dev_list[gaussiandex], 'height': height_list[gaussiandex]} parameters.append(temp_parameter_dict) parameters = tuple(parameters) # Compile the list of functions for the concentric list. As this is multi- # gaussian fitting, it is expected to only be gaussian functions. functions = [] for gaussiandex in range(gaussian_count): functions.append(gaussian_function) functions = tuple(functions) # Execute the envelope function. y_values = misc.generate_function_envelope(x_values, functions, parameters) # Sort the values. sort_index = np.argsort(x_values) x_values = x_values[sort_index] y_values = y_values[sort_index] return np.array(x_values, dtype=float), np.array(y_values, dtype=float)
def fit_multigaussian(x_values, y_values, gaussian_count=None, window_len_ratio=0.1, sg_polyorder=3, prominence=0.10, *args, **kwargs): """ Fit a gaussian function with 3 degrees of freedom but with many gaussians. Input: x_values = the x-axial array of the values y_values = the y-axial array of the values gaussian_count = the number of expected gaussian functions fft_keep = the percentage kept by the fft truncation, use a lower fft_keep if there is a lot of noise prom_height_ratio = the ratio of prominence to height for width detection, a lower value increases accuracy until there are too little patterns. Returns center_array,std_dev_array,height_array,covariance_array center_array = the central value of the gaussian std_dev_array = the standard deviation of the gaussian height_array = the height of the gaussian function along the x-axis covariance_array = a convariance matrix of the fit """ # The total number of points, useful. try: n_datapoints = len(x_values) except: raise InputError('It does not make sense to try and fit a ' 'single point.' ' --Kyubey') else: n_datapoints = valid.validate_int_value(n_datapoints, greater_than=0) # Initial variables. center_array = [] std_dev_array = [] height_array = [] covariance_array = [] # Type check. x_values = valid.validate_float_array(x_values, size=n_datapoints) y_values = valid.validate_float_array(y_values, size=n_datapoints) if (gaussian_count is not None): # Gaussian count can't be less than 0. gaussian_count = valid.validate_int_value( gaussian_count, greater_than=0) window_len_ratio = valid.validate_float_value(window_len_ratio) sg_polyorder = valid.validate_int_value(sg_polyorder) prominence = valid.validate_float_value(prominence) # Implement the Savitzky-Golay filtering algorithm. # Window width needs to be an odd interger by Scipy and algorithm # stipulation. window_width = int(window_len_ratio * n_datapoints) if (window_width % 2 == 0): # It is even, make odd. window_width += 1 elif (window_width % 2 == 1): # It is odd, it should be good. pass filtered_y_values = sp_sig.savgol_filter(y_values, window_width, sg_polyorder) # Detect possible peaks of Gaussian functions. peak_index, peak_properties = \ sp_sig.find_peaks(filtered_y_values, prominence=prominence) left_bases = peak_properties['left_bases'] right_bases = peak_properties['right_bases'] # Attempt to fit a gaussian curve between the ranges of each peak. for peakdex, left_basedex, right_basedex in \ zip(peak_index, left_bases, right_bases): # Separate each of the gaussians and try to find parameters. center, std_dev, height, covariance = \ fit_gaussian(x_values[left_basedex:right_basedex], y_values[left_basedex:right_basedex], center_guess=x_values[peakdex]) # Append the values to the arrays of information. center_array.append(center) std_dev_array.append(std_dev) height_array.append(height) covariance_array.append(covariance) # Type check before returning, just in case. center_array = valid.validate_float_array(center_array) std_dev_array = valid.validate_float_array(std_dev_array) height_array = valid.validate_float_array(height_array) covariance_array = valid.validate_float_array(covariance_array) return center_array, std_dev_array, height_array, covariance_array
def fit_bessel_function_1st_integer(x_values, y_values, max_order=1000, negative_order=False): """ This is the main bessel fitting algorithm. It can fit, and then extract, the order of the bessel function according to the best fit of the data points. However, it can only do integer orders, and will not attempt float orders or complex orders. Input: x_values = Input x values y_values = Input y values max_order = The absolute value of the max order that is to be checked. negative_order = If the user prefers the negative order if the positive and negative order is the same. Output: fit_order = The primary positive fit order """ # The total number of points, useful. n_datapoints = len(x_values) n_datapoints = valid.validate_int_value(n_datapoints, greater_than=0) if (n_datapoints <= 1): raise InputError('It does not make sense to fit one or less points.' ' --Kyubey') # Type check x_values = valid.validate_float_array(x_values, size=n_datapoints) y_values = valid.validate_float_array(y_values, size=n_datapoints) negative_order = valid.validate_boolean_value(negative_order) # Because an order of 0 is a thing. searchable_orders = range(max_order + 1) # Generate list of output values, given inputs and searching through all # given possible orders. output_ranges = [] for orderdex in searchable_orders: output_ranges.append(bessel_function_1st(x_values, orderdex)) # Copy the positive domain and transpose to the negative order domain via # the relationship of non-linearity of Bessel functions of the the first # kind of interger order. positive_order_outputs = np.array(output_ranges) negative_order_outputs = np.array(output_ranges) for orderdex, output_array in enumerate(negative_order_outputs): negative_order_outputs[orderdex] *= (-1)**orderdex # Use the least squares fitting algorithm. # Define square residuals for both positive and negative. positive_order_sq_residuals = \ np.sum((y_values - positive_order_outputs[:])**2, axis=1) negative_order_sq_residuals = \ np.sum((y_values - negative_order_outputs[:])**2, axis=1) # Find the lowest square residual of both the positive and negative values. positive_order_fit = np.argmin(positive_order_sq_residuals) negative_order_fit = np.argmin(negative_order_sq_residuals) # It might be the case that the values of the negative and the positive # order is the same via the non-linearity relationship, if so, send the # positive value if the negative order is not wanted. if (positive_order_fit == negative_order_fit): if (negative_order): return int(negative_order_fit) else: return int(positive_order_fit) elif (positive_order_sq_residuals[positive_order_fit] < negative_order_sq_residuals[negative_order_fit]): return int(positive_order_fit) elif (positive_order_sq_residuals[positive_order_fit] > negative_order_sq_residuals[negative_order_fit]): return int(negative_order_fit) else: raise OutputError('There seems to be something wrong, it is not known' 'what is wrong.' ' --Kyubey')
def generate_noisy_multigaussian(center_list, std_dev_list, height_list, noise_domain_list, x_domain, n_datapoints, gaussian_count=None, cumulative_noise=False): """ Generate multiple gaussians with some aspect of noise within one dataset. Input: center_list = list of central x values std_dev_list = list of standard deviations of the functions height_list = list of heights (y-off set) of the functions noise_domain_list = list of uniform random distribution of noise from perfect gauss function x_domain_list = absolute domains of the gaussian functions n_datapoints = total number of datapoints n_datapoints_list = list of number of datapoints (overrides n_datapoints) gaussian_count = the number of gaussian functions to be made cumulative_noise = if each gaussian has noise (True), or just the entire set (False). Output: x_values,y_values x_values = the x-axial array of the gaussian function within the domain y_values = the y-axial array of the gaussian function within the domain """ # Assume the center list is the highest priority (but check the # std_dev) for double checking the gaussian count. if (gaussian_count is None): gaussian_count = len(center_list) # Double check with std_dev if ((gaussian_count != len(std_dev_list)) or (len(std_dev_list) != len(center_list))): raise InputError('The number of gaussians to generate is not ' 'known, nor can it be accurately derived from ' 'the inputs given. --Kyubey') # Type check. center_list = valid.validate_float_array(center_list, size=gaussian_count) std_dev_list = valid.validate_float_array( std_dev_list, size=gaussian_count) height_list = valid.validate_float_array(height_list, size=gaussian_count) noise_domain_list = valid.validate_float_array(noise_domain_list, shape=(gaussian_count, 2)) x_domain = valid.validate_float_array(x_domain, shape=(2,), size=2) cumulative_noise = valid.validate_boolean_value(cumulative_noise) n_datapoints = valid.validate_int_value(n_datapoints, greater_than=0) # Type check optional elements gaussian_count = valid.validate_int_value(gaussian_count, greater_than=0) cumulative_noise = valid.validate_boolean_value(cumulative_noise) # Initialize initial variables. x_values = [] y_values = [] # Check how to distribute noise. if (cumulative_noise): # Each gaussian must be generated on its own. for gaussiandex in range(gaussian_count): # Generate gaussian. temp_x_values, temp_y_values = \ generate_gaussian(center_list[gaussiandex], std_dev_list[gaussiandex], height_list[gaussiandex], x_domain, np.ceil(n_datapoints / gaussian_count)) temp_y_values = misc.generate_noise(temp_y_values, noise_domain_list[gaussiandex], distribution='uniform') # Store for return x_values = np.append([x_values], [temp_x_values], axis=0) y_values = np.append([y_values], [temp_y_values], axis=0) # Maximize the values, discarding everything lower than. x_values = np.amax(x_values, axis=0) y_values = np.amax(y_values, axis=0) else: # Generate noise of every point after gaussian generation. # Generate gaussian x_values, y_values = generate_multigaussian(center_list, std_dev_list, height_list, x_domain, gaussian_count, np.ceil(n_datapoints / gaussian_count)) # Generate noise. Warn the user that only the first noise domain is # being used. kyubey_warning(OutputWarning, ('Only the first element of the ' 'noise_domian_list is used if ' 'cumulative_noise is False.' ' --Kyubey')) y_values = misc.generate_noise(y_values, noise_domain_list[0], distribution='uniform') return np.array(x_values, dtype=float), np.array(y_values, dtype=float)
def generate_noise( input_array, noise_domain, distribution='uniform', center=None, std_dev=None, # Normal distribution terms. debug=False): """ Takes a set of 'perfect' datapoints and scatters them based on some randomly generated noise. The generated noise can be distributed in a number of ways. Input: input_array = array of datapoints to be scattered from the original value noise_domain = (2,) shaped array of the upper and lower bounds of scattering distribution = method of random number distribution - 'uniform' - 'gaussian' debug = Debug mode Output: output_array = scattered 'noisy' datapoints """ # Type check. input_array = valid.validate_float_array(input_array, size=len(input_array)) noise_domain = valid.validate_float_array(noise_domain, shape=(2, ), size=2) distribution = valid.validate_string(distribution) # Initial conditions n_datapoints = len(input_array) # Ensure the lower bound of the noise domain is the first element. if (noise_domain[0] < noise_domain[-1]): # This is correct behavior. pass elif (noise_domain[0] > noise_domain[-1]): # Warn and change, the array seems to be reversed. noise_domain = np.flip(noise_domain, axis=0) elif (noise_domain[0] == noise_domain[-1]): raise ValueError('Noise domain range is detected to be zero. There is ' 'no functional use of this function. --Kyubey') # Check for distribution method, generate noise array from method. if (distribution == 'uniform'): if (debug): print('Noise distribution set to "uniform".') noise_array = np.random.uniform(noise_domain[0], noise_domain[1], size=n_datapoints) elif ((distribution == 'gaussian') or (distribution == 'normal')): if (debug): print('Noise distribution set to "gaussian".') kyubey_warning(OutputWarning, ('Noise domain is ignored under ' 'gaussian distribution. --Kyubey')) # Type check center and standard deviation. if (std_dev is None): raise InputError('Noise distribution is set to gaussian, there is ' 'no standard deviation input.') else: # Standard deviation cannot be negative center = valid.validate_float_value(center) std_dev = valid.validate_float_value(std_dev, greater_than=0) noise_array = np.random.normal(center, std_dev, size=n_datapoints) # Noise array plus standard values. return input_array + noise_array
def generate_function_envelope(x_values, functions, parameters): """ Generate a function (x,y points) based on the maximum value of a list of functions, given their parameters. This creates an envelope function around the list of functions. Input: x_values = x input values functions = list of functions to be used, the first entry of each function is assumed to be the main input value of the function. parameters = list of tuples or dictionaries of the parameters to be used, parallel array to functions, it must lineup with the function definition or be a dictionary of inputs. Output: y_values = y output values """ # Initial values. y_values = [] # Type check, the only initial type checking that can be done is for # x_values. x_values = valid.validate_float_array(x_values) parameters = list(parameters) # Number of functions. total_functions = len(functions) # Test if the parameters array is parallel. if (len(parameters) != total_functions): raise InputError('The number of parameter lists is not equal to ' 'the total number of functions. ' 'Expected: {expt} Actual: {act} ' ' --Kyubey'.format(expt=total_functions, act=len(parameters))) # Obtain values of each y_output per function. for functiondex in range(total_functions): # Attempt to get a function signature. try: function_signature = inspect.signature(functions[functiondex]) except Exception: raise InputError('Cannot get function signature from function ' 'number {funt_num}. Ensure the input is correct.' ' --Kyubey'.format(funt_num=functiondex + 1)) # Obtain number of arguments and paramerers for this function. Assume # the first is the main input. n_arguments = len(function_signature.parameters) - 1 n_parameters = len(parameters[functiondex]) # Check that the current list of parameters is of correct size. if (n_parameters != n_arguments): raise InputError('Not enough parameters for function {funt_num}.' 'Expected: {expt} Actual: {act}.' ' --Kyubey'.format(expt=n_arguments, act=n_parameters)) # Check if the user provided a dictionary or parallel tuples assume # that it is. is_dictionary_parameters = True if isinstance(parameters[functiondex], dict): is_dictionary_parameters = True # Get the name of the first (assumped to be x-inputs) term of the # function. x_input_name = list(function_signature.parameters.keys())[0] # Create a dictionary entry and insert at the beginning of the list. x_input_dict = {str(x_input_name): x_values} # For backwards compatability: try: parameters[functiondex] = copy.deepcopy({ **x_input_dict, **parameters[functiondex] }) except Exception: parameters[functiondex] = \ merge_two_dicts(x_input_dict, parameters[functiondex]) elif isinstance(parameters[functiondex], (list, tuple)): is_dictionary_parameters = False # Input the first element, the x-values, just as the first element. parameters[functiondex] = list(parameters[functiondex]) parameters[functiondex] = (x_values, ) + parameters[functiondex] else: # Try and adapt the input into one of the two accepted types. try: parameters[functiondex] = dict(parameters[functiondex]) except TypeError: try: parameters[functiondex] = list(parameters[functiondex]) except Exception: raise TypeError( 'The parameters for function {funt_num} ' 'is not and cannot be turned into the ' 'accepted input types.' ' --Kyubey'.format(funt_num=functiondex + 1)) else: raise InputError('The parameter input for function {funt_num} ' 'is unworkable. Please enter it as a ' 'dictionary or tuple of parameters.' ' --Kyubey'.format(funt_num=functiondex + 1)) # Begin execution of the function. Expect the raising of errors. try: if (is_dictionary_parameters): # Output the function given the parameters of the same index. # Use argument slicing based on dictionaries. y_values.append( functions[functiondex](**parameters[functiondex])) else: # Output the function given the parameters of the same index. # Use argument slicing based on aligned tuples or lists. y_values.append( functions[functiondex](*parameters[functiondex])) except Exception: print('Error occurred on function {funt_num} ' '( functiondex = {functdex}.' ' --Kyubey'.format(funt_num=functiondex + 1, functdex=functiondex)) # Re-raise the error. raise # Extract only the highest values of y_points. y_values = np.amax(y_values, axis=0) return np.array(y_values, dtype=float)
def line_integral_boundaries(view_line_point, cloud_equation, box_width, view_line_deltas=(1, 0, 0), n_guesses=100): """ This function determines the points that intersect the sphere, starting with it entering and exit. It returns the ranges of points that would yield line integral boundaries that integrate within the cloud volume. By default, the cloud equation should be a function such that it returns a float, f(x,y,z), based on implicit shape making: f(x,y,z) = 0. If not, it should be at least a string that contains the python syntax expression of the shape for f(x,y,z) = 0, i.e., left-hand side of the equation only. """ # Type check. view_line_point = valid.validate_float_array(view_line_point, shape=(3, )) # Check for both cases. try: cloud_function = valid.validate_function_call(cloud_equation, n_parameters=3) except Exception: # Warn the user that sympy parsing is going to be used. # Try to use a sympy parsing. Assume a normal cartesian implicit # surface. variables = ('x', 'y', 'z') cloud_function = misc.user_equation_parse(cloud_equation, variables) box_width = valid.validate_float_value(box_width, greater_than=0) # Define the sightline parametric equations. def x_param(t): return view_line_deltas[0] * t + view_line_point[0] def y_param(t): return view_line_deltas[1] * t + view_line_point[1] def z_param(t): return view_line_deltas[2] * t + view_line_point[2] # Assume that the user's function accepts x,y,z in that order. def parameterized_cloud_equation(t): return cloud_function(x_param(t), y_param(t), z_param(t)) # Find all of the roots of the parameterized function. initial_guesses = np.linspace(-box_width, box_width, n_guesses) eq_roots = sp_opt.fsolve(parameterized_cloud_equation, initial_guesses, xtol=1e-10) sort_eq_roots = np.sort(eq_roots) # Have only unique roots. unique_index = (np.abs(sort_eq_roots[1:] - sort_eq_roots[:-1])) > 1e-8 neg_bound_roots = sort_eq_roots[:-1][unique_index] pos_bound_roots = sort_eq_roots[1:][unique_index] # There always exists an odd number of regions. The surface is closed and # has an even number of intersections by the sightline that passes in and # out of the surface as per topology. By default, the first and last groups # will not be within the cloud by the closed nature of the cloud. Assume # that the light goes from -x -> +x such that the yz plane is normal when # 'seen', thus the observer is near +x axis head. lower_bounds = neg_bound_roots[0::2] upper_bounds = pos_bound_roots[0::2] return lower_bounds, upper_bounds
def fit_dual_dimension_gaussian(points, center_cutoff_factor=0.05, height_cutoff_factor=0.42, strip_width=0.2): """ This function computes the values that describe a given 2D elliptical gaussian. """ try: n_datapoints = len(points[0]) except: points = valid.validate_float_array(points) n_datapoints = len(points[0]) # Type check, three dimensional points are expected. points = valid.validate_float_array(points, shape=(3, n_datapoints)) center_cutoff_factor = valid.validate_float_value(center_cutoff_factor, greater_than=0, less_than=1) height_cutoff_factor = valid.validate_float_value(height_cutoff_factor, greater_than=0, less_than=1) strip_width = valid.validate_float_value(strip_width, greater_than=0) # Sort based off of z-values for convince. sort_index = np.argsort(points[2]) points = points[:, sort_index] # Attempt to find the height of the gaussian. A weighted average of the # lowest z-points should be alright. The lower ceil(42%) is used just for # fun. The weight function is arbitrary, but used as it favors small values, # the points that might be considered at the bottom of the gaussian. height_cutoff = int(np.ceil(height_cutoff_factor * n_datapoints)) fit_height = np.average(points[2, :height_cutoff], weights=(1 / points[2, :height_cutoff]**2)) # Do a translation to "zero-out" the datapoints along the z-axis points[2] -= fit_height # Attempt to find the center of the gaussian through weighted averages over # both axis. The cut off is such that the very low valued points do not # over power the average and the weights through attrition. The value of # ceil(5%) is arbitrary. center_cutoff = int(np.ceil(center_cutoff_factor * n_datapoints)) x_center_fit = np.average(points[0, -center_cutoff:], weights=points[2, -center_cutoff:]**2) y_center_fit = np.average(points[1, -center_cutoff:], weights=points[2, -center_cutoff:]**2) fit_center = np.array([x_center_fit, y_center_fit], dtype=float) # Do a translation to center the datapoints along the xy-plane. points[0] -= x_center_fit points[1] -= y_center_fit # Determine the standard deviation. The normal fitting gaussian function # does not work as well because of the built in normalization factor. def subgauss(x_input, std_dev, amp, height): """ This is a modified superset of gaussians. """ # The centers should have already been detected and shifted. A priori # value center = 0 # amp being the amplitude return ((amp * np.exp(-0.5 * ((x_input - center) / std_dev)**2)) + height) # Extract a strip of values along the x and y axes. x_valid_points = np.where(np.abs(points[1]) <= strip_width / 2.0) x_strip_points = points[:, x_valid_points[0]] y_valid_points = np.where(np.abs(points[0]) <= strip_width / 2.0) y_strip_points = points[:, y_valid_points[0]] x_gauss_ans = sp_opt.curve_fit(subgauss, x_strip_points[0], x_strip_points[2]) y_gauss_ans = sp_opt.curve_fit(subgauss, y_strip_points[1], y_strip_points[2]) # The only value desired is the standard deviation. x_std_dev = float(x_gauss_ans[0][0]) y_std_dev = float(y_gauss_ans[0][0]) fit_std_dev = np.array([x_std_dev, y_std_dev], dtype=float) # Package all of the obtained values. And return. return fit_center, fit_std_dev, fit_height
def fit_gaussian(x_values, y_values, center_guess=None, std_dev_guess=None, height_guess=None, center_bounds=None, std_dev_bounds=None, height_bounds=None): """ Fit a gaussian function with 3 degrees of freedom. Input: x_values = the x-axial array of the values y_values = the y-axial array of the values center_guess = a starting point for the center std_dev_guess = a starting point for the std_dev height_guess = a starting point for the height Returns center,std_dev,height,covariance center = the central value of the gaussian std_dev = the standard deviation of the gaussian height = the height of the gaussian function along the x-axis covariance = a convariance matrix of the fit """ # The total number of points, useful. try: n_datapoints = len(x_values) except: raise InputError('It does not make sense to try and fit a ' 'single point.' ' --Kyubey') else: n_datapoints = valid.validate_int_value(n_datapoints, greater_than=0) # Type check x_values = valid.validate_float_array(x_values) y_values = valid.validate_float_array(y_values) # Type check optional issues. # Type check the guesses if (center_guess is not None): center_guess = valid.validate_float_value(center_guess) else: # The default of scipy's curve fit. center_guess = 1 if (std_dev_guess is not None): std_dev_guess = valid.validate_float_value( std_dev_guess, greater_than=0) else: # The default of scipy's curve fit. std_dev_guess = 1 if (height_guess is not None): height_guess = valid.validate_float_value(height_guess) else: # The default of scipy's curve fit. height_guess = 1 # Type check bounds. if (center_bounds is not None): center_bounds = valid.validate_float_array(center_bounds, size=2) center_bounds = np.sort(center_bounds) else: center_bounds = np.array([-np.inf, np.inf]) if (std_dev_bounds is not None): std_dev_bounds = valid.validate_float_array(std_dev_bounds, size=2, deep_validate=True, greater_than=0) std_dev_bounds = np.sort(std_dev_bounds) else: std_dev_bounds = np.array([0, np.inf]) if (height_bounds is not None): height_bounds = valid.validate_float_array(height_bounds) height_bounds = np.sort(height_bounds) else: height_bounds = np.array([-np.inf, np.inf]) # Compiling the guesses. guesses = np.array([center_guess, std_dev_guess, height_guess]) # Compiling the bounds lower_bounds = (center_bounds[0], std_dev_bounds[0], height_bounds[0]) upper_bounds = (center_bounds[1], std_dev_bounds[1], height_bounds[1]) bounds = (lower_bounds, upper_bounds) # Use scipy's curve optimization function for the gaussian function. fit_parameters, covariance = sp_opt.curve_fit(gaussian_function, x_values, y_values, p0=guesses, bounds=bounds) # For ease. center = fit_parameters[0] std_dev = fit_parameters[1] height = fit_parameters[2] return center, std_dev, height, covariance
def dual_dimensional_gauss_equation_rot(input_points, centers, std_devs, height, theta, dimensions): """ This is the general gaussian equation for a rotatable gaussian for some angle theta (in radians). """ try: n_datapoints = len(input_points[0]) except: input_points = valid.validate_float_array(input_points) n_datapoints = len(input_points[0]) # Validate, dimensions must go first. dimensions = valid.validate_int_value(dimensions, greater_than=0) input_points = valid.validate_float_array(input_points, shape=(2, n_datapoints)) centers = valid.validate_float_array(centers, shape=(dimensions, ), size=dimensions) std_devs = valid.validate_float_array(std_devs, shape=(dimensions, ), size=dimensions, deep_validate=True, greater_than=0) height = valid.validate_float_value(height) # Adapt for over/under rotation of theta. try: theta = valid.validate_float_value(theta, greater_than=0, less_than=2 * np.pi) except ValueError: # A loop is to be done. Have an insurance policy. loopbreak = 0 while ((theta < 0) or (theta > 2 * np.pi)): if (theta < 0): theta += 2 * np.pi elif (theta > 0): theta = theta % (2 * np.pi) # Ensure that the loop does not get stuck in the event of # unpredicted behavior. loopbreak += 1 if (loopbreak > 100): raise InputError('The value of theta cannot be ' 'nicely confined to 0 <= θ <= 2π ' ' --Kyubey') # Following Wikipedia's parameter definitions. a = ((np.cos(theta)**2 / (2 * std_devs[0]**2)) + (np.sin(theta)**2 / (2 * std_devs[1]**2))) b = (-(np.sin(2 * theta) / (4 * std_devs[0]**2)) + (np.sin(2 * theta) / (4 * std_devs[1]**2))) c = ((np.sin(theta)**2 / (2 * std_devs[0]**2)) + (np.cos(theta)**2 / (2 * std_devs[1]**2))) # Amplitude or normalization normalization_term = 1 / (2 * np.pi * std_devs[0] * std_devs[1]) # General equation z_values = (normalization_term * np.exp(-(a * (input_points[0] - centers[0])**2 + (2 * b * ((input_points[0] - centers[0]) * (input_points[1] - centers[1]))) + (c * (input_points[1] - centers[1])**2)))) # Return values. output_points = np.append(input_points, np.array([z_values]), axis=0) return z_values, output_points
def gaussian_bessel_fit(x_values, y_values, # Arbitrary values. arbitrary={'fft_cutoff': 0.01, 'prominence': 0.25, 'prom_height_ratio': 0.25, 'gauss_toler': 0.1, 'bess_mask_toler': 0.01}): """ This function detects and fits multiple gaussian functions on a bessel function. """ # Type check x_values = valid.validate_float_array(x_values) y_values = valid.validate_float_array(y_values) # Detect potential gaussian locations. def _detect_gaussians_and_mask(x_values, y_values, # Arbitrary fft_cutoff, prominence, prom_height_ratio, gauss_toler, *args, **kwargs): """ This function detects for possible locations of gaussians using arbitrary fft methods. After detected, they are masked. """ # Type check x_values = valid.validate_float_array(x_values) y_values = valid.validate_float_array(y_values) n_datapoints = len(x_values) # Sort sort_index = np.argsort(x_values) x_values = x_values[sort_index] y_values = y_values[sort_index] # Perform a fft and cut the first and last percents of values. y_fft = np.fft.fft(y_values) y_fft[int(n_datapoints*fft_cutoff):-int(n_datapoints*fft_cutoff)] = 0 # Revert the fft transform y_ifft = np.fft.ifft(y_fft) # Find and estimate x values of gaussian peaks. peak_index = sp_sig.find_peaks(y_ifft, prominence=prominence)[0] center_guesses = x_values[peak_index] # Determine Gaussian bounds using half peak width as a weak # approximation for FWHF peak_widths = sp_sig.peak_widths(np.abs(y_ifft), peaks=peak_index, rel_height=prom_height_ratio) peak_lower_bounds = np.array(np.floor(peak_widths[2]), dtype=int) peak_upper_bounds = np.array(np.ceil(peak_widths[3]), dtype=int) # Mask the entire set within the gaussian bounds. True passes. passed = np.ones(n_datapoints, dtype=bool) for lowerdex, upperdex in zip(peak_lower_bounds, peak_upper_bounds): passed[lowerdex:upperdex] = False # Also mask those less than some tolerance. passed[np.where(y_ifft < gauss_toler)] = False # Return only the valid values via the valid index. return x_values[np.where(passed)], y_values[np.where(passed)] # Get Bessel only data to attempt to fit a Bessel function to. clear_bessel_x, clear_bessel_y = \ _detect_gaussians_and_mask(x_values=x_values, y_values=y_values, **arbitrary) fitted_order = bessfit.fit_bessel_function_1st_integer(clear_bessel_x, clear_bessel_y) def _bessel_mask(x_values, y_values, order, bess_mask_toler, *args, **kwargs): """ This masks values from a bessel function. """ # Type check x_values = valid.validate_float_array(x_values) y_values = valid.validate_float_array(y_values) # Once there is the fitted order, mask all the bessel function values # from the points. bessel_x = copy.deepcopy(x_values) bessel_y = bessfit.bessel_function_1st(bessel_x, fitted_order) # Mask the entire set within the gaussian bounds. True passes. passed_index = np.where(np.abs(y_values - bessel_y) > bess_mask_toler) # Return only the valid values via the valid index. return x_values[passed_index], y_values[passed_index] # Get gaussian only data. clear_gauss_x, clear_gauss_y = _bessel_mask(x_values=x_values, y_values=y_values, order=fitted_order, **arbitrary) # Attempt to fit gaussians center_array, std_dev_array, height_array, covariance = \ gaussfit.fit_multigaussian(clear_gauss_x, clear_gauss_y) # It should be the case that it is all. Return order first. return fitted_order, center_array, std_dev_array, height_array, covariance