Example #1
0
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)
Example #2
0
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")
Example #3
0
def solve(polys,
          MSmatrix=0,
          eigvals=True,
          verbose=False,
          return_all_roots=True,
          max_cond_num=1.e6,
          macaulay_zero_tol=1.e-12):
    '''
    Finds the roots of the given list of polynomials.

    Parameters
    ----------
    polys : list of polynomial objects
        Polynomials to find the common roots of.
    MSmatrix : int
        Controls which Moller-Stetter matrix is constructed
        For a univariate polynomial, the options are:
            0 (default) -- The companion or colleague matrix, rotated 180 degrees
            1 -- The unrotated companion or colleague matrix
            -1 -- The inverse of the companion or colleague matrix
        For a multivariate polynomial, 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, where variables are index from x1, ..., xn
            Some negative integer i >= -dimension -- The Moller-Stetter matrix of x_i-inverse
    eigvals : bool
        Whether to compute roots of univariate polynomials from eigenvalues (True) or eigenvectors (False).
        Roots of multivariate polynomials are always comptued from eigenvectors
    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.
    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.
    '''
    polys = match_poly_dimensions(polys)
    # Determine polynomial type and dimension of the system
    poly_type = is_power(polys, return_string=True)
    dim = polys[0].dim

    if dim == 1:
        if len(polys) == 1:
            return oneD.solve(polys[0],
                              MSmatrix=MSmatrix,
                              eigvals=eigvals,
                              verbose=verbose)
        else:
            zeros = np.unique(
                oneD.solve(polys[0],
                           MSmatrix=MSmatrix,
                           eigvals=eigvals,
                           verbose=verbose))
            #Finds the roots of each succesive polynomial and checks which roots are common.
            for poly in polys[1:]:
                if len(zeros) == 0:
                    break
                zeros2 = np.unique(
                    oneD.solve(poly,
                               MSmatrix=MSmatrix,
                               eigvals=eigvals,
                               verbose=verbose))
                common = list()
                tol = 1.e-10
                for zero in zeros2:
                    spot = np.where(np.abs(zeros - zero) < tol)
                    if len(spot[0]) > 0:
                        common.append(zero)
                zeros = common
            return zeros
    else:
        if MSmatrix < 0:
            return division(polys,
                            verbose=verbose,
                            divisor_var=-MSmatrix - 1,
                            return_all_roots=return_all_roots,
                            max_cond_num=max_cond_num,
                            macaulay_zero_tol=macaulay_zero_tol)
        else:
            return multiplication(polys,
                                  verbose=verbose,
                                  MSmatrix=MSmatrix,
                                  return_all_roots=return_all_roots,
                                  max_cond_num=max_cond_num,
                                  macaulay_zero_tol=macaulay_zero_tol)