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)
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
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
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
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
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)
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