Пример #1
0
def subdivision_solve_1d(f,a,b,deg,interval_data,root_tracker,tols,max_level,level=0):
    """Finds the roots of a one-dimensional function using subdivision and chebyshev approximation.

    Parameters
    ----------
    f : function from R -> R
        The function to interpolate.
    a : numpy array
        The lower bound on the interval.
    b : numpy array
        The upper bound on the interval.
    deg : int
        The degree of the approximation.
    interval_data : IntervalData
        A class to run the subinterval checks and keep track of the solve progress
    root_tracker : RootTracker
        A class to keep track of the roots that are found.
    tols : Tolerances
        The tolerances to be used.
    max_level : int
        The maximum level for the recursion
    level : int
        The current level of the recursion.

    Returns
    -------
    coeffs : numpy array
        The coefficient of the chebyshev interpolating polynomial.
    """
    if level > max_level:
        # TODO Refine case where there may be a root and it goes too deep.
        interval_data.track_interval("Too Deep", [a, b])
        return

    RAND = 0.5139303900908738
    interval_data.print_progress()
    coeff, inf_norm = interval_approximate_1d(f,a,b,deg)
    coeff2, inf_norm = interval_approximate_1d(f,a,b,deg*2,inf_norm)
    coeff2[slice_top(coeff)] -= coeff
    error = np.sum(np.abs(coeff2))
    if error > tols.abs_approx_tol+tols.rel_approx_tol*inf_norm:
        #Subdivide the interval and recursively call the function.
        div_spot = a + (b-a)*RAND
        subdivision_solve_1d(f,a,div_spot,deg,interval_data,root_tracker,tols,max_level,level+1)
        subdivision_solve_1d(f,div_spot,b,deg,interval_data,root_tracker,tols,max_level,level+1)
    else:
        while error + abs(coeff[-1]) < tols.abs_approx_tol+tols.rel_approx_tol*inf_norm and len(coeff) > 5:
            error += abs(coeff[-1])
            coeff = coeff[:-1]
        try:
            good_zeros_tol = max(tols.min_good_zeros_tol, error*tols.good_zeros_factor)
            zeros = transform(good_zeros_1d(multCheb(coeff),good_zeros_tol,good_zeros_tol),a,b)
            interval_data.track_interval('Spectral', [a,b])
            root_tracker.add_roots(zeros, a, b, 'Spectral')
        except ConditioningError as e:
            div_spot = a + (b-a)*RAND
            subdivision_solve_1d(f,a,div_spot,deg,interval_data,root_tracker,tols,max_level,level+1)
            subdivision_solve_1d(f,div_spot,b,deg,interval_data,root_tracker,tols,max_level,level+1)
Пример #2
0
def full_cheb_approximate(f,
                          a,
                          b,
                          deg,
                          abs_approx_tol,
                          rel_approx_tol,
                          good_deg=None):
    """Gives the full chebyshev approximation and checks if it's good enough.

    Parameters
    ----------
    f : function
        The function we approximate.
    a : numpy array
        The lower bound on the interval.
    b : numpy array
        The upper bound on the interval.
    deg : int
        The degree to approximate with.
    rel_approx_tol : float or list
        The relative tolerance used in the approximation tolerance. The error is bouned by
        error < abs_approx_tol + rel_approx_tol * inf_norm_of_approximation
    abs_approx_tol : float or list
        The absolute tolerance used in the approximation tolerance. The error is bouned by
        error < abs_approx_tol + rel_approx_tol * inf_norm_of_approximation
    good_deg : numpy array
        Interpoation degree that is guaranteed to give an approximation valid
        to within approx_tol.

    Returns
    -------
    coeff : numpy array
        The coefficient array of the interpolation. If it can't get a good
        approximation and needs to subdivide, returns None.
    inf_norm : float
        The inf norm of f on [a, b]
    error : float
        The approximation error
    """
    # We don't know what degree we want
    if good_deg is None:
        good_deg = deg
    # Try degree deg and see if it's good enough
    coeff = interval_approximate_nd(f, a, b, good_deg)
    coeff2, inf_norm = interval_approximate_nd(f,
                                               a,
                                               b,
                                               good_deg * 2,
                                               return_inf_norm=True)
    coeff2[slice_top(coeff.shape)] -= coeff

    error = np.sum(np.abs(coeff2))
    if error > abs_approx_tol + rel_approx_tol * inf_norm:
        return None, inf_norm, error
    else:
        return coeff, inf_norm, error
Пример #3
0
    def _mon_mult1(initial_matrix, idx, dim_mult):
        """
        Executes monomial multiplication in one dimension.

        Parameters
        ----------
        initial_matrix : array_like
            Matrix of coefficients that represent a Chebyshev polynomial.
        idx : tuple of ints
            The index of a monomial of one variable to multiply by initial_matrix.
        dim_mult : int
            The location of the non-zero value in idx.

        Returns
        -------
        ndarray
            Coeff that are the result of the one dimensial monomial multiplication.

        """

        p1 = np.zeros(initial_matrix.shape + idx)
        p1[slice_bottom(initial_matrix)] = initial_matrix

        largest_idx = [i - 1 for i in initial_matrix.shape]
        new_shape = [
            max(i, j)
            for i, j in itertools.zip_longest(largest_idx, idx, fillvalue=0)
        ]  #finds the largest length in each dimmension
        if initial_matrix.shape[dim_mult] <= idx[dim_mult]:
            add_a = [
                i - j for i, j in itertools.zip_longest(
                    new_shape, largest_idx, fillvalue=0)
            ]
            add_a_list = np.zeros((len(new_shape), 2))
            #changes the second column to the values of add_a and add_b.
            add_a_list[:, 1] = add_a
            #uses add_a_list and add_b_list to pad each polynomial appropriately.
            initial_matrix = np.pad(initial_matrix, add_a_list.astype(int),
                                    'constant')

        number_of_dim = initial_matrix.ndim
        shape_of_self = initial_matrix.shape

        #Loop iterates through each dimension of the polynomial and folds in that dimension
        for i in range(number_of_dim):
            if idx[i] != 0:
                initial_matrix = MultiCheb._fold_in_i_dir(
                    initial_matrix, number_of_dim, i, shape_of_self[i], idx[i])
        if p1.shape != initial_matrix.shape:
            idx = [i - j for i, j in zip(p1.shape, initial_matrix.shape)]

            result = np.zeros(np.array(initial_matrix.shape) + idx)
            result[slice_top(initial_matrix)] = initial_matrix
            initial_matrix = result
        Pf = p1 + initial_matrix
        return .5 * Pf
Пример #4
0
def create_matrix(poly_coeffs, degree, dim, divisor_var):
    ''' Builds a Macaulay matrix for reduction.

    Parameters
    ----------
    poly_coeffs : list.
        Contains numpy arrays that hold the coefficients of the polynomials to be put in the matrix.
    degree : int
        The degree of the Macaulay Matrix
    dim : int
        The dimension of the polynomials going into the matrix.
    divisor_var : int
        What variable is being divided by. 0 is x, 1 is y, etc. Defaults to x.

    Returns
    -------
    matrix : 2D numpy array
        The Macaulay matrix.
    matrix_terms : numpy array
        The ith row is the term represented by the ith column of the matrix.
    cuts : tuple
        When the matrix is reduced it is split into 3 parts with restricted pivoting. These numbers indicate
        where those cuts happen.
    '''
    bigShape = [degree + 1] * dim

    matrix_terms, cuts = get_matrix_terms(poly_coeffs, dim, divisor_var)

    #Get the slices needed to pull the matrix_terms from the coeff matrix.
    matrix_term_indexes = list()
    for row in matrix_terms.T:
        matrix_term_indexes.append(row)

    #Adds the poly_coeffs to flat_polys, using added_zeros to make sure every term is in there.
    added_zeros = np.zeros(bigShape)
    flat_polys = list()
    for coeff in poly_coeffs:
        slices = slice_top(coeff)
        added_zeros[slices] = coeff
        flat_polys.append(added_zeros[tuple(matrix_term_indexes)])
        added_zeros[slices] = np.zeros_like(coeff)
    del poly_coeffs

    #Make the matrix. Reshape is faster than stacking.
    matrix = np.reshape(flat_polys, (len(flat_polys), len(matrix_terms)))

    #Sorts the rows of the matrix so it is close to upper triangular.
    matrix = row_swap_matrix(matrix)
    return matrix, matrix_terms, cuts
Пример #5
0
def create_matrix(poly_coeffs, degree, dim, varsToRemove):
    ''' Builds a Macaulay matrix.

    Parameters
    ----------
    poly_coeffs : list.
        Contains numpy arrays that hold the coefficients of the polynomials to be put in the matrix.
    degree : int
        The degree of the Macaulay Matrix
    dim : int
        The dimension of the polynomials going into the matrix.
    varsToRemove : list
        The variables to remove from the basis because we have linear polysnomials
    Returns
    -------
    matrix : 2D numpy array
        The Macaulay matrix.
    matrix_terms : numpy array
        The ith row is the term represented by the ith column of the matrix.
    cut : int
        Number of monomials of highest degree
    '''
    bigShape = [degree + 1] * dim

    matrix_terms, cut = sorted_matrix_terms(degree, dim, varsToRemove)

    #Get the slices needed to pull the matrix_terms from the coeff matrix.
    matrix_term_indexes = list()
    for row in matrix_terms.T:
        matrix_term_indexes.append(row)

    #Adds the poly_coeffs to flat_polys, using added_zeros to make sure every term is in there.
    added_zeros = np.zeros(bigShape)
    flat_polys = list()
    for coeff in poly_coeffs:
        slices = slice_top(coeff)
        added_zeros[slices] = coeff
        flat_polys.append(added_zeros[tuple(matrix_term_indexes)])
        added_zeros[slices] = np.zeros_like(coeff)
    del poly_coeffs

    #Make the matrix. Reshape is faster than stacking.
    matrix = np.reshape(flat_polys, (len(flat_polys), len(matrix_terms)))

    #Sorts the rows of the matrix so it is close to upper triangular.
    matrix = row_swap_matrix(matrix)
    return matrix, matrix_terms, cut
Пример #6
0
def subdivision_solve_1d(f,
                         a,
                         b,
                         deg,
                         target_deg,
                         interval_data,
                         root_tracker,
                         tols,
                         max_level,
                         level=0,
                         method='svd',
                         trust_small_evals=False):
    """Finds the roots of a one-dimensional function using subdivision and
    chebyshev approximation.

    Parameters
    ----------
    f : function from R -> R
        The function to interpolate.
    a : numpy array
        The lower bound on the interval.
    b : numpy array
        The upper bound on the interval.
    deg : int
        The degree of the approximation.
    target_deg : int
        The degree to subdivide down to before building the Macauly matrix.
    interval_data : IntervalData
        A class to run the subinterval checks and keep track of the solve progress
    root_tracker : RootTracker
        A class to keep track of the roots that are found.
    tols : Tolerances
        The tolerances to be used.
    max_level : int
        The maximum level for the recursion
    level : int
        The current level of the recursion.

    Returns
    -------
    coeffs : numpy array
        The coefficient of the chebyshev interpolating polynomial.
    """
    if level > max_level:
        # TODO Refine case where there may be a root and it goes too deep.
        interval_data.track_interval("Too Deep", [a, b])
        return

    # Determine the point at which to subdivide the interval
    RAND = 0.5139303900908738
    interval_data.print_progress()

    # Approximate the function using Chebyshev polynomials
    coeff = interval_approximate_1d(f, a, b, deg)
    coeff2, sign_change, inf_norm = interval_approximate_1d(
        f, a, b, deg * 2, return_bools=True, return_inf_norm=True)

    coeff2[slice_top(coeff.shape)] -= coeff

    # Calculate the approximate error between the deg and 2*deg approximations
    error = np.sum(np.abs(coeff2))
    allowed_error = tols.abs_approx_tol + tols.rel_approx_tol * inf_norm

    if error > allowed_error:
        # Subdivide the interval and recursively call the function.
        div_spot = a + (b - a) * RAND
        good_deg = deg
        subdivision_solve_1d(f, a, div_spot, good_deg, target_deg,
                             interval_data, root_tracker, tols, max_level,
                             level + 1)
        subdivision_solve_1d(f, div_spot, b, good_deg, target_deg,
                             interval_data, root_tracker, tols, max_level,
                             level + 1)
    else:
        # Trim the coefficient array (reduce the degree) as much as we can.
        # This identifies a 'good degree' with which to approximate the function
        # if it is less than the given approx degree.
        last_coeff_size = abs(coeff[-1])
        new_error = error + last_coeff_size
        while new_error < allowed_error:
            if len(coeff) == 1:
                break
            #maybe a list pop here? idk if worth it to switch away from arrays
            coeff = coeff[:-1]
            last_coeff_size = abs(coeff[-1])
            error = new_error
            new_error = error + last_coeff_size
        if not trust_small_evals:
            error = max(error, macheps)
        good_deg = max(len(coeff) - 1, 1)

        # Run interval checks to eliminate regions
        if not sign_change:  # Skip checks if there is a sign change
            if interval_data.check_interval(coeff, error, a, b):
                return

        try:
            good_zeros_tol = max(tols.min_good_zeros_tol,
                                 error * tols.good_zeros_factor)
            zeros = transform(
                good_zeros_1d(multCheb(coeff), good_zeros_tol, good_zeros_tol),
                a, b)
            interval_data.track_interval("Macaulay", [a, b])
            root_tracker.add_roots(zeros, a, b, "Macaulay")
        except (ConditioningError, TooManyRoots) as e:
            div_spot = a + (b - a) * RAND
            subdivision_solve_1d(f, a, div_spot, good_deg, target_deg,
                                 interval_data, root_tracker, tols, max_level,
                                 level + 1)
            subdivision_solve_1d(f, div_spot, b, good_deg, target_deg,
                                 interval_data, root_tracker, tols, max_level,
                                 level + 1)
Пример #7
0
def full_cheb_approximate(f,
                          a,
                          b,
                          deg,
                          abs_approx_tol,
                          rel_approx_tol,
                          good_deg=None):
    """Gives the full chebyshev approximation and checks if it's good enough.

    Parameters
    ----------
    f : function
        The function we approximate.
    a : numpy array
        The lower bound on the interval.
    b : numpy array
        The upper bound on the interval.
    deg : int
        The degree to approximate with.
    rel_approx_tol : float or list
        The relative tolerance used in the approximation tolerance. The error is bouned by
        error < abs_approx_tol + rel_approx_tol * inf_norm_of_approximation
    abs_approx_tol : float or list
        The absolute tolerance used in the approximation tolerance. The error is bouned by
        error < abs_approx_tol + rel_approx_tol * inf_norm_of_approximation
    good_deg : numpy array
        Interpoation degree that is guaranteed to give an approximation valid to within approx_tol.

    Returns
    -------
    coeff : numpy array
        The coefficient array of the interpolation. If it can't get a good approximation and needs to subdivide, returns None.
    bools: numpy array
        (2^n, 1) array of bools corresponding to which subintervals the function changes sign in
        If it cannot get a good approximation, it returns which directions to subdivide in.
    inf_norm : float
        The inf norm of f on [a,b]
    error : float
        The approximation error
    """
    #We know what degree we want
    #     if good_deg is not None:
    #         coeff, bools, inf_norm = interval_approximate_nd(f,a,b,good_deg,return_bools=True)
    #         return coeff, bools, inf_norm
    #Try degree deg and see if it's good enough
    coeff, inf_norm = interval_approximate_nd(f, a, b, deg)
    coeff2, bools, inf_norm = interval_approximate_nd(f,
                                                      a,
                                                      b,
                                                      deg * 2,
                                                      return_bools=True,
                                                      inf_norm=inf_norm)
    #     print(coeff)
    #     print(coeff2)
    coeff2[slice_top(coeff)] -= coeff

    error = np.sum(np.abs(coeff2))
    if error > abs_approx_tol + rel_approx_tol * inf_norm:
        #Find the directions to subdivide
        dim = len(a)
        # TODO: Intelligent Subdivision.
        # div_dimensions = []
        # slices = [slice(0,None,None)]*dim
        # for d in range(dim):
        #     slices[d] = slice(deg+1,None,None)
        #     if np.sum(np.abs(coeff2[tuple(slices)])) > approx_tol/dim:
        #         div_dimensions.append(d)
        #     slices[d] = slice(0,None,None)
        # if len(div_dimensions) == 0:
        #     div_dimensions.append(0)
        # return None, np.array(div_dimensions)

        #for now, subdivide in every dimension
        return None, np.arange(dim), inf_norm, error
    else:
        return coeff, bools, inf_norm, error