def correctZeros(poly, MSmatrix, eigvals=True, checkNumber=True): ''' A helper function. Takes in a polynomial, find the zeros, and calculates how many of the zeros are correct. In this function it asserts that the number of zeros is equal to the product of the degrees, which is only valid if the polynomial is random, and that at least 95% of the zeros are correct (so it will pass even on bad random runs) ''' zeros = solve(poly, MSmatrix=MSmatrix, eigvals=eigvals) if checkNumber: assert (len(zeros) == poly.degree) correct = 0 outOfRange = 0 for zero in zeros: good = True if not np.isclose(0, poly([zero]), atol=1.e-3): good = False if good: correct += 1 assert (100 * correct / (len(zeros)) > 95)
def division(polys, divisor_var=0, max_cond_num=1.e6, macaulay_zero_tol=1.e-12, verbose=False, polish=False, return_all_roots=True): '''Calculates the common zeros of polynomials using a division matrix. Parameters -------- polys: list of MultiCheb Polynomials The polynomials for which the common roots are found. divisor_var : int What variable is being divided by. 0 is x, 1 is y, etc. Defaults to x. 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 If True prints information about the solve. polish: bool If True runs a newton polish on the zeros before returning. return_all_roots : bool If True returns all the roots, otherwise just the ones in the unit box. Returns ----------- zeros : numpy array The common roots of the polynomials. Each row is a root. ''' #This first section creates the Macaulay Matrix with the monomials that don't have #the divisor variable in the first columns. polys, transform, is_projected = polys, lambda x: x, False if len(polys) == 1: from yroots.OneDimension import solve return transform(solve(polys[0], MSmatrix=0)) power = is_power(polys) dim = polys[0].dim matrix_degree = np.sum([poly.degree for poly in polys]) - len(polys) + 1 poly_coeff_list = [] for poly in polys: poly_coeff_list = add_polys(matrix_degree, poly, poly_coeff_list) matrix, matrix_terms, cuts = create_matrix(poly_coeff_list, matrix_degree, dim, divisor_var) 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 monomial, Second element is degree of y monomial \n', matrix_terms) print( '\nLocation of Cuts in the Macaulay Matrix into [ Mb | M1* | M2* ]\n', cuts) #If bottom left is zero only does the first QR reduction on top part of matrix (for speed). Otherwise does it on the whole thing if np.allclose(matrix[cuts[0]:, :cuts[0]], 0): matrix, matrix_terms = rrqr_reduceMacaulay( matrix, matrix_terms, cuts, max_cond_num=max_cond_num, macaulay_zero_tol=macaulay_zero_tol) else: matrix, matrix_terms = rrqr_reduceMacaulay( matrix, matrix_terms, cuts, max_cond_num=max_cond_num, macaulay_zero_tol=macaulay_zero_tol) if isinstance(matrix, int): return -1 VB = matrix_terms[matrix.shape[0]:] if verbose: np.set_printoptions(suppress=True, linewidth=200) print("\nFinal Macaulay Matrix\n", matrix) print("\nColumns in Macaulay Matrix\n", matrix_terms) #------------> chebyshev if not power: #Builds the inverse matrix. The terms are the vector basis as well as y^k/x terms for all k. Reducing #this matrix allows the y^k/x terms to be reduced back into the vector basis. x_pows_over_y = matrix_terms[np.where( matrix_terms[:, divisor_var] == 0)[0]] x_pows_over_y[:, divisor_var] = -np.ones(x_pows_over_y.shape[0], dtype='int') inv_matrix_terms = np.vstack((x_pows_over_y, VB)) inv_matrix = np.zeros([len(x_pows_over_y), len(inv_matrix_terms)]) #A bunch of different dictionaries are used below for speed purposes and to prevent repeat calculations. #A dictionary of term in inv_matrix_terms to their spot in inv_matrix_terms. inv_spot_dict = dict() spot = 0 for term in inv_matrix_terms: inv_spot_dict[tuple(term)] = spot spot += 1 #A dictionary of terms on the diagonal to their reduction in the vector basis. diag_reduction_dict = dict() for i in range(matrix.shape[0]): term = matrix_terms[i] diag_reduction_dict[tuple(term)] = matrix[i][-len(VB):] #A dictionary of terms to the terms in their quotient when divided by x. (symbolically) divisor_terms_dict = dict() for term in matrix_terms: divisor_terms_dict[tuple(term)] = get_divisor_terms( term, divisor_var) #A dictionary of terms to their quotient when divided by x. (in the vector basis) term_divide_dict = dict() for term in matrix_terms[-len(VB):]: term_divide_dict[tuple(term)] = divide_term( term, inv_matrix_terms, inv_spot_dict, diag_reduction_dict, len(VB), divisor_terms_dict) #Builds the inv_matrix by dividing the rows of matrix by x. for i in range(cuts[0]): inv_matrix[i] = divide_row(matrix[i][-len(VB):], matrix_terms[-len(VB):], term_divide_dict, len(inv_matrix_terms)) spot = matrix_terms[i] spot[divisor_var] -= 1 inv_matrix[i][inv_spot_dict[tuple(spot)]] += 1 #Reduces the inv_matrix to solve for the y^k/x terms in the vector basis. Q, R = qr(inv_matrix) if np.linalg.cond(R[:, :R.shape[0]]) > max_cond_num: return -1 inv_solutions = np.hstack((np.eye(R.shape[0]), solve_triangular(R[:, :R.shape[0]], R[:, R.shape[0]:]))) #A dictionary of term in the vector basis to their spot in the vector basis. VB_spot_dict = dict() spot = 0 for row in VB: VB_spot_dict[tuple(row)] = spot spot += 1 #A dictionary of terms of type y^k/x to their reduction in the vector basis. inv_reduction_dict = dict() for i in range(len(inv_solutions)): inv_reduction_dict[tuple( inv_matrix_terms[i])] = inv_solutions[i][len(inv_solutions):] #Builds the division matrix and finds the eigenvalues and eigenvectors. division_matrix = build_division_matrix(VB, VB_spot_dict, diag_reduction_dict, inv_reduction_dict, divisor_terms_dict) #<---------end Chebyshev else: #--------->Power basisDict = makeBasisDict(matrix, matrix_terms, VB) #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 division matrix division_matrix = np.zeros((len(VB), len(VB))) for i in range(VB.shape[0]): var = np.zeros(dim) var[divisor_var] = 1 term = tuple(VB[i] - var) if term in VBdict: division_matrix[VBdict[term]][i] += 1 else: division_matrix[:, i] -= basisDict[term] #<----------end Power vals, vecs = eig(division_matrix, left=True, right=False) #conjugate because scipy gives the conjugate eigenvector vecs = vecs.conj() # if len(vals) > len(np.unique(np.round(vals, 10))): # return -1 # eigenvalue_cond = np.linalg.cond(vecs) # if eigenvalue_cond*tol > 1: # return -1 # if verbose: # print("\nDivision Matrix\n", np.round(division_matrix[::-1,::-1], 2)) # print("\nLeft Eigenvectors (as rows)\n", vecs.T) # if not power: # if np.max(np.abs(vals)) > 1.e6: # return -1 #Calculates the zeros, the x values from the eigenvalues and the y values from the eigenvectors. zeros = list() for i in range(len(vals)): # if power and abs(vecs[-1][i]) < 1.e-3: # #This root has magnitude greater than 1, will possibly generate a false root due to instability # continue # if np.abs(vals[i]) < 1.e-5: # continue root = np.zeros(dim, dtype=complex) for spot in range(0, divisor_var): root[spot] = vecs[-(2 + spot)][i] / vecs[-1][i] for spot in range(divisor_var + 1, dim): root[spot] = vecs[-(1 + spot)][i] / vecs[-1][i] root[divisor_var] = 1 / vals[i] # conditions = condeigv(division_matrix.T) # if np.abs(vals[i]) > 1: # print(root, conditions[i]) if polish: root = newton_polish(polys, root) #throw out bad roots in cheb if not power: if np.any([abs(poly(root)) > 1.e-1 for poly in polys]): continue zeros.append(root) if return_all_roots: return transform(np.array(zeros)) else: # only return roots in the unit complex hyperbox zeros = transform(np.array(zeros)) return zeros[np.all(np.abs(zeros) <= 1, axis=0)]
def multiplication(polys, max_cond_num, macaulay_zero_tol, verbose=False, MSmatrix=0, return_all_roots=True): ''' Finds the roots of the given list of multidimensional polynomials using a multiplication matrix. Parameters ---------- polys : list of polynomial objects Polynomials to find the common roots of. 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 return_all_roots : bool If True returns all the roots, otherwise just the ones in the unit box. 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 ------- roots : numpy array The common roots of the polynomials. Each row is a root. Raises ------ ConditioningError if MSMultMatrix(...) raises a ConditioningError. ''' #We don't want to use Linear Projection right now # polys, transform, is_projected = polys, lambda x:x, False if len(polys) == 1: from yroots.OneDimension import solve return transform(solve(polys[0], MSmatrix=0)) poly_type = is_power(polys, return_string=True) dim = polys[0].dim if MSmatrix not in list(range(dim + 1)): raise ValueError( 'MSmatrix must be 0 (random polynomial), or the index of a variable' ) #By Bezout's Theorem. Useful for making sure that the reduced Macaulay Matrix is as we expect degrees = [poly.degree for poly in polys] max_number_of_roots = np.prod(degrees) try: m_f, var_dict, basisDict, VB = MSMultMatrix( polys, poly_type, verbose=verbose, MSmatrix=MSmatrix, max_cond_num=max_cond_num, macaulay_zero_tol=macaulay_zero_tol) except ConditioningError as e: raise e if verbose: print("\nM_f:\n", m_f[::-1, ::-1]) # Get list of indexes of single variables and store vars that were not # in the vector space basis. var_spots = [] removed_var_order = [] removed_var_spots = [] var_mask = [] for order, spot in enumerate(get_var_list(dim)): if spot in var_dict: var_spots.append(var_dict[tuple(spot)]) var_mask.append(True) else: removed_var_order.append(order) removed_var_spots.append(spot) var_mask.append(False) # Get left eigenvectors (come in conjugate pairs) vals, vecs = eig(m_f, left=True, right=False) if verbose: print('\nLeft Eigenvectors (as rows)\n', vecs.T) print('\nEigenvals\n', vals) zeros_spot = var_dict[tuple(0 for i in range(dim))] #throw out roots that were calculated unstably # vecs = vecs[:,np.abs(vecs[zeros_spot]) > 1.e-10] if verbose: print('\nVariable Spots in the Vector\n', var_spots) print('\nEigeinvecs at the Variable Spots:\n', vecs[var_spots]) print('\nConstant Term Spot in the Vector\n', zeros_spot) print('\nEigeinvecs at the Constant Term\n', vecs[zeros_spot]) roots = np.zeros([len(vecs), dim], dtype=complex) roots[:, var_mask] = (vecs[var_spots] / vecs[zeros_spot]).T #Compute the removed variables for order, spot in zip(removed_var_order, removed_var_spots): for coeff, pows in zip(basisDict[spot], VB): temp = coeff for place, num in enumerate(pows): if num > 0: temp *= roots[:, place]**num roots[:, order] -= temp #Check if too many roots assert roots.shape[ 0] <= max_number_of_roots, "Found too many roots,{}/{}/{}:{}".format( roots.shape, max_number_of_roots, degrees, roots) if return_all_roots: roots = np.array(roots, dtype=complex) # #print(roots) # REMARK: We don't always have good information about the derivatives, # so we can't use Newton polishing on our roots. # for i in range(len(roots)): # roots[i] = newton_polish(polys,roots[i],niter=100,tol=1e-20) # #print(roots) return roots else: # only return roots in the unit complex hyperbox return roots[np.all(np.abs(roots) <= 1, axis=0)]
def multiplication(polys, max_cond_num, verbose=False, return_all_roots=True, method='svd'): ''' Finds the roots of the given list of multidimensional polynomials using a multiplication matrix. Parameters ---------- polys : list of polynomial objects Polynomials to find the common roots of. max_cond_num : float The maximum condition number of the Macaulay Matrix Reduction verbose : bool Prints information about how the roots are computed. return_all_roots : bool If True returns all the roots, otherwise just the ones in the unit box. returns ------- roots : numpy array The common roots of the polynomials. Each row is a root. Raises ------ ConditioningError if reduce_macaulay() raises a ConditioningError. TooManyRoots if the macaulay matrix returns more roots than the Bezout bound. ''' #We don't want to use Linear Projection right now # polys, transform, is_projected = polys, lambda x:x, False if len(polys) == 1: from yroots.OneDimension import solve return transform(solve(polys[0], MSmatrix=0)) poly_type = is_power(polys, return_string=True) dim = polys[0].dim #By Bezout's Theorem. Useful for making sure that the reduced Macaulay Matrix is as we expect degrees = [poly.degree for poly in polys] max_number_of_roots = np.prod(degrees) matrix, matrix_terms, cut = build_macaulay(polys, verbose) # Attempt to reduce the Macaulay matrix if method == 'qrt': try: E, Q, cond, cond_back = reduce_macaulay_qrt( matrix, cut, max_cond_num) except ConditioningError as e: raise e elif method == 'tvb': try: E, Q, cond, cond_back = reduce_macaulay_tvb( matrix, cut, max_cond_num) except ConditioningError as e: raise e elif method == 'svd': try: E, Q, cond, cond_back = reduce_macaulay_svd( matrix, cut, max_cond_num) except ConditioningError as e: raise e # Construct the Möller-Stetter matrices # M is a 3d array containing the multiplication-by-x_i matrix in M[...,i] if poly_type == "MultiCheb": if method == 'qrt' or method == 'svd': M = ms_matrices_cheb(E, Q, matrix_terms, dim) elif method == 'tvb': M = ms_matrices_p_cheb(E, Q, matrix_terms, dim, cut) else: if method == 'qrt' or method == 'svd': M = ms_matrices(E, Q, matrix_terms, dim) elif method == 'tvb': M = ms_matrices_p(E, Q, matrix_terms, dim, cut) # Compute the roots using eigenvalues of the Möller-Stetter matrices roots, cond_eig = msroots(M) # Check if too many roots if roots.shape[0] > max_number_of_roots: raise TooManyRoots("Found too many roots,{}/{}/{}:{}".format( roots.shape, max_number_of_roots, degrees, roots)) if return_all_roots: return roots else: # only return roots in the unit complex hyperbox return roots[np.all(np.abs(roots) <= 1, axis=0)]
def multiplication(polys, max_cond_num, verbose=False, return_all_roots=True,method='svd'): ''' Finds the roots of the given list of multidimensional polynomials using a multiplication matrix. Parameters ---------- polys : list of polynomial objects Polynomials to find the common roots of. max_cond_num : float The maximum condition number of the Macaulay Matrix Reduction verbose : bool Prints information about how the roots are computed. return_all_roots : bool If True returns all the roots, otherwise just the ones in the unit box. returns ------- roots : numpy array The common roots of the polynomials. Each row is a root. ''' #We don't want to use Linear Projection right now # polys, transform, is_projected = polys, lambda x:x, False if len(polys) == 1: from yroots.OneDimension import solve return transform(solve(polys[0], MSmatrix=0)) poly_type = is_power(polys, return_string = True) dim = polys[0].dim #By Bezout's Theorem. Useful for making sure that the reduced Macaulay Matrix is as we expect bezout_bound = np.prod([poly.degree for poly in polys]) matrix, matrix_terms, cut = build_macaulay(polys, verbose) roots = np.array([]) # If cut is zero, then all the polynomials are linear and we solve # using solve_linear. if cut == 0: roots, cond = solve_linear([p.coeff for p in polys]) # Make sure roots is a 2D array. roots = np.array([roots]) else: # Attempt to reduce the Macaulay matrix if method == 'svd': res = reduce_macaulay_svd(matrix,cut,bezout_bound,max_cond_num) if res[0] is None: return res E,Q = res elif method == 'qrt': res = reduce_macaulay_qrt(matrix,cut,bezout_bound,max_cond_num) if res[0] is None: return res E,Q = res elif method == 'tvb': res = reduce_macaulay_tvb(matrix,cut,bezout_bound,max_cond_num) if res[0] is None: return res E,Q = res else: raise ValueError("Method must be one of 'svd','qrt' or 'tvb'") # Construct the Möller-Stetter matrices # M is a 3d array containing the multiplication-by-x_i matrix in M[...,i] if poly_type == "MultiCheb": if method == 'qrt' or method == 'svd': M = ms_matrices_cheb(E,Q,matrix_terms,dim) elif method == 'tvb': M = ms_matrices_p_cheb(E,Q,matrix_terms,dim,cut) else: if method == 'qrt' or method == 'svd': M = ms_matrices(E,Q,matrix_terms,dim) elif method == 'tvb': M = ms_matrices_p(E,Q,matrix_terms,dim,cut) # Compute the roots using eigenvalues of the Möller-Stetter matrices roots = msroots(M) if return_all_roots: return roots else: # only return roots in the unit complex hyperbox return roots[[np.all(np.abs(root) <= 1) for root in roots]]
def divisionNew(polys, divisor_var=0, tol=1.e-12, verbose=False, polish=False, return_all_roots=True): '''Calculates the common zeros of polynomials using a division matrix. Parameters -------- polys: list of MultiCheb Polynomials The polynomials for which the common roots are found. divisor_var : int What variable is being divided by. 0 is x, 1 is y, etc. Defaults to x. tol : float The tolerance parameter for the Macaulay Reduce. verbose : bool If True prints information about the solve. polish: bool If True runs a newton polish on the zeros before returning. Returns ----------- zeros : numpy array The common roots of the polynomials. Each row is a root. ''' #This first section creates the Macaulay Matrix with the monomials that don't have #the divisor variable in the first columns. polys, transform, is_projected = LinearProjection.remove_linear( polys, 1e-4, 1e-8) if len(polys) == 1: from yroots.OneDimension import solve return transform(solve(polys[0], MSmatrix=0)) power = is_power(polys) if power: raise ValueError("This only works for Chebyshev polynomials") dim = polys[0].dim matrix_degree = np.sum([poly.degree for poly in polys]) - len(polys) + 1 poly_coeff_list = [] for poly in polys: poly_coeff_list = add_polys(matrix_degree, poly, poly_coeff_list) matrix, matrix_terms, cuts = create_matrix(poly_coeff_list, matrix_degree,\ dim, divisor_var) x_pows_over_y = matrix_terms[:cuts[0]].copy() x_pows_over_y[:, divisor_var] = -np.ones(cuts[0], dtype='int') inv_matrix_terms = np.vstack((x_pows_over_y, matrix_terms)) num_rows = matrix.shape[0] inv_matrix = np.zeros([num_rows + matrix.shape[0], len(inv_matrix_terms)]) cuts = tuple([cuts[0], cuts[0] + cuts[1]]) #A dictionary of term in inv_matrix_terms to their spot in inv_matrix_terms. inv_spot_dict = dict() spot = 0 for term in inv_matrix_terms: inv_spot_dict[tuple(term)] = spot spot += 1 #A dictionary of terms to the terms in their quotient when divided by x. (symbolically) divisor_terms_dict = dict() for term in matrix_terms: divisor_terms_dict[tuple(term)] = get_divisor_terms(term, divisor_var) #A dictionary of terms to their quotient when divided by x. (in the vector basis) term_divide_dict = dict() for term in matrix_terms: term_divide_dict[tuple(term)] = divide_term(term, inv_matrix_terms, inv_spot_dict, divisor_terms_dict) #Builds the inv_matrix by dividing the rows of matrix by x. for i in range(num_rows): inv_matrix[i] = divide_row(matrix[i], matrix_terms, term_divide_dict, len(inv_matrix_terms)) inv_matrix[num_rows:, cuts[0]:] = matrix matrix, matrix_terms, perm = rrqr_reduceMacaulay(inv_matrix, inv_matrix_terms, cuts, accuracy=tol, return_perm=True) if isinstance(matrix, int): return -1 for term in term_divide_dict: term_divide_dict[tuple(term)] = term_divide_dict[tuple(term)][perm] VB = matrix_terms[matrix.shape[0]:] basisDict = makeBasisDict2(matrix, matrix_terms) # print(len(VB)) #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 #Builds the division matrix and finds the eigenvalues and eigenvectors. division_matrix = build_division_matrix(VB, VBdict, basisDict, term_divide_dict, matrix_terms) vals, vecs = eig(division_matrix, left=True, right=False) #conjugate because scipy gives the conjugate eigenvector vecs = vecs.conj() if len(vals) > len(np.unique(np.round(vals, 10))): return -1 vals2, vecs2 = eig(vecs) sorted_vals2 = np.sort(np.abs(vals2)) #Sorted smallest to biggest if sorted_vals2[0] < sorted_vals2[-1] * tol: return -1 if verbose: print("\nDivision Matrix\n", np.round(division_matrix[::-1, ::-1], 2)) print("\nLeft Eigenvectors (as rows)\n", vecs.T) if np.max(np.abs(vals)) > 1.e6: return -1 #Calculates the zeros, the x values from the eigenvalues and the y values from the eigenvectors. zeros = list() for i in range(len(vals)): if np.abs(vals[i]) < 1.e-5: continue root = np.zeros(dim, dtype=complex) for spot in range(0, divisor_var): root[spot] = vecs[-(2 + spot)][i] / vecs[-1][i] for spot in range(divisor_var + 1, dim): root[spot] = vecs[-(1 + spot)][i] / vecs[-1][i] root[divisor_var] = 1 / vals[i] if polish: root = newton_polish(polys, root, tol=tol) #throw out bad roots in cheb if np.any([abs(poly(root)) > 1.e-1 for poly in polys]): continue zeros.append(root) if return_all_roots: return transform(np.array(zeros)) else: # only return roots in the unit complex hyperbox zeros = transform(np.array(zeros)) return zeros[np.all(np.abs(zeros) <= 1, axis=0)]