Ejemplo n.º 1
0
    def test_get_noisy_rbf_coefficients(self):
        """Check solution of noisy RBF coefficients problem.

        This function verifies that the solution of the convex problem
        that computes the RBF coefficients for a noisy interpolant on
        a small test istance satisfies the (noisy) interpolation
        conditions.
        """
        ref = [0.0]
        fast_node_index = np.array([2, 3, 4])
        fast_node_err_bounds = [(-1.0, 1.0), (-1.0, 1.0), (-1.0, 1.0)]
        (l, h) = aux.get_noisy_rbf_coefficients(self.settings, self.n, self.k,
                                                self.Amat[:self.k, :self.k],
                                                self.Amat[:self.k, self.k:],
                                                self.node_val, fast_node_index,
                                                fast_node_err_bounds,
                                                self.rbf_lambda, self.rbf_h)
        # Verify interpolation conditions for noisy nodes
        for (i, j) in enumerate(fast_node_index):
            val = ru.evaluate_rbf(self.settings, self.node_pos[j], self.n,
                                  self.k, self.node_pos, l, h)
            lb = self.node_val[j] + fast_node_err_bounds[i][0]
            ub = self.node_val[j] + fast_node_err_bounds[i][1]
            self.assertLessEqual(lb, val, msg='Node value outside bounds')
            self.assertGreaterEqual(ub, val, msg='Node value outside bounds')
        # Verify interpolation conditions for regular (exact) nodes
        for i in range(self.k):
            if i in fast_node_index:
                continue
            val = ru.evaluate_rbf(self.settings, self.node_pos[j], self.n,
                                  self.k, self.node_pos, l, h)
            self.assertAlmostEqual(self.node_val[j],
                                   val,
                                   msg='Node value does not match')
Ejemplo n.º 2
0
def rbf_optimize(settings, dimension, var_lower, var_upper, objfun,
                 objfun_fast = None, integer_vars = None, 
                 init_node_pos = None, init_node_val = None,
                 output_stream = sys.stdout):
    """Optimize a black-box function.

    Optimize an unknown function over a box using the enhanced RBF
    method.

    Parameters
    ----------

    settings : rbfopt_settings.RbfSettings
        Global and algorithmic settings.

    dimension : int
        The dimension of the problem, i.e. size of the space.

    var_lower : List[float]
        Vector of variable lower bounds.

    var_upper : List[float]
        Vector of variable upper bounds.

    objfun : Callable[List[float]]
        The unknown function we want to optimize.

    objfun_fast : Callable[List[float]]
        A faster, lower quality version of the unknown function we
        want to optimize. If None, it is assumed that such a version
        of the function is not available.

    integer_vars : List[int] or None
        A list containing the indices of the integrality constrained
        variables. If None or empty list, all variables are assumed to
        be continuous.

    init_node_pos : List[List[float]] or None
        Coordinates of points at which the function value is known. If
        None, the initial points will be generated by the
        algorithm. This must be of length at least dimension + 1, if
        provided.

    init_node_val : List[float] or None
        Function values corresponding to the points given in
        init_node_pos. Should be None if the previous argument is
        None.

    output_stream : file or None 
        A stream object that will be used to print output. By default,
        this will be the standard output stream.

    Returns
    ---
    (float, List[float], int, int, int)
        A quintuple (value, point, itercount, evalcount,
        fast_evalcount) containing the objective function value of the
        best solution found, the corresponding value of the decision
        variables, the number of iterations of the algorithm, the
        total number of function evaluations, and the number of these
        evaluations that were performed in 'fast' mode.
    """

    assert(len(var_lower) == dimension)
    assert(len(var_upper) == dimension)
    assert((integer_vars is None) or (len(integer_vars) == 0) or
           (max(integer_vars) < dimension))
    assert(init_node_pos is None or 
           (len(init_node_pos) == len(init_node_val) and
            len(init_node_pos) >= dimension + 1))
    assert(isinstance(settings, RbfSettings))

    # Start timing
    start_time = time.time()

    # Set the value of 'auto' parameters if necessary
    l_settings = settings.set_auto_parameters(dimension, var_lower,
                                              var_upper, integer_vars)

    # Local and global RBF models are usually the same
    best_local_rbf, best_global_rbf = l_settings.rbf, l_settings.rbf
    
    # We use n to denote the dimension of the problem, same notation
    # of the paper. This is redundant but it simplifies our life.
    n = dimension

    # Set random seed. Some of the (external) libraries use numpy's
    # random generator, we use python's internal generator, so we have
    # to seed both for consistency.
    random.seed(l_settings.rand_seed)
    np.random.seed(l_settings.rand_seed)

    # Iteration number
    itercount = 0

    # Total number of function evaluations in accurate mode
    evalcount = 0
    # Total number of fast function evaluation
    fast_evalcount = 0

    # Identifier of the current step within the cyclic optimization
    # strategy counter. This typically increases at every iteration,
    # but sometimes we may decide to repeat a step.
    current_step = 0

    # Current number of consecutive local searches
    num_cons_ls = 0

    # Number of consecutive cycles without improvement
    num_stalled_cycles = 0

    # Number of consecutive discarded points
    num_cons_discarded = 0

    # Number of restarts in fast mode
    num_fast_restarts = 0
    
    # Initialize identifiers of the search steps
    inf_step = 0
    local_search_step = (l_settings.num_global_searches + 1)
    cycle_length = (l_settings.num_global_searches + 2)
    restoration_step = (l_settings.num_global_searches + 3)
    # Determine which step is the first of each loop
    first_step = (inf_step if l_settings.do_infstep else inf_step + 1)

    # Initialize settings for two-phase optimization.
    # two_phase_optimization indicates if the fast buy noisy objective
    # function is available.
    # is_best_fast indicates if the best known objective function
    # value was evaluated in fast mode or in accurate mode.
    # current_mode indicates the evaluation mode for the objective
    # function at a given stage.
    if (objfun_fast is not None):
        two_phase_optimization = True
        is_best_fast = True
        current_mode = 'fast'
    else:
        two_phase_optimization = False
        is_best_fast = False
        current_mode = 'accurate'

    # Round variable bounds to integer if necessary
    ru.round_integer_bounds(var_lower, var_upper, integer_vars)

    # List of node coordinates is node_pos, list of node values is in
    # node_val. We keep a current list and a global list; they can be
    # different in case of restarts.

    # We must choose the initial interpolation points. If they are not
    # given, generate them using the chosen strategy.
    if (init_node_pos is None):
        node_pos = ru.initialize_nodes(l_settings, var_lower, var_upper, 
                                       integer_vars)
        if (current_mode == 'accurate'):
            node_val = [objfun(point) for point in node_pos]
            evalcount += len(node_val)
        else:
            node_val = [objfun_fast(point) for point in node_pos]
            fast_evalcount += len(node_val)
        node_is_fast = [current_mode == 'fast' for val in node_val]
    else:
        node_pos = init_node_pos
        node_val = init_node_val
        # We assume that initial points provided by the user are
        # 'accurate'.
        node_is_fast = [False for val in node_val]

    # Make a copy, in the original space
    all_node_pos = [point for point in node_pos]
    all_node_val = [val for val in node_val]
    # Store if each function evaluation is fast or accurate
    all_node_is_fast = [val for val in node_is_fast]
    # We need to remember the index of the first node in all_node_pos
    # after every restart
    all_node_pos_size_at_restart = 0

    # Rescale the domain of the function
    node_pos = [ru.transform_domain(l_settings, var_lower, var_upper, point)
                for point in node_pos]

    (l_lower,
     l_upper) = ru.transform_domain_bounds(l_settings, var_lower, var_upper)

    # Current minimum value among the nodes, and its index
    fmin_index = node_val.index(min(node_val))
    fmin = node_val[fmin_index]
    # Current maximum value among the nodes
    fmax = max(all_node_val)
    # Denominator of errormin
    gap_den = (abs(l_settings.target_objval) 
               if (abs(l_settings.target_objval) >= l_settings.eps_zero)
               else 1.0)
    # Shift due to fast function evaluation
    gap_shift = (ru.get_fast_error_bounds(l_settings, fmin)[1]
                 if is_best_fast else 0.0)
    # Current minimum distance from the optimum
    gap = ((fmin + gap_shift - l_settings.target_objval)/gap_den)

    # Best value function at the beginning of an optimization cycle
    fmin_cycle_start = fmin

    # Print the initialization points
    for (i, val) in enumerate(node_val):
        min_dist = ru.get_min_distance(node_pos[i], node_pos[:i] + 
                                       node_pos[(i+1):])
        print('Iteration {:3d}'.format(itercount) + 
              ' {:16s}'.format('Initialization') +
              ': objval{:s}'.format('~' if node_is_fast[i] else ' ') +
              ' {:16.6f}'.format(val) +
              ' min_dist {:9.4f}'.format(min_dist) +
              ' gap {:8.2f}'.format(gap*100),
              file = output_stream)

    # Main loop
    while (itercount < l_settings.max_iterations and
           evalcount < l_settings.max_evaluations and
           time.time() - start_time < l_settings.max_clock_time and
           gap > l_settings.eps_opt):
        # If the user wants to skip inf_step as in the original paper
        # of Gutmann (2001), we proceed to the next iteration.
        if (current_step == inf_step and not l_settings.do_infstep):
            current_step = (current_step+1) % cycle_length
            continue

        # Check if we should restart. We only restart if the initial
        # sampling strategy is random, otherwise it makes little sense.
        if (num_cons_discarded >= l_settings.max_consecutive_discarded or 
            (num_stalled_cycles >= l_settings.max_stalled_cycles and
             evalcount + n + 1 < l_settings.max_evaluations and
             l_settings.init_strategy != 'all_corners' and
             l_settings.init_strategy != 'lower_corners')):
            print('Restart at iteration {:3d}'.format(itercount),
                  file = output_stream)
            output_stream.flush()
            # We update the number of fast restarts here, so that if
            # we hit the limit on fast restarts, we can evaluate
            # points in accurate mode after restarting (even if
            # current_mode is updated in a subsequent block of code)
            num_fast_restarts += (1 if current_mode == 'fast' else 0.0)
            # Store the current number of nodes
            all_node_pos_size_at_restart = len(all_node_pos)
            # Compute a new set of starting points
            node_pos = ru.initialize_nodes(l_settings, var_lower, var_upper, 
                                           integer_vars)
            if (current_mode == 'accurate' or
                num_fast_restarts > l_settings.max_fast_restarts or
                fast_evalcount + n + 1 >= l_settings.max_fast_evaluations):
                node_val = [objfun(point) for point in node_pos]
                evalcount += len(node_val)
            else:
                node_val = [objfun_fast(point) for point in node_pos]
                fast_evalcount += len(node_val)
            node_is_fast = [current_mode == 'fast' for val in node_val]
            # Print the initialization points
            for (i, val) in enumerate(node_val):
                min_dist = ru.get_min_distance(node_pos[i], node_pos[:i] + 
                                               node_pos[(i+1):])
                print('Iteration {:3d}'.format(itercount) + 
                      ' {:16s}'.format('Initialization') +
                      ': objval{:s}'.format('~' if node_is_fast[i] else ' ') +
                      ' {:16.6f}'.format(val) +
                      ' min_dist {:9.4f}'.format(min_dist) +
                      ' gap {:8.2f}'.format(gap*100),
                      file = output_stream)
            all_node_pos.extend(node_pos)
            all_node_val.extend(node_val)
            all_node_is_fast.extend(node_is_fast)
            # Rescale the domain of the function
            node_pos = [ru.transform_domain(l_settings, var_lower,
                                            var_upper, point)
                        for point in node_pos]            
            (l_lower, l_upper) = ru.transform_domain_bounds(l_settings,
                                                            var_lower,
                                                            var_upper)
            # Update all counters and values to restart properly
            fmin_index = node_val.index(min(node_val))
            fmin = node_val[fmin_index]
            fmax = max(node_val)
            fmin_cycle_start = fmin
            num_stalled_cycles = 0
            num_cons_discarded = 0
            is_best_fast = node_is_fast[fmin_index]

        # Number of nodes at current iteration
        k = len(node_pos)

        # Compute indices of fast node evaluations (sparse format)
        fast_node_index = ([i for (i, val) in enumerate(node_is_fast) if val]
                           if two_phase_optimization else list())

        # If function scaling is automatic, determine which one to use
        if (settings.function_scaling == 'auto' and 
            current_step <= first_step):
            sorted_node_val = sorted(node_val)
            if (sorted_node_val[len(sorted_node_val)//2] - 
                sorted_node_val[0] > l_settings.log_scaling_threshold):
                l_settings.function_scaling = 'log'
            else:
                l_settings.function_scaling = 'off'
        
        # Rescale nodes if necessary
        (scaled_node_val, scaled_fmin, scaled_fmax,
         node_err_bounds) = ru.transform_function_values(l_settings, node_val,
                                                         fmin, fmax,
                                                         fast_node_index)

        # If RBF selection is automatic, at the beginning of each
        # cycle check if a different RBF yields a better model
        if (settings.rbf == 'auto' and k > n+1 and current_step <= first_step):
            best_local_rbf = ms.get_best_rbf_model(l_settings, n, k, node_pos,
                                                   scaled_node_val,
                                                   int(math.ceil(k*0.1)))
            best_global_rbf = ms.get_best_rbf_model(l_settings, n, k, node_pos,
                                                    scaled_node_val,
                                                    int(math.ceil(k*0.7)))
        # If we are in local search or just before local search, use a
        # local model.
        if (current_step >= (local_search_step - 1)):
            l_settings.rbf = best_local_rbf
        # Otherwise, global.
        else:
            l_settings.rbf = best_global_rbf

        try:
            # Compute the matrices necessary for the algorithm
            Amat = ru.get_rbf_matrix(l_settings, n, k, node_pos)
            Amatinv = ru.get_matrix_inverse(l_settings, Amat)

            # Compute RBF interpolant at current stage
            if (fast_node_index):
                # Get coefficients for the exact RBF
                rc = ru.get_rbf_coefficients(l_settings, n, k, 
                                             Amat, scaled_node_val)
                # RBF with some fast function evaluations
                rc = aux.get_noisy_rbf_coefficients(l_settings, n, k, 
                                                    Amat[:k, :k],
                                                    Amat[:k, k:],
                                                    scaled_node_val,
                                                    fast_node_index,
                                                    node_err_bounds,
                                                    rc[0], rc[1])
                (rbf_l, rbf_h) = rc
            else:
                # Fully accurate RBF
                rc = ru.get_rbf_coefficients(l_settings, n, k, 
                                             Amat, scaled_node_val)
                (rbf_l, rbf_h) = rc
                
        except np.linalg.LinAlgError:
            # Error in the solution of the linear system. We must
            # switch to a restoration phase.
            current_step = restoration_step
            node_val.pop()
            node_pos.pop()
            if (node_is_fast.pop()):
                fast_node_index.pop()
            (scaled_node_val, scaled_fmin, scaled_fmax,
             node_err_bounds) = ru.transform_function_values(l_settings,
                                                             node_val,
                                                             fmin, fmax,
                                                             fast_node_index)
            k = len(node_pos)


        # For displaying purposes, record what type of iteration we
        # are performing
        iteration_id = ''
        
        # Initialize the new point to None
        next_p = None

        if (current_step == inf_step):
            # Infstep: explore the parameter space
            next_p = aux.maximize_one_over_mu(l_settings, n, k, l_lower,
                                              l_upper, node_pos, Amatinv,
                                              integer_vars)
            iteration_id = 'InfStep'

        elif (current_step == restoration_step):
            # Restoration: pick a random point, far enough from
            # current points
            restoration_done = False
            cons_restoration = 0
            while (not restoration_done and
                   cons_restoration < l_settings.max_consecutive_restoration):
                next_p = [random.uniform(var_lower[i], var_upper[i])
                          for i in range(n)]
                ru.round_integer_vars(next_p, integer_vars)
                if (ru.get_min_distance(next_p, node_pos) >
                    l_settings.min_dist):
                    # Try inverting the RBF matrix to see if
                    # nonsingularity is restored
                    try:
                        Amat = ru.get_rbf_matrix(l_settings, n, k + 1,
                                                 node_pos + [next_p])
                        Amatinv = ru.get_matrix_inverse(l_settings, Amat)
                        restoration_done = True
                    except np.linalg.LinAlgError:
                        cons_restoration += 1
                else:
                    cons_restoration += 1
            if (not restoration_done):
                print('Restoration phase keeps failing. Abort.',
                      file = output_stream)
                # This will force the optimization process to return
                break
            iteration_id = 'Restoration'
            
        elif (current_step == local_search_step):
            # Local search: compute the minimum of the RBF.
            min_rbf = aux.minimize_rbf(l_settings, n, k, l_lower,
                                       l_upper, node_pos,
                                       rbf_l, rbf_h, integer_vars)
            if (min_rbf is not None):
                min_rbf_val = ru.evaluate_rbf(l_settings, min_rbf, n, k, 
                                              node_pos, rbf_l, rbf_h)
            # If the RBF cannot me minimized, or if the minimum is
            # larger than the node with smallest value, just take the
            # node with the smallest value.
            if (min_rbf is None or 
                (min_rbf_val >= scaled_fmin + l_settings.eps_zero)):
                min_rbf = node_pos[fmin_index]
                min_rbf_val = scaled_fmin
            # Check if point can be accepted: is there an improvement?
            if (min_rbf_val <= (scaled_fmin - l_settings.eps_impr * 
                                max(1.0, abs(scaled_fmin)))):
                target_val = min_rbf_val
                next_p = min_rbf
                iteration_id = 'LocalStep'                    
            else:
                # If the point is not improving, we solve a global
                # search problem, rescaling the search box to enforce
                # some sort of local search
                target_val = scaled_fmin - 0.01*max(1.0, abs(scaled_fmin))
                local_varl = [max(l_lower[i], min_rbf[i] -
                                  l_settings.local_search_box_scaling * 
                                  0.33 * (l_upper[i] - l_lower[i]))
                              for i in range(n)]
                local_varu = [min(l_upper[i], min_rbf[i] +
                                  l_settings.local_search_box_scaling * 
                                  0.33 * (l_upper[i] - l_lower[i]))
                              for i in range(n)]
                ru.round_integer_bounds(local_varl, local_varu,
                                        integer_vars)
                next_p  = aux.maximize_h_k(l_settings, n, k, local_varl,
                                           local_varu, node_pos, rbf_l, 
                                           rbf_h, Amatinv, target_val,
                                           integer_vars)
                iteration_id = 'AdjLocalStep'

        else:
            # Global search: compromise between finding a good value
            # of the objective function, and improving the model.
            # Choose target value for the objective function. To do
            # so, we need the minimum of the RBF interpolant.
            min_rbf = aux.minimize_rbf(l_settings, n, k, l_lower,
                                       l_upper, node_pos,
                                       rbf_l, rbf_h, integer_vars)
            if (min_rbf is not None):
                min_rbf_val = ru.evaluate_rbf(l_settings, min_rbf, n, k, 
                                              node_pos, rbf_l, rbf_h)
            # If the RBF cannot me minimized, or if the minimum is
            # larger than the node with smallest value, just take the
            # node with the smallest value.
            if (min_rbf is None or 
                min_rbf_val >= scaled_fmin + l_settings.eps_zero):
                min_rbf = node_pos[fmin_index]
                min_rbf_val = scaled_fmin
            # The scaling factor is 1 - h/kappa, where h goes from
            # 0 to kappa-1 over the course of one global search
            # cycle, and kappa is the number of global searches.
            scaling = (1 - ((current_step - 1) /
                            l_settings.num_global_searches))**2
            # Compute the function value used to determine the target
            # value. This is given by the sorted value in position
            # sigma_n, where sigma_n is a function described in the
            # paper by Gutmann (2001). If clipping is disabled, we
            # simply take the largest function value.
            if (l_settings.targetval_clipping):
                local_fmax = ru.get_fmax_current_iter(l_settings, n, k, 
                                                      current_step, 
                                                      scaled_node_val)
            else:
                local_fmax = fmax

            target_val = (min_rbf_val - 
                          scaling * (local_fmax - min_rbf_val))

            # If the global search is almost a local search, we
            # restrict the search to a box following Regis and
            # Shoemaker (2007)
            if (scaling <= config.LOCAL_SEARCH_THRESHOLD):
                local_varl = [max(l_lower[i], min_rbf[i] -
                                  l_settings.local_search_box_scaling * 
                                  math.sqrt(scaling) * 
                                  (l_upper[i] - l_lower[i]))
                              for i in range(n)]
                local_varu = [min(l_upper[i], min_rbf[i] +
                                  l_settings.local_search_box_scaling * 
                                  math.sqrt(scaling) * 
                                  (l_upper[i] - l_lower[i]))
                              for i in range(n)]
                ru.round_integer_bounds(local_varl, local_varu,
                                        integer_vars)
            # Otherwise, use original bounds
            else:
                local_varl = l_lower
                local_varu = l_upper

            next_p  = aux.maximize_h_k(l_settings, n, k, local_varl,
                                       local_varu, node_pos, rbf_l,
                                       rbf_h, Amatinv, target_val,
                                       integer_vars)
            iteration_id = 'GlobalStep'

        # -- end if

        # If previous points were evaluated in low quality and we are
        # now in high-quality local search mode, then we should verify
        # if it is better to evaluate a brand new point or re-evaluate
        # a previously known point.
        if ((two_phase_optimization == True) and
            (current_step == local_search_step) and
            (current_mode == 'accurate')):
            (ind, bump) = ru.get_min_bump_node(l_settings, n, k, Amat, 
                                               scaled_node_val,
                                               fast_node_index, 
                                               node_err_bounds, target_val)
            
            if (ind is not None and next_p is not None):
                # Check if the newly proposed point is very close to
                # an existing one.
                if (ru.get_min_distance(next_p, node_pos) >
                    l_settings.min_dist):
                    # If not, compute bumpiness of the newly proposed point.
                    n_bump = ru.get_bump_new_node(l_settings, n, k, node_pos, 
                                                  scaled_node_val, next_p,
                                                  fast_node_index,
                                                  node_err_bounds, target_val)
                else:
                    # If yes, we will simply reevaluate the existing
                    # point (if it can be reevaluated).
                    ind = ru.get_min_distance_index(next_p, node_pos)
                    n_bump = (float('inf') if node_is_fast[ind] 
                              else float('-inf'))
                if (n_bump > bump):
                    # In this case we want to put the new point at the
                    # same location as one of the old points.  Remove
                    # the noisy function evaluation from the data
                    # structures, so that the point can be evaluated
                    # in accurate mode.
                    next_p = node_pos.pop(ind)
                    node_val.pop(ind)
                    node_is_fast.pop(ind)
                    all_node_pos.pop(all_node_pos_size_at_restart + ind)
                    all_node_val.pop(all_node_pos_size_at_restart + ind)
                    all_node_is_fast.pop(all_node_pos_size_at_restart + ind)
                    # We must update k here to make sure it is consistent
                    # until the start of the next iteration.
                    k = len(node_pos)
                                                     
        # If the optimization failed or the point is too close to
        # current nodes, discard it. Otherwise, add it to the list.
        if ((next_p is None) or 
            (ru.get_min_distance(next_p, node_pos) <= l_settings.min_dist)):
            current_step = (current_step+1) % cycle_length
            num_cons_ls = 0
            num_cons_discarded += 1
            print('Iteration {:3d}'.format(itercount) + ' Discarded',
                  file = output_stream)
            output_stream.flush()
        else:
            min_dist = ru.get_min_distance(next_p, node_pos)
            # Transform back to original space if necessary
            next_p_orig = ru.transform_domain(l_settings, var_lower,
                                              var_upper, next_p, True)
            # Evaluate the new point, in accurate mode or fast mode
            if (current_mode == 'accurate'):
                next_val = objfun(next_p_orig)
                evalcount += 1
                node_is_fast.append(False)
            else: 
                next_val = objfun_fast(next_p_orig)
                fast_evalcount += 1
                # Check if the point improves over existing points, or
                # if it could be optimal according to tolerances. In
                # this case, perform a double evaluation.
                best_possible = fmin + (ru.get_fast_error_bounds(l_settings,
                                                                 fmin)[0]
                                        if is_best_fast else 0.0)
                if ((next_val <= best_possible -
                     l_settings.eps_impr*max(1.0, abs(best_possible))) or
                    (next_val <= l_settings.target_objval +
                     l_settings.eps_opt*abs(l_settings.target_objval) -
                     ru.get_fast_error_bounds(l_settings, next_val)[0])):
                    print('Iteration {:3d}'.format(itercount) + 
                          ' {:16s}'.format(iteration_id) + ': objval~' +
                          ' {:16.6f}'.format(next_val) +
                          ' min_dist {:9.4f}'.format(min_dist) +
                          ' gap {:8.2f}'.format(gap*100),
                          file = output_stream)
                    output_stream.flush()
                    next_val = objfun(next_p_orig)
                    evalcount += 1
                    node_is_fast.append(False)
                else:
                    node_is_fast.append(True)

            # Add to the lists
            node_pos.append(next_p)
            node_val.append(next_val)
            all_node_pos.append(next_p_orig)
            all_node_val.append(next_val)
            all_node_is_fast.append(node_is_fast[-1])

            if ((current_step == local_search_step) and
                (next_val <= fmin - l_settings.eps_impr*max(1.0,abs(fmin))) and
                (num_cons_ls < l_settings.max_consecutive_local_searches - 1)):
                # Keep doing local search
                num_cons_ls += 1
            else:
                current_step = (current_step+1) % cycle_length
                num_cons_ls = 0
            num_cons_discarded = 0
                        
            # Update fmin
            if (next_val < fmin):
                fmin_index = k
                fmin = next_val
                is_best_fast = node_is_fast[-1]
            fmax = max(fmax, next_val)
            # Shift due to fast function evaluation
            gap_shift = (ru.get_fast_error_bounds(l_settings, next_val)[1]
                         if is_best_fast else 0.0)
            gap = min(gap, (next_val + gap_shift - l_settings.target_objval) /
                      gap_den)

            print('Iteration {:3d}'.format(itercount) + 
                  ' {:16s}'.format(iteration_id) +
                  ': objval{:s}'.format('~' if node_is_fast[-1] else ' ') +
                  ' {:16.6f}'.format(next_val) +
                  ' min_dist {:9.4f}'.format(min_dist) +
                  ' gap {:8.2f}'.format(gap*100),
                  file = output_stream)
            output_stream.flush()

        # Update iteration number
        itercount += 1

        # At the beginning of each loop of the cyclic optimization
        # strategy, check if the main loop is stalling
        if (current_step <= first_step): 
            if (fmin <= (fmin_cycle_start - l_settings.max_stalled_objfun_impr
                         * max(1.0, abs(fmin_cycle_start)))):
                num_stalled_cycles = 0
                fmin_cycle_start = fmin
            else:
                num_stalled_cycles += 1

        # Check if we should switch to the second phase of two-phase
        # optimization. The conditions for switching are:
        # 1) Optimization in fast mode restarted too many times.
        # 2) We reached the limit of fast mode iterations.
        if ((two_phase_optimization == True) and (current_mode == 'fast') and
            ((num_fast_restarts > l_settings.max_fast_restarts) or
             (itercount >= l_settings.max_fast_iterations) or
             (fast_evalcount >= l_settings.max_fast_evaluations))):
            print('Switching to accurate mode ' +
                  'at iteration {:3d}'.format(itercount),
                  file = output_stream)
            output_stream.flush()
            current_mode = 'accurate'            
            
    # -- end while

    # Find best point and return it
    i = all_node_val.index(min(all_node_val))
    fmin = all_node_val[i]
    gap_shift = (ru.get_fast_error_bounds(l_settings, fmin)[1]
                   if all_node_is_fast[i] else 0.0)
    gap = ((fmin + gap_shift - l_settings.target_objval) / gap_den)

    print('Summary: iterations {:3d}'.format(itercount) + 
          ' evaluations {:3d}'.format(evalcount) + 
          ' fast_evals {:3d}'.format(fast_evalcount) + 
          ' clock time {:7.2f}'.format(time.time() - start_time) + 
          ' objval{:s}'.format('~' if (all_node_is_fast[i]) else ' ') +
          ' {:15.6f}'.format(fmin) + 
          ' gap {:6.2f}'.format(100*gap),
          file = output_stream)
    output_stream.flush()

    return (all_node_val[i], all_node_pos[i],
            itercount, evalcount, fast_evalcount)
Ejemplo n.º 3
0
def get_bump_new_node(settings, n, k, node_pos, node_val, new_node,
                      fast_node_index, fast_node_err_bounds, target_val):
    """Compute the bumpiness with a new interpolation point.

    Computes the bumpiness of the interpolant obtained by setting a
    new node in a specified location, at value target_val.

    Parameters
    ----------
    settings : rbfopt_settings.RbfSettings
        Global and algorithmic settings.

    n : int
        Dimension of the problem, i.e. the space where the point lives.

    k : int
        Number of nodes, i.e. interpolation points.

    node_pos : List[List[float]]
        Location of current interpolation nodes.

    node_val : List[float]
        List of values of the function at the nodes.

    new_node : List[float]
        Location of new interpolation node.

    fast_node_index : List[int]
        List of indices of nodes whose function value should be
        considered variable withing the allowed range.
    
    fast_node_err_bounds : List[int]
        Allowed deviation from node values for nodes affected by
        error. This is a list of tuples (lower, upper) of the same
        length as fast_node_index.

    target_val : float
        Target function value at which we want to move the node.
    
    Returns
    -------
    float
        The bumpiness of the interpolant having a new node at the
        specified location, with value target_val.
    """
    assert(isinstance(settings, RbfSettings))
    assert(len(node_val)==k)
    assert(len(node_pos)==k)
    assert(len(fast_node_index)==len(fast_node_err_bounds))
    assert(new_node is not None)

    # Add the new node to existing ones
    n_node_pos = node_pos + [new_node]
    n_node_val = node_val + [target_val]

    # Compute the matrices necessary for the algorithm
    Amat = get_rbf_matrix(settings, n, k + 1, n_node_pos)

    # Get coefficients for the exact RBF
    (rbf_l, rbf_h) = get_rbf_coefficients(settings, n, k + 1, Amat,
                                          n_node_val)

    # Get RBF coefficients for noisy interpolant
    (rbf_l, rbf_h) = aux.get_noisy_rbf_coefficients(settings, n, k + 1, 
                                                    Amat[:(k+1), :(k+1)],
                                                    Amat[:(k+1), (k+1):],
                                                    n_node_val,
                                                    fast_node_index,
                                                    fast_node_err_bounds,
                                                    rbf_l, rbf_h)

    bumpiness = math.fsum(rbf_l[h]*Amat[h,j]*rbf_l[j]
                          for h in range(k+1) for j in range(k+1))

    return bumpiness
Ejemplo n.º 4
0
def get_min_bump_node(settings, n, k, Amat, node_val,
                      fast_node_index, fast_node_err_bounds, 
                      target_val):
    """Compute the bumpiness obtained by moving an interpolation point.

    Compute the bumpiness of the interpolant obtained by moving a
    single node (the one that yields minimum bumpiness, which is
    determined by this function) within target_val plus or minus
    error, to target_val.

    Parameters
    ----------
    settings : rbfopt_settings.RbfSettings
        Global and algorithmic settings.

    n : int
        Dimension of the problem, i.e. the space where the point lives.

    k : int
        Number of nodes, i.e. interpolation points.

    Amat : numpy.matrix
        The matrix A = [Phi P; P^T 0] of equation (3) in the paper by
        Costa and Nannicini.

    node_val : List[float]
        List of values of the function at the nodes.

    fast_node_index : List[int]
        List of indices of nodes whose function value should be
        considered variable withing the allowed range.
    
    fast_node_err_bounds : List[int]
        Allowed deviation from node values for nodes affected by
        error. This is a list of tuples (lower, upper) of the same
        length as fast_node_index.

    target_val : float
        Target function value at which we want to move the node.

    Returns
    -------
    (int, float)
        The index of the node and corresponding bumpiness value
        indicating the sought node in the list node_pos.
    """
    assert(isinstance(settings, RbfSettings))
    assert(len(node_val)==k)
    assert(isinstance(Amat, np.matrix))
    assert(len(fast_node_index)==len(fast_node_err_bounds))

    # Extract the matrices Phi and P from
    Phimat = Amat[:k, :k]
    Pmat = Amat[:k, k:]
    
    min_bump_index, min_bump = None, float('Inf')
    for (pos, i) in enumerate(fast_node_index):
        # Check if we are within the allowed range
        if (node_val[i] + fast_node_err_bounds[pos][0] <= target_val and
            node_val[i] + fast_node_err_bounds[pos][1] >= target_val):
            # If so, compute bumpiness. Save original data.
            orig_node_val = node_val[i]
            orig_node_err_bounds = fast_node_err_bounds[pos]
            # Fix this node at the target value.
            node_val[i] = target_val
            fast_node_err_bounds[pos] = (0.0, 0.0)
            # Compute RBF interpolant.
            # Get coefficients for the exact RBF first
            (rbf_l, rbf_h) = get_rbf_coefficients(settings, n, k, 
                                                  Amat, node_val)
            # And now the noisy version
            (rbf_l,
             rbf_h) = aux.get_noisy_rbf_coefficients(settings, n, k, Phimat,
                                                     Pmat, node_val,
                                                     fast_node_index,
                                                     fast_node_err_bounds,
                                                     rbf_l, rbf_h)
            # Restore original values
            node_val[i] = orig_node_val
            fast_node_err_bounds[pos] = orig_node_err_bounds
            # Compute bumpiness using the formula \lambda^T \Phi \lambda
            bump = math.fsum(rbf_l[h]*Phimat[h,j]*rbf_l[j]
                             for h in range(k) for j in range(k))
            if (bump < min_bump):
                min_bump_index, min_bump = i, bump

    return (min_bump_index, min_bump)