def solve_linear(coeffs): """Finds the roots when the coeffs are **all** linear. Parameters ---------- coeffs : list A list of the coefficient arrays. They should all be linear. Returns ------- solve_linear : numpy array The root, if any. """ dim = len(coeffs[0].shape) A = np.zeros([dim, dim]) B = np.zeros(dim) for row in range(dim): coeff = coeffs[row] spot = tuple([0] * dim) B[row] = coeff[spot] var_list = get_var_list(dim) for col in range(dim): if coeff.shape[0] == 1: A[row, col] = 0 else: A[row, col] = coeff[var_list[col]] #solve the system try: return np.linalg.solve(A, -B), np.nan except np.linalg.LinAlgError as e: if str(e) == 'Singular matrix': #if the system is dependent, then there are infinitely many roots #if the system is inconsistent, there are no roots #TODO: this should be more airtight than raising a warning #if the rightmost column of U from LU decomposition # is a pivot column, system is inconsistent # otherwise, it's dependent U = lu(np.hstack((A, B.reshape(-1, 1))))[2] pivot_columns = [ np.flatnonzero(U[i, :])[0] for i in range(U.shape[0]) if np.flatnonzero(U[i, :]).shape[0] > 0 ] if not (U.shape[1] - 1 in pivot_columns): #independent warnings.warn('System potentially has infinitely many roots') return np.zeros([0, dim]), np.zeros([0, dim])
def makeBasisDict(matrix, matrix_terms, VB, power): '''Calculates and returns the basisDict. This is a dictionary of the terms on the diagonal of the reduced Macaulay matrix to the terms in the Vector Basis. It is used to create the multiplication matrix in root_finder. Parameters -------- matrix: numpy array The reduced Macaulay matrix. matrix_terms : numpy array The terms in the matrix. The i'th row is the term represented by the i'th column of the matrix. VB : numpy array Each row is a term in the vector basis. power : bool If True, the initial polynomials were MultiPower. If False, they were MultiCheb. Returns ----------- basisDict : dict Maps terms on the diagonal of the reduced Macaulay matrix (tuples) to numpy arrays that represent the terms reduction into the Vector Basis. ''' basisDict = {} VBSet = set() for i in VB: VBSet.add(tuple(i)) #We don't actually need most of the rows, so we only get the ones we need if power: neededSpots = set() for term, mon in itertools.product(VB, get_var_list(VB.shape[1])): if tuple(term + mon) not in VBSet: neededSpots.add(tuple(term + mon)) for i in range(matrix.shape[0]): term = tuple(matrix_terms[i]) if power and term not in neededSpots: continue basisDict[term] = matrix[i][matrix.shape[0]:] return basisDict
def nullspace(linear_polys): """Builds a matrix to represent the system of linear polynomials. Columns 1:-1 represent coefficients of linear terms, while Column -1 represents the constant terms. Parameters ---------- linear_polys : list list of linear MultiCheb objects Returns ------- A: ((n,n) ndarray) The RREF of A. Pc: ((n,) ndarray) The column pivoting array. """ dim = linear_polys[0].dim A = np.zeros((len(linear_polys),dim+1)) for i,p in enumerate(linear_polys): A[i,:-1] = p.coeff[tuple(get_var_list(dim))] A[i,-1] = p.coeff[tuple([0]*dim)] return rref(A)
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 bounding_parallelepiped(linear): """ A helper function for projecting polynomials. It accepts a linear polynomial and return vectors describing an (n-1)-dimensional parallelepiped that covers the intersection between the linear polynomial (it's variety) and the n-dimensional hypercube. Note: The parallelepiped can be described using just one vertex, and (n-1) vectors, each of dimension n. Second Note: This first attempt is very simple, and can be greatly improved by creating a parallelepiped that much more closely surrounds the points. Currently, it just makes an nd-rectangle. Parameters ---------- linear : numpy array The coefficients of the linear function. Returns ------- p0 : numpy array One vertex of the parallelepiped. edges : numpy array Array of vectors describing the edges of the parallelepiped, from p0. """ dim = linear.ndim coord = np.ones((dim-1,2)) coord[:,0] = -1 const = linear[tuple([0]*dim)] coeff = np.zeros(dim) # flatten the linear coefficients for i,idx in enumerate(get_var_list(dim)): coeff[i] = linear[idx] # get the intersection points with the hypercube edges lower = -np.ones(dim) upper = np.ones(dim) vert = [] for i in range(dim): pts = np.array([(pt[:i]+ (0,) + pt[i:]) for pt in product(*coord)]) val = -const for j,c in enumerate(coeff): if i==j: continue val = val - c*pts[:,j] if not np.isclose(coeff[i], 0): pts[:,i] = val/coeff[i] else: pts[:,i] = np.nan mask = np.all(lower <= pts, axis=1) & np.all(pts <= upper, axis=1) if np.any(mask): vert.append(pts[mask]) # what to do if no intersections if len(vert) == 0: p0 = -const/np.dot(coeff, coeff)*coeff Q, R = np.linalg.qr(np.column_stack([coeff, np.eye(dim)[:,:dim-1]])) edges = Q[:,1:] return p0, edges # do the thing vert = np.unique(np.vstack(vert), axis=0) v0 = vert[0] vert_shift = vert - v0 Q, vert_flat, _ = qr(vert_shift.T, pivoting=True) vert_flat = vert_flat[:-1] # remove flattened dimension min_vals = np.min(vert_flat, axis=1) max_vals = np.max(vert_flat, axis=1) p0 = Q[:,:-1].dot(min_vals) + v0 edges = Q[:,:-1].dot(np.diag(max_vals-min_vals)) return p0, edges
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 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 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)