示例#1
0
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
示例#2
0
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)
示例#7
0
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
示例#8
0
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
示例#10
0
    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)
示例#12
0
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
示例#13
0
    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
示例#16
0
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)
示例#26
0
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
示例#30
0
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