def step_fw(objective_function, feasible_region, point_x, step_size_param): """ Makes a Vanilla Frank-Wolfe step. Note that the VANILLA FW algorithm only uses the cartesian coordinates and does not use the active set or the barycentric coordinates for anything. """ grad = objective_function.evaluate_grad(point_x.cartesian_coordinates) v = feasible_region.lp_oracle(grad) wolfe_gap = grad.dot(point_x.cartesian_coordinates - v) d = v - point_x.cartesian_coordinates alpha_max = 1.0 alpha = step_size( objective_function, point_x.cartesian_coordinates, d, grad, alpha_max, step_size_param, ) if alpha != alpha_max: flag, point_v = point_x.is_vertex_in_support(v) if flag == False: new_barycentric_coordinates = list(point_x.barycentric_coordinates) new_barycentric_coordinates.append(0.0) point_x = Point( point_x.cartesian_coordinates, tuple(new_barycentric_coordinates), point_v.support, ) return point_x + alpha * (point_v - point_x), wolfe_gap, 0.0 else: return Point(v, (1.0,), (v,)), wolfe_gap, 0.0
def away_step_fw(objective_function, feasible_region, point_x, step_size_param): """Makes a away-step Frank-Wolfe step.""" grad = objective_function.evaluate_grad(point_x.cartesian_coordinates) v = feasible_region.lp_oracle(grad) point_a, index_max = feasible_region.away_oracle(grad, point_x) wolfe_gap = grad.dot(point_x.cartesian_coordinates - v) strong_wolfe_gap = grad.dot(point_a.cartesian_coordinates - v) if wolfe_gap > grad.dot( point_a.cartesian_coordinates - point_x.cartesian_coordinates ): alpha_max = 1.0 alpha = step_size( objective_function, point_x.cartesian_coordinates, v - point_x.cartesian_coordinates, grad, alpha_max, step_size_param, ) if alpha != alpha_max: flag, point_v = point_x.is_vertex_in_support(v) if flag == False: new_barycentric_coordinates = list(point_x.barycentric_coordinates) new_barycentric_coordinates.append(0.0) point_x = Point( point_x.cartesian_coordinates, tuple(new_barycentric_coordinates), point_v.support, ) return point_x + alpha * (point_v - point_x), wolfe_gap, strong_wolfe_gap else: return ( Point(v, (1.0,), (v,)), wolfe_gap, strong_wolfe_gap, ) else: alpha_max = point_x.barycentric_coordinates[index_max] / ( 1.0 - point_x.barycentric_coordinates[index_max] ) alpha = step_size( objective_function, point_x.cartesian_coordinates, point_x.cartesian_coordinates - point_a.cartesian_coordinates, grad, alpha_max, step_size_param, ) point_x = point_x + alpha * (point_x - point_a) if alpha == alpha_max: point_x = point_x.delete_vertex_in_support(index_max) return point_x, wolfe_gap, strong_wolfe_gap
def max_vertex(d, vertices): """ Iterate over current active set and return vertex with greatest inner product. Parameters ---------- d: np.ndarray Direction. vertices: tuple(np.ndarray) or list(np.ndarray) Tuple or list of vertices. Returns ------- Point """ max_prod = d.dot(vertices[0]) max_ind = 0 for i in range(1, len(vertices)): if d.dot(vertices[i]) > max_prod: max_prod = d.dot(vertices[i]) max_ind = i barycentric = np.zeros(len(vertices)) barycentric[max_ind] = 1.0 return Point(vertices[max_ind], barycentric, vertices), max_ind
def dummy_call_argmin_quadratic_over_active_set(): LOGGER.info("Compiling argmin_quadratic_over_active_set with numba jit.") try: active_set = ( np.array([1.0, 0.0, 0.0]), np.array([0.0, 1.0, 0.0]), ) point_reference = Point( np.array([0.5, 0.5, 0.0]), np.array([0.5, 0.5]), active_set, ) _ = argmin_quadratic_over_active_set( 1.0, np.array([1.0, 1.0, 1.0]), active_set, point_reference, "dual gap", 10e-3, ) except: LOGGER.info( "Compiling argmin_quadratic_over_active_set with numba jit failed." ) return False LOGGER.info( "Compiling argmin_quadratic_over_active_set with numba jit done.") return True
def dipfw(objective_function, feasible_region, point_x, step_size_param): """Makes a decompositional invariant Frank-Wolfe step.""" grad = objective_function.evaluate_grad(point_x.cartesian_coordinates) v = feasible_region.lp_oracle(grad) grad_aux = grad.copy() for i in range(len(grad_aux)): if point_x.cartesian_coordinates[i] == 0.0: grad_aux[i] = -1.0e15 a = feasible_region.lp_oracle(-grad_aux) d = v - a alpha_max = calculate_stepsize(point_x.cartesian_coordinates, d) assert ( step_size_param["type_step"] == "line_search" ), "DIPFW only accepts exact linesearch." alpha = step_size( objective_function, point_x.cartesian_coordinates, d, grad, alpha_max, step_size_param, ) new_cartesian = point_x.cartesian_coordinates + alpha * d return ( Point(new_cartesian, (1.0,), (new_cartesian,)), grad.dot(point_x.cartesian_coordinates - v), 0.0, )
def pairwise_step_fw(objective_function, feasible_region, point_x, step_size_param): """Makes a pairwise-step Frank-Wolfe step.""" grad = objective_function.evaluate_grad(point_x.cartesian_coordinates) v = feasible_region.lp_oracle(grad) wolfe_gap = grad.dot(point_x.cartesian_coordinates - v) point_a, index_max = feasible_region.away_oracle(grad, point_x) strong_wolfe_gap = grad.dot(point_a.cartesian_coordinates - v) # Find the weight of the extreme point a in the decomposition. alpha_max = point_x.barycentric_coordinates[index_max] alpha = step_size( objective_function, point_x.cartesian_coordinates, v - point_a.cartesian_coordinates, grad, alpha_max, step_size_param, ) flag, point_v = point_x.is_vertex_in_support(v) if flag == False: new_barycentric_coordinates = list(point_x.barycentric_coordinates) new_barycentric_coordinates.append(0.0) point_x = Point( point_x.cartesian_coordinates, tuple(new_barycentric_coordinates), point_v.support, ) new_barycentric_coordinates = list(point_a.barycentric_coordinates) new_barycentric_coordinates.append(0.0) point_a = Point( point_a.cartesian_coordinates, tuple(new_barycentric_coordinates), point_v.support, ) point_x = point_x + alpha * (point_v - point_a) if alpha == alpha_max: point_x = point_x.delete_vertex_in_support(index_max) return point_x, wolfe_gap, strong_wolfe_gap
def run( self, objective_function, feasible_region, point_initial, epsilon=0.0, initial_eta=None, initial_sigma=None, shared_buffers_dict=None, last_restart_iter=0, epsilon_f=1e-12, ): """ Run PF-ACC given an initial point and an active set/feasible region. Parameters ---------- objective_function: implemented _AbstractObjectiveFunction Objective function over which this algorithm optimizes. feasible_region: implemented _AbstractFeasibleRegion Feasible region over which this algorithm optimizes. point_initial: Point Initial point object. epsilon: float Accuracy w.r.t. to norm of gradient mapping. initial_eta: float Initial estimate of the smoothness parameter eta. initial_sigma: float Initial estimate of the strong convexity parameter sigma. shared_buffers_dict: dict If provided, updates the shared memory buffers. last_restart_iter: int The iteration since the last restart in PFLaCG. epsilon_f: float Accuaracy w.r.t. to primal gap to early halt algorithm. Returns ------- list(run_status) """ LOGGER.info( f"ACC process started at last_restart_iter = {last_restart_iter}") if len(feasible_region.vertices) <= 1: return point_initial, initial_eta, initial_sigma, 0 # Precomputations matrix = np.vstack(feasible_region.vertices) base_quadratic = matrix.dot(matrix.T) # Initial shared buffers based on shared_buffers_dict if shared_buffers_dict: global_eta = shared_buffers_dict["global_eta"] global_sigma = shared_buffers_dict["global_sigma"] ret_x_cartesian_coordinates_shm = shared_memory.SharedMemory( name=shared_buffers_dict["ret_x_cartesian_coordinates"]) ret_x_cartesian_coordinates = np.ndarray( shape=objective_function.dim, dtype=np.float64, buffer=ret_x_cartesian_coordinates_shm.buf, ) ret_x_barycentric_coordinates_shm = shared_memory.SharedMemory( name=shared_buffers_dict["ret_x_barycentric_coordinates"]) ret_x_barycentric_coordinates = np.ndarray( shape=len(feasible_region.vertices), dtype=np.float64, buffer=ret_x_barycentric_coordinates_shm.buf, ) global_iter = shared_buffers_dict["global_iter"] ACC_paused_flag = shared_buffers_dict["ACC_paused_flag"] buffer_lock = shared_buffers_dict["buffer_lock"] else: global_eta, global_sigma = None, None # Initializtion point_x = point_initial if np.allclose(point_x.cartesian_coordinates, point_x.support[0]): point_y = Point( point_x.support[1], [1.0 if i == 1 else 0.0 for i in range(len(point_x.support))], point_x.support, ) else: point_y = Point( point_x.support[0], [1.0 if i == 0 else 0.0 for i in range(len(point_x.support))], point_x.support, ) # Guess a eta if initial_sigma is None if initial_sigma is None or initial_sigma == 0.0: x = point_x.cartesian_coordinates y = point_y.cartesian_coordinates initial_sigma = ( 2.0 * (objective_function.evaluate(y) - objective_function.evaluate(x) - np.dot(objective_function.evaluate_grad(x), y - x)) / (np.linalg.norm(y - x)**2)) # Set initial_eta to initial_sigma if initial_eta is None if initial_eta is None or initial_eta == 0.0: initial_eta = initial_sigma eta = initial_eta sigma = initial_sigma LOGGER.info(f"initial_sigma = {initial_sigma}") LOGGER.info(f"initial_eta = {initial_eta}") iteration = 0 # Early return if primal gap is small strong_wolfe_gap = compute_strong_wolfe_gap(point_x, objective_function, feasible_region) if strong_wolfe_gap <= epsilon_f: LOGGER.info("Early halting ACC with wolfe_gap <= epsilon_f") if shared_buffers_dict: buffer_lock.acquire() global_eta.value = eta global_sigma.value = sigma buffer_lock.release() return point_x, eta, sigma, iteration point_x_plus = argmin_quadratic_over_active_set( quadratic_coefficient=eta / 2.0, linear_vector=(objective_function.evaluate_grad( point_x.cartesian_coordinates) - eta * point_x.cartesian_coordinates), active_set=feasible_region.vertices, point_reference=point_x, tolerance_type="gradient mapping", tolerance=eta / 32, base_quadratic=base_quadratic, ) grad_mapping = (point_x.cartesian_coordinates - point_x_plus.cartesian_coordinates) while np.linalg.norm( grad_mapping) > epsilon and strong_wolfe_gap > epsilon_f: ( point_x, grad_mapping, strong_wolfe_gap, eta, sigma, _iteration, ) = self.ACC_iter( objective_function, feasible_region, point_initial=point_x, eta=eta, sigma=sigma, global_eta=global_eta, global_sigma=global_sigma, base_quadratic=base_quadratic, epsilon_f=epsilon_f, ) iteration += _iteration LOGGER.info("ACC about to update buffer.") if shared_buffers_dict: buffer_lock.acquire() global_eta.value = eta global_sigma.value = sigma buffer_lock.release() while shared_buffers_dict: # if global_iter is None, then assume no iteration sync required. if global_iter: with global_iter.get_lock(): _global_iter = global_iter.value else: _global_iter = np.infty if _global_iter >= last_restart_iter + iteration or not self.iter_sync: # Update shared buffers buffer_lock.acquire() ret_x_cartesian_coordinates[:] = point_x.cartesian_coordinates[:] ret_x_barycentric_coordinates[:] = point_x.barycentric_coordinates[:] buffer_lock.release() # Continue with the next ACC with ACC_paused_flag.get_lock(): ACC_paused_flag.value = 0 break else: # Pausing ACC's execution and sleep for some time. with ACC_paused_flag.get_lock(): ACC_paused_flag.value = 1 time.sleep(WAIT_TIME_FOR_LOCK) if shared_buffers_dict: buffer_lock.acquire() global_eta.value = eta global_sigma.value = sigma buffer_lock.release() return point_x, eta, sigma, iteration
def run( self, objective_function, feasible_region, exit_criterion, point_initial=None, ): """ Minimizing objective function over feasible region using PF-LaCG. Parameters ---------- objective_function: implemented _AbstractObjectiveFunction Objective function over which this algorithm optimizes. feasible_region: implemented _AbstractFeasibleRegion Feasible region over which this algorithm optimizes. exit_criterion: ExitCriterion Conditions required for it to halt the execution. point_initial: Point Initial point object. Returns ------- list(run_status) """ if point_initial is None: vertex = feasible_region.initial_point.copy() point_initial = Point(vertex, (1.0, ), (vertex, )) else: point_initial = point_initial # Initialization strong_wolfe_gap_out = compute_strong_wolfe_gap( point_initial, objective_function, feasible_region) iteration = 0 start_time = time.time() duration = 0.0 num_halvings = 0 f_val = objective_function.evaluate( point_initial.cartesian_coordinates) run_status = ( iteration, duration, f_val, 0.0, # Dummy dual gap since we are concerned about SWG here strong_wolfe_gap_out, ) run_history = [run_status] LOGGER.info( "Running PFLaCG: " "iteration = {1}, duration = {2:.{0}f}, " "f_val = {3:.{0}f}, dual_gap = {4:.{0}f}, SWG = {5:.{0}f}".format( DISPLAY_DECIMALS, *run_status)) point_x_FAFW = point_initial point_x_ACC = point_initial active_set_ACC = point_initial.support strong_wolfe_gap_FAFW = strong_wolfe_gap_out strong_wolfe_gap_ACC = strong_wolfe_gap_out eta = None sigma = None # Create new shared memory buffers and buffer lock ret_x_cartesian_coordinates_shm = shared_memory.SharedMemory( create=True, size=np.zeros(shape=objective_function.dim, dtype=np.float64).nbytes, ) ret_x_cartesian_coordinates = np.ndarray( shape=objective_function.dim, dtype=np.float64, buffer=ret_x_cartesian_coordinates_shm.buf, ) ret_x_cartesian_coordinates[:] = point_x_ACC.cartesian_coordinates[:] global_eta = Value("d", 0) global_sigma = Value("d", 0) ACC_paused_flag = Value("i", 0) buffer_lock = Lock() global_iter = Value("i", 0) ACC_restart_flag = True ACC_process_started = False while not exit_criterion.has_met_exit_criterion(run_status): # Set halving strong Wolfe gap target_accuracy = strong_wolfe_gap_FAFW * self.ratio LOGGER.info(f"SWG target accuracy = {target_accuracy}") num_halvings += 1 if ACC_restart_flag: LOGGER.info("Restarting ACC") ret_x_barycentric_coordinates_shm = shared_memory.SharedMemory( create=True, size=np.zeros(shape=len(active_set_ACC), dtype=np.float64).nbytes, ) ret_x_barycentric_coordinates = np.ndarray( shape=len(active_set_ACC), dtype=np.float64, buffer=ret_x_barycentric_coordinates_shm.buf, ) ret_x_barycentric_coordinates[:] = point_x_ACC.barycentric_coordinates[:] ret_x_cartesian_coordinates[:] = point_x_ACC.cartesian_coordinates[:] shared_buffers_dict = { "buffer_lock": buffer_lock, "global_iter": global_iter, "ACC_paused_flag": ACC_paused_flag, "global_eta": global_eta, "global_sigma": global_sigma, "ret_x_cartesian_coordinates": ret_x_cartesian_coordinates_shm.name, "ret_x_barycentric_coordinates": ret_x_barycentric_coordinates_shm.name, } LOGGER.info( f"Creating ACC process with set size {len(active_set_ACC)}" ) convex_hull_ACC = ConvexHull(active_set_ACC) ACC_process = Process( target=self.ACC.run, args=( objective_function, convex_hull_ACC, point_x_ACC, 0.0, eta, sigma, shared_buffers_dict, iteration, exit_criterion.criterion_value, ), ) if len(active_set_ACC) > 1: LOGGER.info("Starting ACC process") ACC_process.start() ACC_process_started = True LOGGER.info("Running FAFW") # Run FAFW and wait for the output point_x_FAFW, _, strong_wolfe_gap_FAFW = self.FAFW.run( objective_function, feasible_region, point_x_FAFW, target_accuracy=target_accuracy, global_iter=global_iter, ) with global_iter.get_lock(): _global_iter = global_iter.value LOGGER.info(f"FAFW returned at global_iter = {_global_iter}") # if iteration sync, need to wait for ACC to complete same #iterations num_wait_interval = 0 while self.iter_sync and ACC_process_started and ACC_process.is_alive( ): with ACC_paused_flag.get_lock(): _ACC_paused_flag = ACC_paused_flag.value if _ACC_paused_flag == 1: # ACC has paused (the buffer is been updated since last ACC restart) break else: LOGGER.info("Waiting for ACC") time.sleep(WAIT_TIME_FOR_LOCK) num_wait_interval += 1 if num_wait_interval > MAX_NUM_WAIT_INTERVALS: LOGGER.info("ACC timed out. Continuing with PFLaCG.") break LOGGER.info("Acquiring buffer") # retrieve the most recent output buffer_lock.acquire() point_x_ACC = Point( np.copy(ret_x_cartesian_coordinates), np.copy(ret_x_barycentric_coordinates), active_set_ACC, ) if ACC_process_started: sigma = global_sigma.value eta = global_eta.value buffer_lock.release() # Compute Strong Wolfe gap (or dual gap) strong_wolfe_gap_ACC_prev = strong_wolfe_gap_ACC strong_wolfe_gap_ACC = compute_strong_wolfe_gap( point_x_ACC, objective_function, feasible_region) if strong_wolfe_gap_FAFW <= min(strong_wolfe_gap_ACC, strong_wolfe_gap_ACC_prev / 2): # Terminate ACC process and set restart flag LOGGER.info("FAFW did better") if ACC_process_started and ACC_process.is_alive(): LOGGER.info("Terminating ACC") ACC_process.terminate() ACC_process.join() ACC_process_started = False with ACC_paused_flag.get_lock(): ACC_paused_flag.value = 0 ret_x_barycentric_coordinates_shm.close() ret_x_barycentric_coordinates_shm.unlink() ACC_restart_flag = True point_x_ACC = point_x_FAFW active_set_ACC = point_x_FAFW.support # Set output points point_x_out = point_x_FAFW strong_wolfe_gap_out = strong_wolfe_gap_FAFW else: LOGGER.info("ACC did better") LOGGER.info("Not terminating ACC") # Allow ACC to continue its execution ACC_restart_flag = False # Couple FAFW by using the better point from ACC if condition satisfies if len(point_x_ACC.support) <= len(point_x_FAFW.support): LOGGER.info("FAFW <- ACC") point_x_FAFW = point_x_ACC strong_wolfe_gap_FAFW = strong_wolfe_gap_ACC # Set output points point_x_out = point_x_ACC strong_wolfe_gap_out = strong_wolfe_gap_ACC # Append output points LOGGER.info("Outputting") with global_iter.get_lock(): iteration = global_iter.value duration = time.time() - start_time f_val = objective_function.evaluate( point_x_out.cartesian_coordinates) run_status = ( iteration, duration, f_val, 0.0, # Dummy dual gap since we are concerned about SWG here strong_wolfe_gap_out, ) LOGGER.info( "Running PFLaCG: " "iteration = {1}, duration = {2:.{0}f}," " f_val = {3:.{0}f}, dual_gap = {4:.{0}f}, SWG = {5:.{0}f}". format(DISPLAY_DECIMALS, *run_status)) run_history.append(run_status) # Cleaning up buffers and process ret_x_cartesian_coordinates_shm.close() ret_x_cartesian_coordinates_shm.unlink() if ACC_process_started and ACC_process.is_alive(): ACC_process.terminate() ACC_process.join() return run_history
def run( self, objective_function, feasible_region, exit_criterion, point_initial=None, save_and_output_results=True, global_iter=None, ): if point_initial is None: vertex = feasible_region.initial_point.copy() point_x = Point(vertex, (1.0,), (vertex,)) else: point_x = point_initial start_time = time.time() grad = objective_function.evaluate_grad(point_x.cartesian_coordinates) iteration = 0 duration = 0.0 f_val = objective_function.evaluate(point_x.cartesian_coordinates) v = feasible_region.lp_oracle(grad) if self.fw_variant == "FW" or self.fw_variant == "DIPFW": strong_wolfe_gap = 0.0 else: a, index_max = feasible_region.away_oracle(grad, point_x) strong_wolfe_gap = grad.dot(a.cartesian_coordinates - v) dual_gap = grad.dot(point_x.cartesian_coordinates - v) if self.fw_variant == "lazy" or self.fw_variant == "lazy quick exit": phi_val = [dual_gap] run_status = (iteration, duration, f_val, dual_gap, strong_wolfe_gap) if save_and_output_results: LOGGER.info( "Running " + str(self.fw_variant) + "({5}): " "iteration = {1:.{0}f}, duration = {2:.{0}f}," "f_val = {3:.{0}f}, dual_gap = {4:.{0}f}, strong_wolfe_gap = {5:.{0}f}".format( DISPLAY_DECIMALS, *run_status, self.fw_variant ) ) run_history = [run_status] while True: point_x_prev = point_x if self.fw_variant == "AFW": point_x, dual_gap_prev, strong_wolfe_gap_prev = away_step_fw( objective_function, feasible_region, point_x, self.step_size_param, ) if self.fw_variant == "PFW": point_x, dual_gap_prev, strong_wolfe_gap_prev = pairwise_step_fw( objective_function, feasible_region, point_x, self.step_size_param, ) if self.fw_variant == "FW": point_x, dual_gap_prev, strong_wolfe_gap_prev = step_fw( objective_function, feasible_region, point_x, self.step_size_param, ) if ( iteration % self.sampling_frequency == 0 and strong_wolfe_gap_prev is None ): grad = objective_function.evaluate_grad( point_x_prev.cartesian_coordinates ) v = feasible_region.lp_oracle(grad) point_a, indexMax = feasible_region.away_oracle(grad, point_x_prev) dual_gap_prev = grad.dot(point_x_prev.cartesian_coordinates - v) strong_wolfe_gap_prev = grad.dot(point_a.cartesian_coordinates - v) if self.fw_variant == "lazy": point_x, dual_gap_prev, strong_wolfe_gap_prev = fw_away_lazy( objective_function, feasible_region, point_x, self.step_size_param, phi_val, ) if iteration % self.sampling_frequency == 0 and ( strong_wolfe_gap_prev is None or dual_gap_prev is None ): grad = objective_function.evaluate_grad( point_x_prev.cartesian_coordinates ) v = feasible_region.lp_oracle(grad) point_a, indexMax = feasible_region.away_oracle(grad, point_x_prev) dual_gap_prev = grad.dot(point_x_prev.cartesian_coordinates - v) strong_wolfe_gap_prev = grad.dot(point_a.cartesian_coordinates - v) if self.fw_variant == "DIPFW": point_x, dual_gap_prev, strong_wolfe_gap_prev = dipfw( objective_function, feasible_region, point_x, self.step_size_param ) iteration += 1 duration = time.time() - start_time f_val = objective_function.evaluate(point_x.cartesian_coordinates) run_status = ( iteration, duration, f_val, dual_gap_prev, strong_wolfe_gap_prev, ) if exit_criterion.has_met_exit_criterion(run_status): break if global_iter: # Increment global iteration count with global_iter.get_lock(): global_iter.value += 1 if save_and_output_results: if dual_gap_prev is None or strong_wolfe_gap_prev is None: LOGGER.info( "Running " + str(self.fw_variant) + ": " "iteration = {1}, duration = {2:.{0}f}, " "f_val = {3:.{0}f}, dual_gap = None, strong_wolfe_gap = None".format( DISPLAY_DECIMALS, *run_status ) ) else: LOGGER.info( "Running " + str(self.fw_variant) + ": " "iteration = {1}, duration = {2:.{0}f}, " "f_val = {3:.{0}f}, dual_gap = {4:.{0}f}, strong_wolfe_gap = {5:.{0}f}".format( DISPLAY_DECIMALS, *run_status ) ) run_history.append(run_status) if save_and_output_results: return run_history else: return point_x_prev, dual_gap_prev, strong_wolfe_gap_prev
def fw_away_lazy( objective_function, feasible_region, point_x, step_size_param, phi_val, K=2.0 ): """Makes a lazy Frank-Wolfe step.""" grad = objective_function.evaluate_grad(point_x.cartesian_coordinates) point_a, index_max, point_v, index_min = point_x.max_min_vertices(grad) # Use old FW vertex. if ( np.dot(grad, point_x.cartesian_coordinates - point_v.cartesian_coordinates) >= np.dot(grad, point_a.cartesian_coordinates - point_x.cartesian_coordinates) and np.dot(grad, point_x.cartesian_coordinates - point_v.cartesian_coordinates) >= phi_val[0] / K ): alpha_max = 1.0 alpha = step_size( objective_function, point_x.cartesian_coordinates, point_v.cartesian_coordinates - point_x.cartesian_coordinates, grad, alpha_max, step_size_param, ) if alpha != alpha_max: return ( point_x + alpha * (point_v - point_x), None, None, ) else: return ( point_v, None, None, ) else: if ( np.dot(grad, point_a.cartesian_coordinates - point_x.cartesian_coordinates) > np.dot( grad, point_x.cartesian_coordinates - point_v.cartesian_coordinates ) and np.dot( grad, point_a.cartesian_coordinates - point_x.cartesian_coordinates ) >= phi_val[0] / K ): alpha_max = point_x.barycentric_coordinates[index_max] / ( 1.0 - point_x.barycentric_coordinates[index_max] ) alpha = step_size( objective_function, point_x.cartesian_coordinates, point_x.cartesian_coordinates - point_a.cartesian_coordinates, grad, alpha_max, step_size_param, ) point_x = point_x + alpha * (point_x - point_a) if alpha == alpha_max: point_x = point_x.delete_vertex_in_support(index_max) return ( point_x, None, None, ) else: v = feasible_region.lp_oracle(grad) strong_wolfe_gap = grad.dot(point_a.cartesian_coordinates - v) wolfe_gap = grad.dot(point_x.cartesian_coordinates - v) if np.dot(grad, point_x.cartesian_coordinates - v) >= phi_val[0] / K: flag, point_v = point_x.is_vertex_in_support(v) alpha_max = 1.0 alpha = step_size( objective_function, point_x.cartesian_coordinates, point_v.cartesian_coordinates - point_x.cartesian_coordinates, grad, alpha_max, step_size_param, ) if flag == False: new_barycentric_coordinates = list(point_x.barycentric_coordinates) new_barycentric_coordinates.append(0.0) point_x = Point( point_x.cartesian_coordinates, tuple(new_barycentric_coordinates), point_v.support, ) if alpha != alpha_max: return ( point_x + alpha * (point_v - point_x), wolfe_gap, strong_wolfe_gap, ) else: return ( Point(v, (1.0,), (v,)), wolfe_gap, strong_wolfe_gap, ) else: phi_val[0] = min( grad.dot(point_x.cartesian_coordinates - v), phi_val[0] / 2.0 ) return point_x, wolfe_gap, strong_wolfe_gap