コード例 #1
0
def test_quadratic_check():
    #keep this updated with the deg_dim used in subdivision solve
    deg_dim = {1: 100, 2: 20, 3: 9, 4: 9}
    num_tests_per_dim = 100
    tests_per_batch = num_tests_per_dim // 2
    tol = 1.e-4
    for dim in deg_dim.keys():
        print(dim)
        deg = deg_dim[dim]
        interval_data = IntervalData(-np.ones(dim), np.ones(dim), [])
        subintervals = interval_data.get_subintervals(interval_data.a,
                                                      interval_data.b, [], tol,
                                                      False)
        _quadratic_check = lambda c, tol: ~quadratic_check(
            c, interval_data.mask, tol, interval_data.RAND, subintervals)
        np.random.seed(42)
        rand_test_cases = np.random.rand(*[tests_per_batch] +
                                         [deg] * dim) * 2 - 1
        randn_test_cases = np.random.randn(*[tests_per_batch] + [deg] * dim)
        for c in rand_test_cases:
            assert np.allclose(base_quadratic_check(c, tol),
                               _quadratic_check(c, tol).flatten())
        for c in randn_test_cases:
            assert np.allclose(base_quadratic_check(c, tol),
                               _quadratic_check(c, tol).flatten())
コード例 #2
0
def test_zero_check2D():
    #curvature_check was causing import errors so it's not included...
    interval_checks = [constant_term_check, full_quad_check, full_cubic_check]
    a = -np.ones(2)
    b = np.ones(2)
    interval_data = IntervalData(a, b, [])
    tol = 1.e-4
    interval_checks.extend([lambda x, tol: linear_check(x, [(a, b)], tol)[0]])
    interval_checks.extend([
        lambda x, tol: ~quadratic_check(x, interval_data.mask, tol,
                                        interval_data.RAND,
                                        np.array([(a, b)] * 4))[0]
    ])

    test_cases = [
        np.array([[100, 2, -2, -1], [2, 2, 1, 0], [-1, 1, 0, 0], [1, 0, 0,
                                                                  0]]),
        np.array([[6.5, 2, -2, -1], [2, 2, 1, 0], [-1, 1, 0, 0], [1, 0, 0,
                                                                  0]]),
        np.array([
            [1.019, .2, .5],
            [0.2, .001, 0],
            [0.5, 0, 0],
        ]),
        np.array([
            [-1.3, .2, 0.5],
            [.2, .001, 0],
            [.5, 0, 0],
        ]),
        np.array([[-1.6, .2, .5, .1], [.2, .001, .1, 0], [0.5, .1, 0, 0],
                  [0.1, 0, 0, 0]])
    ]
    correct_results = [False, True, True, True, True, True]

    for method in interval_checks[:-2]:
        for res, c in zip(correct_results, test_cases):
            assert res == method(c, tol)
    for res, c in zip(correct_results, test_cases):
        assert res == ~interval_checks[-1](c, tol)[0]
        print(~interval_checks[-1](c, tol)[0])
コード例 #3
0
def solve(funcs,
          a,
          b,
          rel_approx_tol=1.e-15,
          abs_approx_tol=1.e-12,
          max_cond_num=1e5,
          good_zeros_factor=100,
          min_good_zeros_tol=1e-5,
          check_eval_error=True,
          check_eval_freq=1,
          plot=False,
          plot_intervals=False,
          deg=None,
          target_deg=2,
          return_potentials=False,
          method='svd',
          target_tol=1.01 * macheps,
          trust_small_evals=False,
          intervalReductions=["improveBound", "getBoundingParallelogram"]):
    """
    Finds the real roots of the given list of functions on a given interval.

    All of the tolerances can be passed in as numbers of iterable types. If
    multiple are passed in as iterable types they must have the same length.
    When the length is more than 1, they are used one after the other to polish
    the roots.

    Parameters
    ----------
    funcs : list of vectorized, callable functions
        Functions to find the common roots of.
        More efficient if functions have an 'evaluate_grid' method handle
        function evaluation at an grid of points.
    a : numpy array
        The lower bound on the interval.
    b : numpy array
        The upper bound on the interval.
    rel_approx_tol : float or list
        The relative tolerance used in the approximation tolerance. The error is bouned by
        error < abs_approx_tol + rel_approx_tol * inf_norm_of_approximation
    abs_approx_tol : float or list
        The absolute tolerance used in the approximation tolerance. The error is bouned by
        error < abs_approx_tol + rel_approx_tol * inf_norm_of_approximation
    max_cond_num : float or list
        The maximum condition number of the Macaulay Matrix Reduction
    macaulay_zero_tol : float or list
        What is considered 0 in the macaulay matrix reduction.
    good_zeros_factor : float or list
        Multiplying this by the approximation error gives how far outside of [-1, 1] a root can
        be and still be considered inside the interval.
    min_good_zeros_tol : float or list
        The smallest the good_zeros_tol can be, which is how far outside of [-1, 1] a root can
        be and still be considered inside the interval.
    check_eval_error : bool
        Whether to compute the evaluation error on the fly and replace the approx tol with it.
    check_eval_freq : int
        The evaluation error will be computed on levels that are multiples of this.
    plot : bool
        If True plots the zeros-loci of the functions along with the computed roots
    plot_intervals : bool
        If True, plot is True, and the functions are 2 dimensional, plots what check/method solved
        each part of the interval.
    deg : int
        The degree used for the approximation. If None, the following degrees
        are used.
        Degree 100 for 1D functions.
        Degree 20 for 2D functions.
        Degree 9 for 3D functions.
        Degree 9 for 4D functions.
        Degree 2 for 5D functions and above.
    target_deg : int
        The degree the approximation needs to be trimmed down to before the
        Macaulay solver is called. If unspecified, it will either be 5 (for 2D
        functions) or match the deg argument.
    return_potentials : bool
        If True, returns the potential roots. Else, it does not.
    method : str (optional)
        The method to use when reducing the Macaulay matrix. Valid options are
        svd, tvb, and qrt.
    target_tol : float
        The final absolute approximation tolerance to use before using any sort
        of solver (Macaulay, linear, etc).
    trust_small_evals : bool
        Whether or not to trust function evaluations that may give floats
        smaller than machine epsilon. This should only be set to True if the
        function evaluations are very accurate.
    intervalReductions : list
        A list specifying the types of interval reductions that should be performed
        on each subinterval. The order of methods in the list determines the order
        in which the interval reductions are performed. To stop any interval
        reduction method from being run, pass in an empty list to this parameter.

    If finding roots of a univariate function, `funcs` does not need to be a list,
    and `a` and `b` can be floats instead of arrays.

    Returns
    -------
    zeros : numpy array
        The common zeros of the polynomials. Each row is a root.
    """
    # Detect the dimension
    if isinstance(funcs, list):
        dim = len(funcs)
    elif callable(funcs):
        dim = 1
    else:
        raise ValueError('`funcs` must be a callable or list of callables.')

    # make a and b the right type
    a = np.float64(a)
    b = np.float64(b)

    # Choose an appropriate max degree for the given dimension if none is specified.
    if deg is None:
        deg_dim = {1: 100, 2: 20, 3: 9, 4: 9}
        if dim > 4:
            deg = 2
        else:
            deg = deg_dim[dim]

    # Sets up the tolerances.
    if isinstance(abs_approx_tol, list):
        abs_approx_tol = [max(tol, 1.01 * macheps) for tol in abs_approx_tol]
    else:
        abs_approx_tol = max(abs_approx_tol, 1.01 * macheps)
    tols = Tolerances(rel_approx_tol=rel_approx_tol,
                      abs_approx_tol=abs_approx_tol,
                      max_cond_num=max_cond_num,
                      good_zeros_factor=good_zeros_factor,
                      min_good_zeros_tol=min_good_zeros_tol,
                      check_eval_error=check_eval_error,
                      check_eval_freq=check_eval_freq,
                      target_tol=target_tol)
    tols.nextTols()

    # Set up the interval data and root tracker classes and cheb blocky copy arr
    interval_data = IntervalData(a, b, intervalReductions)
    root_tracker = RootTracker()
    values_arr.memo = {}
    initialize_values_arr(dim, 2 * (deg + 3))

    if dim == 1:
        # In one dimension, we don't use target_deg; it's the same as deg
        target_deg = deg
        solve_func = subdivision_solve_1d
        if isinstance(funcs, list):
            funcs = funcs[0]
    else:
        solve_func = subdivision_solve_nd

    # TODO : Set the maximum number of subdivisions so that
    # intervals cannot possibly be smaller than 2^-51
    max_level = 52

    # Initial Solve
    solve_func(funcs,
               a,
               b,
               deg,
               target_deg,
               interval_data,
               root_tracker,
               tols,
               max_level,
               method=method,
               trust_small_evals=trust_small_evals)
    root_tracker.keep_possible_duplicates()

    # Polishing
    while tols.nextTols():
        polish_intervals = root_tracker.get_polish_intervals()
        interval_data.add_polish_intervals(polish_intervals)
        for new_a, new_b in polish_intervals:
            interval_data.start_polish_interval()
            solve_func(funcs,
                       new_a,
                       new_b,
                       deg,
                       target_deg,
                       interval_data,
                       root_tracker,
                       tols,
                       max_level,
                       method=method)
            root_tracker.keep_possible_duplicates(),
    print("\rPercent Finished: 100%{}".format(' ' * 50))

    # Print results
    interval_data.print_results()

    # Plotting
    if plot:
        if dim == 1:
            x = np.linspace(a, b, 1000)
            plt.plot(x, funcs(x), color='k')
            plt.plot(np.real(root_tracker.roots),
                     np.zeros(len(root_tracker.roots)),
                     'o',
                     color='none',
                     markeredgecolor='r')
            plt.show()
        elif dim == 2:
            interval_data.plot_results(funcs, root_tracker.roots,
                                       plot_intervals)

    if len(root_tracker.potential_roots) != 0:
        warnings.warn(
            "Some intervals subdivided too deep and some potential roots were found. To access these roots, rerun the solver with the keyword return_potentials=True"
        )

    if return_potentials:
        return root_tracker.roots, root_tracker.potential_roots
    else:
        return root_tracker.roots
コード例 #4
0
def solve(funcs,
          a,
          b,
          rel_approx_tol=1.e-12,
          abs_approx_tol=1.e-8,
          max_cond_num=1e7,
          good_zeros_factor=100,
          min_good_zeros_tol=1e-5,
          check_eval_error=True,
          check_eval_freq=1,
          plot=False,
          plot_intervals=False,
          deg=9,
          target_deg=5,
          max_level=999,
          return_potentials=False,
          method='svd'):
    '''
    Finds the real roots of the given list of functions on a given interval.

    All of the tolerances can be passed in as numbers of iterable types. If
    multiple are passed in as iterable types they must have the same length.
    When the length is more than 1, they are used one after the other to polish
    the roots.

    Parameters
    ----------
    funcs : list of vectorized, callable functions
        Functions to find the common roots of.
        More efficient if functions have an 'evaluate_grid' method handle
        function evaluation at an grid of points.
    a : numpy array
        The lower bound on the interval.
    b : numpy array
        The upper bound on the interval.
    rel_approx_tol : float or list
        The relative tolerance used in the approximation tolerance. The error is bouned by
        error < abs_approx_tol + rel_approx_tol * inf_norm_of_approximation
    abs_approx_tol : float or list
        The absolute tolerance used in the approximation tolerance. The error is bouned by
        error < abs_approx_tol + rel_approx_tol * inf_norm_of_approximation
    max_cond_num : float or list
        The maximum condition number of the Macaulay Matrix Reduction
    macaulay_zero_tol : float or list
        What is considered 0 in the macaulay matrix reduction.
    good_zeros_factor : float or list
        Multiplying this by the approximation error gives how far outside of [-1,1] a root can
        be and still be considered inside the interval.
    min_good_zeros_tol : float or list
        The smallest the good_zeros_tol can be, which is how far outside of [-1,1] a root can
        be and still be considered inside the interval.
    check_eval_error : bool
        Whether to compute the evaluation error on the fly and replace the approx tol with it.
    check_eval_freq : int
        The evaluation error will be computed on levels that are multiples of this.
    plot : bool
        If True plots the zeros-loci of the functions along with the computed roots
    plot_intervals : bool
        If True, plot is True, and the functions are 2 dimensional, plots what check/method solved
        each part of the interval.
    deg : int
        The degree used for the approximation. If None, the following degrees
        are used.
        Degree 50 for 1D functions.
        Degree 9 for 2D functions.
        Degree 5 for 3D functions.
        Degree 3 for 4D functions.
        Degree 2 for 5D functions and above.
    target_deg : int
        The degree the approximation needs to be trimmed down to before the
        Macaulay solver is called. If unspecified, it will either be 5 (for 2D
        functions) or match the deg argument.
    max_level : int
        The maximum levels deep the recursion will go. Increasing it above 999 may result in recursion error!
    return_potentials : bool
        If True, returns the potential roots. Else, it does not.
    method : str (optional)
        The method to use when reducing the Macaulay matrix. Valid options are
        svd, tvb, and qrt.

    If finding roots of a univariate function, `funcs` does not need to be a list,
    and `a` and `b` can be floats instead of arrays.

    Returns
    -------
    zeros : numpy array
        The common zeros of the polynomials. Each row is a root.
    '''
    #Detect the dimension
    if not isinstance(funcs, list):
        dim = 1
    else:
        dim = len(funcs)

    #make a and b the right type
    a = np.float64(a)
    b = np.float64(b)

    # Choose an appropriate max degree for the given dimension if none is specified.
    if deg is None:
        deg_dim = {1: 50, 2: 9, 3: 5, 4: 3}
        if dim > 4:
            deg = 2
        else:
            deg = deg_dim[dim]

    # Choose an appropriate target degree if none is specified
    if target_deg is None:
        if dim != 2:
            target_deg = deg
        else:
            target_deg = 5

    #Sets up the tolerances.
    tols = Tolerances(rel_approx_tol=rel_approx_tol,
                      abs_approx_tol=abs_approx_tol,
                      max_cond_num=max_cond_num,
                      good_zeros_factor=good_zeros_factor,
                      min_good_zeros_tol=min_good_zeros_tol,
                      check_eval_error=check_eval_error,
                      check_eval_freq=check_eval_freq)
    tols.nextTols()

    #Set up the interval data and root tracker classes
    interval_data = IntervalData(a, b)
    root_tracker = RootTracker()

    if dim == 1:
        solve_func = subdivision_solve_1d
        if isinstance(funcs, list):
            funcs = funcs[0]
    else:
        solve_func = subdivision_solve_nd

    #Initial Solve
    solve_func(funcs, a, b, deg, target_deg, interval_data, \
              root_tracker, tols, max_level, method=method)
    root_tracker.keep_possible_duplicates()

    #Polishing
    while tols.nextTols():
        polish_intervals = root_tracker.get_polish_intervals()
        interval_data.add_polish_intervals(polish_intervals)
        for new_a, new_b in polish_intervals:
            interval_data.start_polish_interval()
            solve_func(funcs,
                       new_a,
                       new_b,
                       deg,
                       target_deg,
                       interval_data,
                       root_tracker,
                       tols,
                       max_level,
                       method=method)
            root_tracker.keep_possible_duplicates(),
    print("\rPercent Finished: 100%{}".format(' ' * 50))

    #Print results
    interval_data.print_results()

    #Plotting
    if plot:
        if dim == 1:
            x = np.linspace(a, b, 1000)
            plt.plot(x, funcs(x), color='k')
            plt.plot(np.real(root_tracker.roots),
                     np.zeros(len(root_tracker.roots)),
                     'o',
                     color='none',
                     markeredgecolor='r')
            plt.show()
        elif dim == 2:
            interval_data.plot_results(funcs, root_tracker.roots,
                                       plot_intervals)

    if len(root_tracker.potential_roots) != 0:
        warnings.warn(
            "Some intervals subdivided too deep and some potential roots were found. To access these roots, rerun the solver with the keyword return_potentials=True"
        )

    if return_potentials:
        return root_tracker.roots, root_tracker.potential_roots
    else:
        return root_tracker.roots
コード例 #5
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
    interval_data = IntervalData(-np.ones(dim), np.ones(dim), [])
    intervals = interval_data.get_subintervals(interval_data.a,
                                               interval_data.b, [], tol, False)
    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