def branch(instance,instance_manager): #branches instance problem into complementary sub problem instances following various rules #adds new instances that have been correctly initialised to instance_manager #returns success of branching log.subtitle("entering branching",instance.id) initial_time = time() branching_candidates = [] if index_branching: partial_time = time() log.write_awaiting_answer("index branching--> ") candidate = index_branching_candidates(instance) if candidate["instances"] != None: branching_candidates.append(candidate) log.write_answer(", score: "+str(branching_candidates[-1]["score"])+", time: "+str(round(time()-partial_time,2))) log.write_answer(", score: "+str(branching_candidates[-1]["score"])+", time: "+str(round(time()-partial_time,2))) if integer_branching: partial_time = time() log.write_awaiting_answer("integer branching--> ") candidate = integer_branching_candidates(instance) if candidate["instances"] != None: branching_candidates.append(candidate) log.write(", score: "+str(branching_candidates[-1]["score"])+", time: "+str(round(time()-partial_time,2))) if cpp_constraint_branching: partial_time = time() log.write_awaiting_answer("c++ constraint branching--> ") candidate = cpp_constraint_branching_candidate(instance) if candidate["instances"] != None: branching_candidates.append(candidate) log.write(", score: "+str(branching_candidates[-1]["score"])+", time: "+str(round(time()-partial_time,2))) if branching_candidates==[]: return False #coefficients = {"integer branching":1.1,"constraint branching":0.8,"index branching":1} #the bigger the coefficient, the lyklier it will be selected #best_candidate = sorted( branching_candidates, key = lambda candidate : candidate["score"]*coefficients[candidate["method"]], reverse=True )[0] best_candidate = select_best_candidate(branching_candidates,instance) log.write("BEST STRATEGY: "+str(best_candidate["method"])) #adding new instances to solving queue for instance in best_candidate["instances"]: set_lower_bound(instance) log.write("value of objective function is " +str(round(instance.objective_value,2)) + " for new instance " +list_to_string(instance.id)+" with lower bound "+str(instance.lower_bound)[:-1]) instance_manager.add(instance) log.end_subtitle("end of branching, method: "+best_candidate["method"]+", smallest boost: "+str(best_candidate["score"]) +", time: "+str(round(time()-initial_time,2))+" seconds") return True
def column_generation_loop(instance, instance_manager): # a signle loop of column generation : adds all available types of cuts to the instance # also verifies if a feasible integer solution is found #0) check if solution is integer and has valid paths if managing.solution_is_integer(instance): instance0 = instance.clone() managing.integerize_solution(instance0) if paths_are_feasible(instance0): log.subtitle( "!!!!! feasible integer solution found with objective value of " + str(round(instance0.objective_value, 2)), instance0.id) assertion = " " if instance_manager.upper_bound > instance0.objective_value else " not " log.write( "new solution is" + assertion + "better than one previously found, this branch is dropped and its solution is" + assertion + "recorded") instance_manager.record_feasible_integer_solution(instance0) return True, False if column_input == "python": raise Exception( "hand implemented heuristics have been commented out in cap_constraint.py for more clarity in code reading, these methods are outdated in comparaison to the c++ library. If you whish to test them, uncomment them and comment this exception out" ) #1) adding capacity cuts #success, count = cap.add_c_cap(instance) #log.write_awaiting_answer("capacity cuts: " +str(count)) #2),3),4) ... # other types of cuts to be added elif column_input == "c++": success = cpp.generate_and_add_columns(instance) else: raise NameError("Enter a valide column input type") remove_inactive_constraints(instance) return False, success
def initialize_instance(instance): # takes an instsanciated pyomo concrete model and adds all the custom attributes that will be used later in the process # among others things it will : define the variables, add the constraint containers, delete the variables that are not usefull (we only keep the strictly inferior trianble of the X matrix), # delete the locations matrix for it is not necessary, we keep it as a seperate variable that will be used only by the graphing functions if instance.entry_type.value == "COORD": ''' we need to retrieve the locations and delete them from all instances for smaller size''' locations = to_list_locations( instance) #global parameter used for graphing of solutions del (instance.locations) # WORKS! else: #entry type is necessarily "WEIGHT" ''' we need to generate random coordinates for the plotting ''' locations = [(random() * 20, random() * 20) for i in range(instance.n.value)] max_cost = 0 for k in instance.costs.values(): if k > max_cost: max_cost = k instance.max_cost = max_cost #managing.normalize_costs(instance) ''' define variable x ''' instance.x = pyo.Var(instance.nodes, instance.nodes, bounds=set_bounds) #deleting uneused variables (i<=j) for i in instance.nodes: for j in instance.nodes: if i <= j: del (instance.x[i, j]) # instance.x[i,j].deactivate() ''' define flow variable ''' instance.flow = pyo.Var(instance.nodes, instance.nodes, bounds=set_bounds_flow) ''' define the objective function ''' instance.objective = pyo.Objective(expr=sum( instance.costs[i, j] * instance.x[i, j] for i in instance.nodes for j in instance.nodes if i > j)) ''' define degree constraints ''' instance.c_deg = pyo.Constraint(instance.nodes, rule=rule_deg) ''' define flow constraints ''' if flow_constraints: instance.c_flow = pyo.Constraint(instance.nodes, rule=rule_flow) instance.c_flow_deg = pyo.Constraint(instance.nodes, instance.nodes, rule=rule_flow_deg) instance.c_flow_deg_indirect = pyo.Constraint( instance.nodes, instance.nodes, rule=rule_flow_deg_indirect) ''' define capacity constraints as an empty list for now ''' instance.c_cap = pyo.ConstraintList( ) # on utilise cette structure pour pouvoir ajouter et supprimer des contraintes par la suite instance.c_mstar = pyo.ConstraintList() instance.c_glm = pyo.ConstraintList() instance.c_fci = pyo.ConstraintList() instance.c_sci = pyo.ConstraintList() instance.c_hti = pyo.ConstraintList() #liste vide pour commencer ''' defining dual values ''' instance.dual = pyo.Suffix(direction=pyo.Suffix.IMPORT) ''' definig list of values that will be fixed by branching (used for problem reduction) ''' ''' and the list of constraints used for branching if constraint branching is used ''' instance.branching_indexes = [] if cpp_constraint_branching: instance.c_branch = pyo.ConstraintList() instance.branching_sets = [] if not (index_branching) and not (integer_branching) and not ( cpp_constraint_branching): log.subtitle( "instance generation failed because no valid branching type is given" ) raise NameError("enter a valid branching type in globals settings") ''' defining wether or not the instance can be subject to constraint deactivation ''' if disable_constraint_deactivation or integer_branching or integer_simplifications: log.write( "disabling constraint deactivation because integer variables are activated (integer branching or simplifications)" ) instance.disable_constraint_deactivation = True instance.constraints_inactivity = {} else: instance.disable_constraint_deactivation = False instance.constraints_inactivity = {} ''' defining others parameters based on those in global.py ''' instance.reduction = 1 instance.id = [0] instance.depth = 0 if reduce_problem: managing.reduce_problem(instance, initial_problem_reduction) #managing.reduce_problem_neighboors(instance,5) if force_integer_to_depot: managing.integerize_edges_to_depot(instance) log.write("integerized " + str(instance.n.value) + " edges to depot " + str(len(list(instance.x.keys()))) + " variables") if force_all_edges_integer: managing.integerize_all_edges(instance) if force_closest_neighboors_integer: integerized = managing.integerize_edges( instance, max_dist=relative_distance_closest_neighboor) log.write("integerized " + str(len(integerized)) + " shortest edges out of " + str(len(list(instance.x.keys()))) + " variables with a relative distance of " + str(relative_distance_closest_neighboor * 100) + '%' + " of max_distance") '''solving the initial instance in order to initialize instance.x values ''' opt.solve(instance) instance.objective_value = pyo.value( instance.objective ) #recording this as attribute to save computation time log.write_timed("finished constructing instance model") return instance, locations
log.write("initial upper bound of cvrp problem is "+str(instance_manager.upper_bound)) ''' printing initial value of objective function ''' log.write("initial value of objective function "+str(round(instance.objective_value,2))+" and is "+str(managing.integer_percent(instance))+"% integer") '''saving initial graph ''' full_graph(instance,locations,"initial") ''' list of old instances used for the graping of the search Tree ''' old_nodes = [] ''' actual branch and cut ''' instance = instance_manager.pop() while instance!=None and max_time_not_reached() and instance.depth<=max_depth: log.subtitle("starting processing of new instance with lower_bound of "+str(instance.lower_bound)+" ( upper_bound is "+str(instance_manager.upper_bound)+") ,depth: "+ str(instance.depth) +", global time: "+str(global_time()),instance.id) if search_tree == "complete" or search_tree == "end": old_nodes.append(instance.clone()) if search_tree == "complete": SearchTree(instance_manager,old_nodes).show() if queue_logging: queue_log.write(instance_manager) if graph_current_solution: full_graph(instance,locations,"current solution",True) if last_push_integer and (instance_manager.upper_bound - instance.objective_value)/instance_manager.upper_bound <= last_push_integer_thresh : instance.disable_constraint_deactivation = True integerized = managing.integerize_edges(instance,smart = True) log.subtitle("instance is close enough to upper bound "+ str(round((1-(instance_manager.upper_bound - instance.objective_value)/instance_manager.upper_bound)*100,2)) +"% : we integerize "+str(len(integerized))+" variables",instance.id)
def column_generation(instance, instance_manager): # general method that will apply all available cut finding algorithms in a loop whose parameters are defined in globals.py log.subtitle("entering column generation", instance.id) initial_time, feasible_integer_found, loop_count, unmoving_count, solver_success, failure_count, obj_val_old, obj_val_old_global = round( time(), 2 ), False, 1, 0, True, 0, instance.objective_value, instance.objective_value while instance.objective_value < instance_manager.upper_bound and loop_count <= ( max_column_generation_count if len(instance.id) > 1 else max_column_generation_count_first_instance) and unmoving_count <= ( max_unmoving_count if len(instance.id) > 1 else max_unmoving_count_first_instance) and not ( feasible_integer_found) and solver_success: log.write_awaiting_answer("loop " + str(loop_count) + "--> ", instance.id) # adding cuts feasible_integer_found, cuts_found = column_generation_loop( instance, instance_manager) #solving updated instance if cuts_found: solver_success = managing.solve(instance) #different cases are verified # we verify if the adding cuts method is a failure if not (cuts_found) and not (feasible_integer_found): log.write_awaiting_answer("all heurisitcs have failed") if not (feasible_integer_found): log.write_answer("improvement: " + str( round( 100 * (instance.objective_value - obj_val_old) / obj_val_old, 5)) + '%' + ", objective: " + str(round(instance.objective_value, 5)) + ", " + str(managing.integer_percent(instance)) + '%' + " integer, unmoving count:" + str(unmoving_count) + ", reduction: " + str(instance.reduction)) # we verify if the objective value is increasing, if not : the cuts found have no effect if (instance.objective_value - obj_val_old) / obj_val_old < unmoving_constraint_threshold: unmoving_count += 1 if unmoving_count >= (max_unmoving_count if instance.depth > 0 else max_unmoving_count_first_instance): log.write( "no evolution in column_generation, moving on to branching", instance.id) break else: unmoving_count = 0 # we verifiy if the objective value is decreasing : if it is, we have probably dropped constraints that were usefull if (instance.objective_value - obj_val_old) / obj_val_old < -0.0001: log.write( "we are losing objective function value! useful constraints were dropped", instance.id) # we count the number of consecutive total failure if not (cuts_found): failure_count += 1 if failure_count > max_failure_count_cuts: log.write( "no evolution in column_generation, moving on to branching", instance.id) break else: failure_count = 0 obj_val_old = instance.objective_value loop_count += 1 log.end_subtitle( "end of column generation, gain " + str(instance.objective_value - obj_val_old_global) + ", objective: " + str(instance.objective_value) + ", time: " + str(round(time() - initial_time, 2)), instance.id) return feasible_integer_found, solver_success