def test_add(): """Test Multivariate Chebyshev polynomial addition.""" t = np.arange(27).reshape((3, 3, 3)) poly1 = MultiCheb(t) poly2 = MultiCheb(np.ones((3, 3, 3))) S = poly1 + poly2 # the sum of the polynomials result = (S.coeff == (poly1.coeff + poly2.coeff)) assert result.all()
def test_evaluate_grid1(): poly = MultiCheb(np.array([[2, 0, 3], [0, -1, 0], [0, 1, 0]])) x = np.arange(3) xy = np.column_stack([x, x]) sol = np.polynomial.chebyshev.chebgrid2d(x, x, poly.coeff) assert (np.all(poly.evaluate_grid(xy) == sol))
def getPoly(deg, power): ''' A helper function for testing. Returns a random 1D polynomial of the given degree. power is a boolean indicating whether or not the polynomial should be MultiPower. ''' coeff = np.random.random_sample(deg + 1) if power: return MultiPower(coeff) else: return MultiCheb(coeff)
def test_evaluate(): cheb = MultiCheb(np.array([[0, 0, 0, 1], [0, 0, 0, 0], [0, 0, 1, 0]])) value = cheb((2, 5)) assert (value == 828) value = cheb((.25, .5)) assert (np.isclose(value, -.5625)) values = cheb([[.25, .5], [1.2, 2.2]]) print(values) assert (np.allclose(values, [-0.5625, 52.3104]))
def chebpolyqeps(Q, eps): dim = Q.shape[0] polys = [] for i in range(dim): coeff = np.zeros([3] * dim) spot = [0] * dim coeff[tuple(spot)] = .5 for j in range(dim): spot[j] = 1 coeff[tuple(spot)] = Q[i, j] * eps spot[j] = 0 spot[i] = 2 coeff[tuple(spot)] = .5 polys.append(MultiCheb(coeff)) return polys
def chebapprox(polys, a, b, deg, atol=1e-15, rtol=1e-15, ttol=1e-15): chebcoeff = [] inf_norms = [] errors = [] for poly in polys: coeff, _, inf_norm, error = full_cheb_approximate( poly, a, b, deg, atol, rtol) chebcoeff.append(coeff) inf_norms.append(inf_norm) errors.append(error) chebcoeff = trim_coeffs(chebcoeff, atol, rtol, ttol, inf_norms, errors)[0] chebpolys = [] for coeff in chebcoeff: chebpolys.append(MultiCheb(coeff)) return chebpolys
def test_cheb2poly(): np.random.seed(23) c1 = MultiCheb(np.array([[0, 0, 0], [0, 0, 0], [0, 1, 1]])) c2 = cheb2poly(c1) truth = np.array([[1, -1, -2], [0, 0, 0], [-2, 2, 4]]) assert np.allclose(truth, c2.coeff) #test2 c3 = MultiCheb(np.array([[3, 1, 4, 7], [8, 3, 1, 2], [0, 5, 6, 2]])) c4 = cheb2poly(c3) truth2 = np.array([[5, -19, -4, 20], [7, -3, 2, 8], [-12, -2, 24, 16]]) assert np.allclose(truth2, c4.coeff) c4_1 = poly2cheb(c4) assert np.allclose(c3.coeff, c4_1.coeff) #Test3 c5 = MultiCheb( np.array([[3, 1, 3, 4, 6], [2, 0, 0, 2, 0], [3, 1, 5, 1, 8]])) c6 = cheb2poly(c5) truth3 = np.array([[0, -9, 12, 12, -16], [2, -6, 0, 8, 0], [12, -4, -108, 8, 128]]) assert np.allclose(truth3, c6.coeff) #test4 - Random 1D matrix2 = np.random.randint(1, 100, np.random.randint(1, 10)) c7 = MultiCheb(matrix2) c8 = cheb2poly(c7) c9 = poly2cheb(c8) assert np.allclose(c7.coeff, c9.coeff) #Test5 - Random 2D shape = list() for i in range(2): shape.append(np.random.randint(2, 10)) matrix1 = np.random.randint(1, 50, (shape)) c10 = MultiCheb(matrix1) c11 = cheb2poly(c10) c12 = poly2cheb(c11) assert np.allclose(c10.coeff, c12.coeff) #Test6 - Random 4D shape = list() for i in range(4): shape.append(np.random.randint(2, 10)) matrix1 = np.random.randint(1, 50, (shape)) c13 = MultiCheb(matrix1) c14 = cheb2poly(c13) c15 = poly2cheb(c14) assert np.allclose(c13.coeff, c15.coeff)
def chebspolyqeps(Q, eps): dim = Q.shape[0] polys = [] const = (1.5 * np.eye(dim) - eps * Q).sum(axis=1) for i in range(dim): coeff = np.zeros([3] * dim) spot = [0] * dim coeff[tuple(spot)] = const[i] for j in range(dim): spot[j] = 1 coeff[tuple(spot)] = Q[i, j] * eps if i == j: coeff[tuple(spot)] -= 2 spot[j] = 0 spot[i] = 2 coeff[tuple(spot)] = .5 polys.append(MultiCheb(coeff)) return polys
def _random_poly(_type, dim): ''' Generates a random linear polynomial that has the form c_1x_1 + c_2x_2 + ... + c_nx_n where n = dim and each c_i is a randomly chosen integer between 0 and 1000. Parameters ---------- _type : string Type of Polynomial to generate. "MultiCheb" or "MultiPower". dim : int Degree of polynomial to generate (?). Returns ------- Polynomial Randomly generated Polynomial. ''' _vars = get_var_list(dim) random_poly_shape = [2 for i in range(dim)] # random_poly_coeff = np.zeros(tuple(random_poly_shape), dtype=int) # for var in _vars: # random_poly_coeff[var] = np.random.randint(1000) random_poly_coeff = np.zeros(tuple(random_poly_shape), dtype=float) #np.random.seed(42) coeffs = np.random.rand(dim) coeffs /= np.linalg.norm(coeffs) for i, var in enumerate(_vars): random_poly_coeff[var] = coeffs[i] if _type == 'MultiCheb': return MultiCheb(random_poly_coeff), _vars else: return MultiPower(random_poly_coeff), _vars
def test_paper_example(): #Power form of the polys p1 = MultiPower(np.array([[1, -4, 0], [0, 3, 0], [1, 0, 0]])) #y^2 + 3xy - 4x +1 p2 = MultiPower(np.array([[3, 0, -2], [6, -6, 0], [0, 0, 0]])) #-6xy -2x^2 + 6y +3 #Cheb form of the polys c1 = MultiCheb(np.array([[2, 0, -1], [6, -6, 0], [0, 0, 0]])) #p1 in Cheb form c2 = MultiCheb(np.array([[1.5, -4, 0], [0, 3, 0], [.5, 0, 0]])) #p2 in Cheb form right_number_of_roots = 4 #~ ~ ~ Power Form, Mx Matrix ~ ~ ~ power_mult_roots = pr.solve([p1, p2], MSmatrix=1) assert len(power_mult_roots) == right_number_of_roots for root in power_mult_roots: assert np.isclose(0, p1(root), atol=1.e-8) assert np.isclose(0, p2(root), atol=1.e-8) #~ ~ ~ Cheb Form, Mx Matrix ~ ~ ~ cheb_mult_roots = pr.solve([c1, c2], MSmatrix=1) assert len(cheb_mult_roots) == right_number_of_roots for root in cheb_mult_roots: assert np.isclose(0, c1(root), atol=1.e-8) assert np.isclose(0, c1(root), atol=1.e-8) #~ ~ ~ Power Form, My Matrix ~ ~ ~ power_multR_roots = pr.solve([p1, p2], MSmatrix=2) assert len(power_multR_roots) == right_number_of_roots for root in power_multR_roots: assert np.isclose(0, p1(root), atol=1.e-8) assert np.isclose(0, p2(root), atol=1.e-8) #~ ~ ~ Cheb Form, My Matrix ~ ~ ~ cheb_multR_roots = pr.solve([c1, c2], MSmatrix=2) assert len(cheb_multR_roots) == right_number_of_roots for root in cheb_multR_roots: assert np.isclose(0, c1(root), atol=1.e-8) assert np.isclose(0, c1(root), atol=1.e-8) #~ ~ ~ Power Form, Pseudorandom Multiplication Matrix ~ ~ ~ power_multrand_roots = pr.solve([p1, p2], MSmatrix=0) assert len(power_multrand_roots) == right_number_of_roots for root in power_multrand_roots: assert np.isclose(0, p1(root), atol=1.e-8) assert np.isclose(0, p2(root), atol=1.e-8) #~ ~ ~ Cheb Form, Pseudorandom Multiplication Matrix ~ ~ ~ cheb_multrand_roots = pr.solve([c1, c2], MSmatrix=0) assert len(cheb_multrand_roots) == right_number_of_roots for root in cheb_multrand_roots: assert np.isclose(0, c1(root), atol=1.e-8) assert np.isclose(0, c1(root), atol=1.e-8) #~ ~ ~ Power Form, Division Matrix ~ ~ ~ power_div_roots = pr.solve([p1, p2], MSmatrix=-1) assert len(power_div_roots) == right_number_of_roots for root in power_div_roots: assert np.isclose(0, p1(root), atol=1.e-8) assert np.isclose(0, p2(root), atol=1.e-8) #~ ~ ~ Cheb Form, Division Matrix ~ ~ ~ cheb_div_roots = pr.solve([c1, c2], MSmatrix=-1) assert len(cheb_div_roots) == right_number_of_roots for root in cheb_div_roots: assert np.isclose(0, c1(root), atol=1.e-8) assert np.isclose(0, c2(root), atol=1.e-8)
def test_mon_mult(): """ Tests monomial multiplication using normal polynomial multiplication. """ np.random.seed(4) #Simple 2D test cases cheb1 = MultiCheb(np.array([[0, 0, 0], [0, 0, 0], [0, 0, 1]])) mon1 = (1, 1) result1 = cheb1.mon_mult(mon1) truth1 = np.array([[0, 0, 0, 0], [0, 0.25, 0, 0.25], [0, 0, 0, 0], [0, 0.25, 0, 0.25]]) assert np.allclose(result1.coeff, truth1) #test with random matrices cheb2 = np.random.randint(-9, 9, (4, 4)) C1 = MultiCheb(cheb2) C2 = cheb2poly(C1) C3 = MultiCheb.mon_mult(C1, (1, 1)) C4 = MultiPower.mon_mult(C2, (1, 1)) C5 = poly2cheb(C4) assert np.allclose(C3.coeff, C5.coeff) # test results of chebyshev mult compared to power multiplication cheb3 = np.random.randn(5, 4) c1 = MultiCheb(cheb3) c2 = MultiCheb(np.ones((4, 2))) for index, i in np.ndenumerate(c2.coeff): if sum(index) == 0: c3 = c1.mon_mult(index) else: c3 = c3 + c1.mon_mult(index) p1 = cheb2poly(c1) p2 = cheb2poly(c2) p3 = p1 * p2 p4 = cheb2poly(c3) assert np.allclose(p3.coeff, p4.coeff) # test results of chebyshev mult compared to power multiplication in 3D cheb4 = np.random.randn(3, 3, 3) a1 = MultiCheb(cheb4) a2 = MultiCheb(np.ones((3, 3, 3))) for index, i in np.ndenumerate(a2.coeff): if sum(index) == 0: a3 = a1.mon_mult(index) else: a3 = a3 + a1.mon_mult(index) q1 = cheb2poly(a1) q2 = cheb2poly(a2) q3 = q1 * q2 q4 = cheb2poly(a3) assert np.allclose(q3.coeff, q4.coeff)
def subdivision_solve_nd(funcs, a, b, deg, target_deg, interval_data, root_tracker, tols, max_level, good_degs=None, level=0, method='svd', use_target_tol=False, trust_small_evals=False): """Finds the common zeros of the given functions. All the zeros will be stored in root_tracker. Parameters ---------- funcs : list Each element of the list is a callable function. 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 in the chebyshev approximation. target_deg : int The degree to subdivide down to before building the Macaulay 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 good_degs : numpy array Interpoation degrees that are guaranteed to give an approximation valid to within approx_tol. level : int The current level of the recursion. method : str (optional) The method to use when reducing the Macaulay matrix. Valid options are svd, tvb, and qrt. use_target_tol : bool Whether or not to use tols.target_tol when making approximations. This is necessary to get a sufficiently accurate approximation from which to build the Macaulay matrix and run the solver. """ 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 potential roots if the residuals are small root_tracker.add_potential_roots((a + b) / 2, a, b, "Too Deep.") return dim = len(a) if tols.check_eval_error: # Using the first abs_approx_tol if not use_target_tol: tols.abs_approx_tol = tols.abs_approx_tols[tols.currTol] if level % tols.check_eval_freq == 0: numSpots = (deg * 2)**len(a) - (deg)**len(a) for func in funcs: tols.abs_approx_tol = max( tols.abs_approx_tol, numSpots * get_abs_approx_tol(func, 3, a, b, dim)) # Using target_tol else: tols.target_tol = tols.target_tols[tols.currTol] if level % tols.check_eval_freq == 0: numSpots = (deg * 2)**len(a) - (deg)**len(a) for func in funcs: tols.target_tol = max( tols.target_tol, numSpots * get_abs_approx_tol(func, 3, a, b, dim)) # Buffer the interval to solve on a larger interval to account for # corners. Right now, it's set to be 5e-10 so that on [-1, 1], the # buffer goes out 1e-9 around the initial search interval. # DETERMINED BY EXPERIMENTATION interval_buffer_size = (b - a) * 5e-10 og_a = a.copy() og_b = b.copy() a -= interval_buffer_size b += interval_buffer_size cheb_approx_list = [] interval_data.print_progress() if good_degs is None: good_degs = [None] * len(funcs) inf_norms = [] approx_errors = [] # Get the chebyshev approximations num_funcs = len(funcs) for func_num, (func, good_deg) in enumerate(zip(funcs, good_degs)): if use_target_tol: coeff, inf_norm, approx_error = full_cheb_approximate( func, a, b, deg, tols.target_tol, tols.rel_approx_tol, good_deg) else: coeff, inf_norm, approx_error = full_cheb_approximate( func, a, b, deg, tols.abs_approx_tol, tols.rel_approx_tol, good_deg) inf_norms.append(inf_norm) approx_errors.append(approx_error) # Subdivides if a bad approximation if coeff is None: if not trust_small_evals: approx_errors = [max(err, macheps) for err in approx_errors] intervals = interval_data.get_subintervals(og_a, og_b, cheb_approx_list, approx_errors, False) #reorder funcs. TODO: fancier things like how likely it is to pass checks funcs2 = funcs.copy() if func_num + 1 < num_funcs: del funcs2[func_num] funcs2.append(func) for new_a, new_b in intervals: subdivision_solve_nd(funcs2, new_a, new_b, deg, target_deg, interval_data, root_tracker, tols, max_level, level=level + 1, method=method, trust_small_evals=trust_small_evals) return else: # Run checks to try and throw out the interval if not trust_small_evals: approx_error = max(approx_error, macheps) if interval_data.check_interval(coeff, approx_error, og_a, og_b): return cheb_approx_list.append(coeff) # Reduce the degree of the approximations while not introducing too much error coeffs, good_approx, approx_errors = trim_coeffs(cheb_approx_list, tols.abs_approx_tol, tols.rel_approx_tol, inf_norms, approx_errors) if not trust_small_evals: approx_errors = [max(err, macheps) for err in approx_errors] # Used if subdividing further. # Only choose good_degs if the approximation after trim_coeffs is good. if good_approx: # good_degs are assumed to be 1 higher than the current approximation # but no larger than the initial degree for more accurate performance. good_degs = [min(coeff.shape[0], deg) for coeff in coeffs] good_zeros_tol = max( tols.min_good_zeros_tol, sum(np.abs(approx_errors)) * tols.good_zeros_factor) # Check if the degree is small enough or if trim_coeffs introduced too much error if np.any(np.array([coeff.shape[0] for coeff in coeffs]) > target_deg + 1) or not good_approx: intervals = interval_data.get_subintervals(og_a, og_b, cheb_approx_list, approx_errors, True) for new_a, new_b in intervals: subdivision_solve_nd(funcs, new_a, new_b, deg, target_deg, interval_data, root_tracker, tols, max_level, good_degs, level + 1, method=method, trust_small_evals=trust_small_evals, use_target_tol=True) # Check if any approx error is greater than target_tol for Macaulay method elif np.any( np.array(approx_errors) > np.array(tols.target_tol) + tols.rel_approx_tol * np.array(inf_norms)): intervals = interval_data.get_subintervals(og_a, og_b, cheb_approx_list, approx_errors, True) for new_a, new_b in intervals: subdivision_solve_nd(funcs, new_a, new_b, deg, target_deg, interval_data, root_tracker, tols, max_level, good_degs, level + 1, method=method, trust_small_evals=trust_small_evals, use_target_tol=True) # Check if everything is linear elif np.all(np.array([coeff.shape[0] for coeff in coeffs]) == 2): if deg != 2: subdivision_solve_nd(funcs, a, b, 2, target_deg, interval_data, root_tracker, tols, max_level, good_degs, level, method=method, trust_small_evals=trust_small_evals, use_target_tol=True) return zero, cond = solve_linear(coeffs) # Store the information and exit zero = good_zeros_nd(zero, good_zeros_tol, good_zeros_tol) zero = transform(zero, a, b) zero = zeros_in_interval(zero, og_a, og_b, dim) interval_data.track_interval("Base Case", [a, b]) root_tracker.add_roots(zero, a, b, "Base Case") # Solve using spectral methods if stable. else: polys = [ MultiCheb(coeff, lead_term=[coeff.shape[0] - 1], clean_zeros=False) for coeff in coeffs ] res = multiplication(polys, max_cond_num=tols.max_cond_num, method=method) #check for a conditioning error if res[0] is None: # Subdivide but run some checks on the intervals first intervals = interval_data.get_subintervals(og_a, og_b, cheb_approx_list, approx_errors, True) for new_a, new_b in intervals: subdivision_solve_nd(funcs, new_a, new_b, deg, target_deg, interval_data, root_tracker, tols, max_level, good_degs, level + 1, method=method, trust_small_evals=trust_small_evals, use_target_tol=True) else: zeros = res zeros = good_zeros_nd(zeros, good_zeros_tol, good_zeros_tol) zeros = transform(zeros, a, b) zeros = zeros_in_interval(zeros, og_a, og_b, dim) interval_data.track_interval("Macaulay", [a, b]) root_tracker.add_roots(zeros, a, b, "Macaulay")
def build_macaulay(initial_poly_list, verbose=False): """Constructs the unreduced Macaulay matrix. Removes linear polynomials by substituting in for a number of variables equal to the number of linear polynomials. Parameters -------- initial_poly_list: list The polynomials in the system we are solving. verbose : bool Prints information about how the roots are computed. Returns ----------- matrix : 2d ndarray The Macaulay matrix matrix_terms : 2d integer ndarray Array containing the ordered basis, where the ith row contains the exponent/degree of the ith basis monomial cut : int Where to cut the Macaulay matrix for the highest-degree monomials varsToRemove : list The variables removed with removing linear polynomials A : 2d ndarray A matrix giving the linear relations between the removed variables and the remaining variables Pc : 1d integer ndarray Array containing the order of the variables as the appear in the columns of A """ power = is_power(initial_poly_list) dim = initial_poly_list[0].dim poly_coeff_list = [] degree = find_degree(initial_poly_list) linear_polys = [poly for poly in initial_poly_list if poly.degree == 1] nonlinear_polys = [poly for poly in initial_poly_list if poly.degree != 1] #Choose which variables to remove if things are linear, and add linear polys to matrix if len(linear_polys) >= 1: #Linear polys involved #get the row rededuced linear coefficients A, Pc = nullspace(linear_polys) varsToRemove = Pc[:len(A)].copy() #add to macaulay matrix for row in A: #reconstruct a polynomial for each row coeff = np.zeros([2] * dim) coeff[tuple(get_var_list(dim))] = row[:-1] coeff[tuple([0] * dim)] = row[-1] if not power: poly = MultiCheb(coeff) else: poly = MultiPower(coeff) poly_coeff_list = add_polys(degree, poly, poly_coeff_list) else: #no linear A, Pc = None, None varsToRemove = [] #add nonlinear polys to poly_coeff_list for poly in nonlinear_polys: poly_coeff_list = add_polys(degree, poly, poly_coeff_list) #Creates the matrix # return (*create_matrix(poly_coeff_list, degree, dim, varsToRemove), A, Pc) return create_matrix(poly_coeff_list, degree, dim, varsToRemove)
def subdivision_solve_nd(funcs, a, b, deg, target_deg, interval_data, root_tracker, tols, max_level, good_degs=None, level=0, method='svd'): """Finds the common zeros of the given functions. All the zeros will be stored in root_tracker. Parameters ---------- funcs : list Each element of the list is a callable function. 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 in the chebyshev approximation. target_deg : int The degree to subdivide down to before building the Macaulay 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 good_degs : numpy array Interpoation degrees that are guaranteed to give an approximation valid to within approx_tol. level : int The current level of the recursion. method : str (optional) The method to use when reducing the Macaulay matrix. Valid options are svd, tvb, and qrt. """ 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 potential roots if the residuals are small root_tracker.add_potential_roots((a + b) / 2, a, b, "Too Deep.") return if tols.check_eval_error: tols.abs_approx_tol = tols.abs_approx_tols[tols.currTol] if level % tols.check_eval_freq == 0: numSpots = (deg * 2)**len(a) - (deg)**len(a) for func in funcs: tols.abs_approx_tol = max( tols.abs_approx_tol, numSpots * getAbsApproxTol(func, 3, a, b)) cheb_approx_list = [] interval_data.print_progress() dim = len(a) if good_degs is None: good_degs = [None] * len(funcs) inf_norms = [] approx_errors = [] #Get the chebyshev approximations for func, good_deg in zip(funcs, good_degs): coeff, change_sign, inf_norm, approx_error = full_cheb_approximate( func, a, b, deg, tols.abs_approx_tol, tols.rel_approx_tol, good_deg) inf_norms.append(inf_norm) approx_errors.append(approx_error) #Subdivides if a bad approximation if coeff is None: intervals = get_subintervals(a, b, change_sign, None, None, None, approx_errors) for new_a, new_b in intervals: subdivision_solve_nd(funcs, new_a, new_b, deg, target_deg, interval_data, root_tracker, tols, max_level, level=level + 1, method=method) return else: #if the function changes sign on at least one subinterval, skip the checks if np.any(change_sign): cheb_approx_list.append(coeff) continue #Run checks to try and throw out the interval if interval_data.check_interval(coeff, approx_error, a, b): return cheb_approx_list.append(coeff) #Make the system stable to solve coeffs, good_approx, approx_errors = trim_coeffs(cheb_approx_list, tols.abs_approx_tol, tols.rel_approx_tol, inf_norms, approx_errors) #Used if subdividing further. good_degs = [coeff.shape[0] - 1 for coeff in coeffs] good_zeros_tol = max( tols.min_good_zeros_tol, np.sum(np.abs(approx_errors)) * tols.good_zeros_factor) #Check if everything is linear if np.all(np.array([coeff.shape[0] for coeff in coeffs]) == 2): if deg != 2: subdivision_solve_nd(funcs, a, b, 2, target_deg, interval_data, root_tracker, tols, max_level, good_degs, level, method=method) return zero, cond = solve_linear(coeffs) grad = [MultiCheb(c).grad(zero) for c in coeffs] #Store the information and exit zero = good_zeros_nd(zero, good_zeros_tol, good_zeros_tol) zero = transform(zero, a, b) interval_data.track_interval("Base Case", [a, b]) root_tracker.add_roots(zero, a, b, "Base Case") #Check if anything is linear # elif np.any(np.array([coeff.shape[0] for coeff in coeffs]) == 2): # #Subdivide but run some checks on the intervals first # intervals = get_subintervals(a,b,np.arange(dim),interval_data,cheb_approx_list,change_sign,approx_errors,True) # for new_a, new_b in intervals: # subdivision_solve_nd(method,funcs,new_a,new_b,deg, target_deg,interval_data,root_tracker,tols,max_level,good_degs,level+1) #Runs the same things as above, but we want to get rid of that eventually so keep them seperate. elif np.any( np.array([coeff.shape[0] for coeff in coeffs]) > target_deg) or not good_approx: intervals = get_subintervals(a, b, np.arange(dim), interval_data, cheb_approx_list, change_sign, approx_errors, True) for new_a, new_b in intervals: subdivision_solve_nd(funcs, new_a, new_b, deg, target_deg, interval_data, root_tracker, tols, max_level, good_degs, level + 1, method=method) #Solve using spectral methods if stable. else: polys = [ MultiCheb(coeff, lead_term=[coeff.shape[0] - 1], clean_zeros=False) for coeff in coeffs ] try: zeros = multiplication(polys, max_cond_num=tols.max_cond_num, method=method) grad = [[poly.grad(z) for poly in polys] for z in zeros] zeros = good_zeros_nd(zeros, good_zeros_tol, good_zeros_tol) zeros = transform(zeros, a, b) interval_data.track_interval("Macaulay", [a, b]) root_tracker.add_roots(zeros, a, b, "Macaulay") except (ConditioningError, TooManyRoots) as e: #Subdivide but run some checks on the intervals first intervals = get_subintervals(a, b, np.arange(dim), interval_data, cheb_approx_list, change_sign, approx_errors, True) for new_a, new_b in intervals: subdivision_solve_nd(funcs, new_a, new_b, deg, target_deg, interval_data, root_tracker, tols, max_level, good_degs, level + 1, method=method)
def run_n_dimension(args, radius, eigvals): num_points = args.num_points eps = args.eps power = args.power real = args.real by_coeffs = args.coeffs dim = args.dimension root_pts = {} residuals = {} powerpolys = [] chebpolys = [] if by_coeffs: for i in range(dim): from yroots.polynomial import getPoly powerpolys.append(getPoly(num_points, dim, power=True)) chebpolys.append(getPoly(num_points, dim, power=False)) else: r = np.random.random((num_points, dim)) * radius + eps roots = 2 * r - radius root_pts = {'roots': np.array(list(product(*np.rot90(roots))))} for i in range(dim): coeffs = np.zeros((num_points + 1, ) * dim) idx = [ slice(None), ] * dim idx[i] = 0 coeffs[tuple(idx)] = polyfromroots(roots[:, i]) lt = [0] * dim lt[i] = num_points powerpolys.append( MultiPower(coeffs)) #, lead_term=lt, clean_zeros=False)) coeffs[tuple(idx)] = chebfromroots(roots[:, i]) chebpolys.append( MultiCheb(coeffs)) #, lead_term=lt, clean_zeros=False)) # plt.subplot(121);plt.imshow(coeffs);plt.subplot(122);plt.imshow(chebpolys[0].coeff);plt.show() for solver in all_solvers: if not isinstance(solver, OneDSolver) and solver.basis in [ 'power', 'both' ]: # if ((not eigvals) or solver.eigvals): name = str(solver) + ' Power' root_pts[name] = solver(powerpolys) residuals[name] = maximal_residual(powerpolys, root_pts[name]) for solver in all_solvers: if not isinstance(solver, OneDSolver) and solver.basis in [ 'cheb', 'both' ]: # if ((not eigvals) or solver.eigvals): name = str(solver) + ' Cheb' root_pts[name] = solver(chebpolys) residuals[name] = maximal_residual(chebpolys, root_pts[name]) if args.hist: evaluations = {} for k, v in root_pts.items(): if k == 'roots': continue # evaluations[k] = [] polys = powerpolys if 'Power' in k else chebpolys # for poly in polys: evaluations[k] = sum(np.abs(poly(root_pts[k])) for poly in polys) ncols = len(evaluations) # plt.figure(figsize=(12,6)) fig, ax = plt.subplots(1, ncols, sharey=True, figsize=(12, 4)) minimal = -15 maximal = 1 for i, (k, v) in enumerate(evaluations.items()): ax[i].hist(np.clip(np.log10(v), minimal, maximal), range=(minimal, maximal), bins=40) ax[i].set_xlabel(r'$log_{10}(p(r_i))$') ax[i].set_title(k) plt.suptitle("Eigenvalues" if eigvals else "Eigenvectors") plt.show() return root_pts, residuals
def base_quadratic_check(test_coeff, tol): """Slow nd-quadratic check to test against. """ #get the dimension and make sure the coeff tensor has all the right # quadratic coeff spots, set to zero if necessary dim = test_coeff.ndim intervals = get_subintervals(-np.ones(dim), np.ones(dim), np.arange(dim), None, None, tol) padding = [(0, max(0, 3 - i)) for i in test_coeff.shape] test_coeff = np.pad(test_coeff.copy(), padding, mode='constant') #Possible extrema of qudaratic part are where D_xk = 0 for some subset of the variables xk # with the other variables are fixed to a boundary value #Dxk = c[0,...,0,1,0,...0] (k-spot is 1) + 4c[0,...,0,2,0,...0] xk (k-spot is 2) # + \Sum_{j\neq k} xj c[0,...,0,1,0,...,0,1,0,...0] (k and j spot are 1) #This gives a symmetric system of equations AX+B = 0 #We will fix different columns of X each time, resulting in slightly different #systems, but storing A and B now will be helpful later #pull out coefficients we care about quad_coeff = np.zeros([3] * dim) A = np.zeros([dim, dim]) B = np.zeros(dim) for spot in itertools.product(np.arange(3), repeat=dim): if np.sum(spot) < 3: spot_array = np.array(spot) if np.sum(spot_array != 0) == 2: #coef of cross terms i, j = np.where(spot_array != 0)[0] A[i, j] = test_coeff[spot].copy() A[j, i] = test_coeff[spot].copy() elif np.any(spot_array == 2): #coef of pure quadratic terms i = np.where(spot_array != 0)[0][0] A[i, i] = 4 * test_coeff[spot].copy() elif np.any(spot_array == 1): #coef of linear terms i = np.where(spot_array != 0)[0][0] B[i] = test_coeff[spot].copy() quad_coeff[spot] = test_coeff[spot] test_coeff[spot] = 0 #create a poly object for evaluations quad_poly = MultiCheb(quad_coeff) #The sum of the absolute values of everything else other_sum = np.sum(np.abs(test_coeff)) def powerset(iterable): "powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)" s = list(iterable) return itertools.chain.from_iterable(itertools.combinations(s, r)\ for r in range(len(s)+1)) mask = [] for interval_num, interval in enumerate(intervals): extreme_points = [] for fixed in powerset(np.arange(dim)): fixed = np.array(fixed) if len(fixed) == 0: #fix no vars--> interior if np.linalg.matrix_rank(A) < A.shape[0]: #no interior critical point continue X = la.solve(A, -B, assume_a='sym') #make sure it's in the domain if np.all([ interval[0][i] <= X[i] <= interval[1][i] for i in range(dim) ]): extreme_points.append(quad_poly(X)) elif len(fixed) == dim: #fix all variables--> corners for corner in itertools.product([0, 1], repeat=dim): #j picks if upper/lower bound. i is which var extreme_points.append( quad_poly( [interval[j][i] for i, j in enumerate(corner)])) else: #fixed some variables --> "sides" #we only care about the equations from the unfixed variables unfixed = np.delete(np.arange(dim), fixed) A_ = A[unfixed][:, unfixed] if np.linalg.matrix_rank(A_) < A_.shape[0]: #no solutions continue fixed_A = A[unfixed][:, fixed] B_ = B[unfixed] for side in itertools.product([0, 1], repeat=len(fixed)): X0 = np.array([interval[j][i] for i, j in enumerate(side)]) X_ = la.solve(A_, -B_ - fixed_A @ X0, assume_a='sym') X = np.zeros(dim) X[fixed] = X0 X[unfixed] = X_ if np.all([ interval[0][i] <= X[i] <= interval[1][i] for i in range(dim) ]): extreme_points.append(quad_poly(X)) #No root if min(extreme_points) > (other_sum + tol) # OR max(extreme_points) < -(other_sum+tol) #Logical negation gives the boolean we want mask.append( np.min(extreme_points) < (other_sum + tol) and np.max(extreme_points) > -(other_sum + tol)) return mask
def test_evaluate2(): cheb = MultiCheb(np.array([[0, 0, 0, 1], [0, 0, 0, 0], [0, 0, .5, 0]])) value = cheb((2, 5)) assert (np.isclose(value, 656.5))
def MacaulayReduction(initial_poly_list, max_cond_num, macaulay_zero_tol, verbose=False): """Reduces the Macaulay matrix to find a vector basis for the system of polynomials. Parameters -------- initial_poly_list: list The polynomials in the system we are solving. max_cond_num : float The maximum condition number of the Macaulay Matrix Reduction macaulay_zero_tol : float What is considered 0 in the macaulay matrix reduction. verbose : bool Prints information about how the roots are computed. Returns ----------- basisDict : dict A dictionary of terms not in the vector basis a matrixes of things in the vector basis that the term can be reduced to. VB : numpy array The terms in the vector basis, each row being a term. varsToRemove : list The variables to remove from the basis because we have linear polysnomials Raises ------ ConditioningError if rrqr_reduceMacaulay(...) raises a ConditioningError. """ power = is_power(initial_poly_list) dim = initial_poly_list[0].dim poly_coeff_list = [] degree = find_degree(initial_poly_list) linear_polys = [poly for poly in initial_poly_list if poly.degree == 1] nonlinear_polys = [poly for poly in initial_poly_list if poly.degree != 1] #Choose which variables to remove if things are linear, and add linear polys to matrix if len(linear_polys) == 1: #one linear varsToRemove = [ np.argmax(np.abs(linear_polys[0].coeff[get_var_list(dim)])) ] poly_coeff_list = add_polys(degree, linear_polys[0], poly_coeff_list) elif len(linear_polys) > 1: #multiple linear #get the row rededuced linear coefficients A, Pc = nullspace(linear_polys) varsToRemove = Pc[:len(A)].copy() #add to macaulay matrix for row in A: #reconstruct a polynomial for each row coeff = np.zeros([2] * dim) coeff[get_var_list(dim)] = row[:-1] coeff[tuple([0] * dim)] = row[-1] if power: poly = MultiPower(coeff) else: poly = MultiCheb(coeff) poly_coeff_list = add_polys(degree, poly, poly_coeff_list) else: #no linear varsToRemove = [] #add nonlinear polys to poly_coeff_list for poly in nonlinear_polys: poly_coeff_list = add_polys(degree, poly, poly_coeff_list) #Creates the matrix matrix, matrix_terms, cuts = create_matrix(poly_coeff_list, degree, dim, varsToRemove) if verbose: np.set_printoptions(suppress=False, linewidth=200) print('\nStarting Macaulay Matrix\n', matrix) print( '\nColumns in Macaulay Matrix\nFirst element in tuple is degree of x, Second element is degree of y\n', matrix_terms) print( '\nLocation of Cuts in the Macaulay Matrix into [ Mb | M1* | M2* ]\n', cuts) try: matrix, matrix_terms = rrqr_reduceMacaulay( matrix, matrix_terms, cuts, max_cond_num=max_cond_num, macaulay_zero_tol=macaulay_zero_tol) except ConditioningError as e: raise e # TODO: rrqr_reduceMacaulay2 is not working when expected. # if np.allclose(matrix[cuts[0]:,:cuts[0]], 0): # matrix, matrix_terms = rrqr_reduceMacaulay2(matrix, matrix_terms, cuts, accuracy = accuracy) # else: # matrix, matrix_terms = rrqr_reduceMacaulay(matrix, matrix_terms, cuts, accuracy = accuracy) if verbose: np.set_printoptions(suppress=True, linewidth=200) print("\nFinal Macaulay Matrix\n", matrix) print("\nColumns in Macaulay Matrix\n", matrix_terms) VB = matrix_terms[matrix.shape[0]:] basisDict = makeBasisDict(matrix, matrix_terms, VB, power) return basisDict, VB, varsToRemove
def MSMultMatrix(polys, poly_type, max_cond_num, macaulay_zero_tol, verbose=False, MSmatrix=0): ''' Finds the multiplication matrix using the reduced Macaulay matrix. Parameters ---------- polys : array-like The polynomials to find the common zeros of poly_type : string The type of the polynomials in polys verbose : bool Prints information about how the roots are computed. MSmatrix : int Controls which Moller-Stetter matrix is constructed. The options are: 0 (default) -- The Moller-Stetter matrix of a random polynomial Some positive integer i < dimension -- The Moller-Stetter matrix of x_i max_cond_num : float The maximum condition number of the Macaulay Matrix Reduction macaulay_zero_tol : float What is considered 0 in the macaulay matrix reduction. Returns ------- multiplicationMatrix : 2D numpy array The multiplication matrix for a random polynomial f var_dict : dictionary Maps each variable to its position in the vector space basis basisDict : dict A dictionary of terms not in the vector basis a matrixes of things in the vector basis that the term can be reduced to. VB : numpy array The terms in the vector basis, each row being a term. Raises ------ ConditioningError if MacaulayReduction(...) raises a ConditioningError. ''' try: basisDict, VB, varsRemoved = MacaulayReduction( polys, max_cond_num=max_cond_num, macaulay_zero_tol=macaulay_zero_tol, verbose=verbose) except ConditioningError as e: raise e dim = max(f.dim for f in polys) # Get the polynomial to make the MS matrix of if MSmatrix == 0: #random poly f = _random_poly(poly_type, dim)[0] else: #multiply by x_i where i is determined by MSmatrix xi_ind = np.zeros(dim, dtype=int) xi_ind[MSmatrix - 1] = 1 coef = np.zeros((2, ) * dim) coef[tuple(xi_ind)] = 1 if poly_type == "MultiPower": f = MultiPower(np.array(coef)) elif poly_type == "MultiCheb": f = MultiCheb(np.array(coef)) else: raise ValueError() if verbose: print( "\nCoefficients of polynomial whose Moller-Stetter matrix we construt\n", f.coeff) #Dictionary of terms in the vector basis their spots in the matrix. VBdict = {} spot = 0 for row in VB: VBdict[tuple(row)] = spot spot += 1 # Build multiplication matrix m_f mMatrix = np.zeros((len(VB), len(VB))) for i in range(VB.shape[0]): f_coeff = f.mon_mult(VB[i], returnType='Matrix') for term in zip(*np.where(f_coeff != 0)): if term in VBdict: mMatrix[VBdict[term]][i] += f_coeff[term] else: mMatrix[:, i] -= f_coeff[term] * basisDict[term] # Construct var_dict var_dict = {} for i in range(len(VB)): mon = VB[i] if np.sum(mon) == 1 or np.sum(mon) == 0: var_dict[tuple(mon)] = i return mMatrix, var_dict, basisDict, VB
def curvature_check(coeff, tol): poly = MultiCheb(coeff) a = np.array([-1.] * poly.dim) b = np.array([1.] * poly.dim) return not can_eliminate(poly, a, b, tol)
def run_one_dimension(args, radius, eigvals): num_points = args.num_points eps = args.eps power = args.power real = args.real by_coeffs = args.coeffs root_pts = {} residuals = {} if by_coeffs: coeffs = (np.random.random(num_points + 1) * 2 - 1) * radius powerpoly = MultiPower(coeffs) chebpoly = MultiCheb(coeffs) else: r = np.sqrt(np.random.random(num_points)) * radius + eps angles = 2 * np.pi * np.random.random(num_points) if power and not real: roots = r * np.exp(angles * 1j) else: roots = 2 * r - radius root_pts = {'roots': roots} powerpoly = MultiPower(polyfromroots(roots)) chebpoly = MultiCheb(chebfromroots(roots)) n = 1000 x = np.linspace(-1, 1, n) X, Y = np.meshgrid(x, 1j * x) for solver in all_solvers: if isinstance(solver, OneDSolver): if (solver.basis == 'cheb' and args.cheb) and ((not eigvals) or solver.eigvals): name = str(solver) root_pts[name] = solver(chebpoly, eigvals) residuals[name] = maximal_residual(chebpoly, root_pts[name]) if (solver.basis == 'power' and args.power) and ((not eigvals) or solver.eigvals): name = str(solver) root_pts[name] = solver(powerpoly, eigvals) residuals[name] = maximal_residual(powerpoly, root_pts[name]) if args.hist: evaluations = {} for k, v in root_pts.items(): if k == 'roots': continue poly = powerpoly if 'power' in k else chebpoly evaluations[k] = np.abs(poly(root_pts[k])) ncols = len(evaluations) fig, ax = plt.subplots(1, ncols, sharey=True, figsize=(12, 4)) minimal = -20 maximal = 1 for i, (k, v) in enumerate(evaluations.items()): ax[i].hist(np.clip(np.log10(v), minimal, maximal), range=(minimal, maximal), bins=40) ax[i].set_xlabel(r'$log_{10}(p(r_i))$') ax[i].set_title(k) plt.suptitle("Eigenvalues" if eigvals else "Eigenvectors") plt.show() return root_pts, residuals
def quadratic_check_nd(test_coeff, intervals, change_sign, tol): """One of subinterval_checks Finds the min of the absolute value of the quadratic part, and compares to the sum of the rest of the terms. Parameters ---------- test_coeff_in : numpy array The coefficient matrix of the polynomial to check intervals : list A list of the intervals to check. change_sign: list A list of bools of whether we know the functions can change sign on the subintervals. tol: float The bound of the sup norm error of the chebyshev approximation. Returns ------- mask : list A list of the results of each interval. False if the function is guarenteed to never be zero in the unit box, True otherwise """ dim = test_coeff.ndim padding = [(0,max(0,3-i)) for i in test_coeff.shape] test_coeff = np.pad(test_coeff.copy(), padding, mode='constant') A = np.zeros([dim,dim]) B = np.zeros(dim) quad_coeff = np.zeros([3]*dim) for spot in itertools.product(np.arange(3),repeat=dim): if np.sum(spot) < 3: spot_array = np.array(spot) if np.sum(spot_array != 0) == 2: i,j = np.where(spot_array != 0)[0] A[i,j] = test_coeff[spot].copy() A[j,i] = test_coeff[spot].copy() elif np.any(spot_array == 2): i = np.where(spot_array != 0)[0][0] A[i,i] = 4*test_coeff[spot].copy() elif np.any(spot_array == 1): i = np.where(spot_array != 0)[0][0] B[i] = test_coeff[spot].copy() quad_coeff[spot] = test_coeff[spot] test_coeff[spot] = 0 quad_poly = MultiCheb(quad_coeff) #The sum of the absolute values of everything else other_sum = np.sum(np.abs(test_coeff)) mask = [] for interval_num, interval in enumerate(intervals): if change_sign[interval_num]: mask.append(True) continue def powerset(iterable): "powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)" s = list(iterable) return itertools.chain.from_iterable(itertools.combinations(s, r)\ for r in range(len(s)+1)) extreme_points = [] for fixed in powerset(np.arange(dim)): fixed = np.array(fixed) if len(fixed) == 0: others = np.arange(dim) if np.linalg.matrix_rank(A) < A.shape[0]: continue X = np.linalg.solve(A, -B) if np.all([interval[0][i] <= X[i] <= interval[1][0] for i in range(dim)]): extreme_points.append(quad_poly(X)) elif len(fixed) == dim: for corner in itertools.product([0,1],repeat=dim): extreme_points.append(quad_poly([interval[j][i] for i,j in enumerate(corner)])) else: others = np.delete(np.arange(dim), fixed) A_ = A[others][:,others] if np.linalg.matrix_rank(A_) < A_.shape[0]: continue fixed_A = A[others][:,fixed] B_ = B[others] for corner in itertools.product([0,1],repeat=len(fixed)): X0 = np.array([interval[j][i] for i,j in enumerate(corner)]) X_ = np.linalg.solve(A_, -B_-fixed_A@X0) X = np.zeros(dim) X[fixed] = X0 X[others] = X_ if np.all([interval[0][i] <= X[i] <= interval[1][0] for i in range(dim)]): extreme_points.append(quad_poly(X)) extreme_points = np.array(extreme_points) #If sign change, True if not np.all(extreme_points > 0) and not np.all(extreme_points < 0): mask.append(True) continue if np.min(np.abs(extreme_points)) - tol > other_sum: mask.append(False) else: mask.append(True) return mask