Example #1
0
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()
Example #2
0
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)
Example #4
0
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
Example #7
0
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)
Example #11
0
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)
Example #12
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")
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)
Example #14
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 #15
0
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
Example #16
0
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
Example #17
0
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))
Example #18
0
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
Example #19
0
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)
Example #21
0
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
Example #22
0
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