示例#1
0
def select_best_candidate(branching_candidates,initial_instance):
    # heuristics to select "best" candidate
    # we want to avoid having too many integer branchings to avoid too great calculation time. 

    # first ranking factor : minimum boost
    for candidate in branching_candidates:
        log.write_awaiting_answer(candidate["method"]+":" )
        for instance in candidate["instances"]:
            log.write_awaiting_answer(str(instance.id[-1])+", boost : "+str(instance.objective_value-initial_instance.objective_value))
        log.write_answer("")
    if len(branching_candidates)==1:
        return branching_candidates[0]
    for candidate in branching_candidates:
        for inst in candidate["instances"]:
            if inst.objective_value-initial_instance.objective_value<precision:
                branching_candidates.remove(candidate)
                break
    if len(branching_candidates)==1:
        return branching_candidates[0]
    clear_winner_thresh = 2
    min_boosts = {branching_candidate["method"]:(min( branching_candidate["instances"], key = lambda instance : instance.objective_value - initial_instance.objective_value ).objective_value-initial_instance.objective_value) for branching_candidate in branching_candidates}
    branching_candidates = sorted( branching_candidates, key = lambda candidate :   min_boosts[candidate["method"]], reverse = True ) #sorts them in decreasing order of smallest boost
    # we have at least 2
    try:
        if min_boosts[branching_candidates[0]["method"]] / min_boosts[branching_candidates[1]["method"]] >= clear_winner_thresh:
            log.write("biggest smallest boost is clear winner")
            return branching_candidates[0]
        # if we get here, the two "best" candidates are not distinguishable by their smallest boost ("best" as in biggest smallest boost)
        if len(branching_candidates) == 3 and min_boosts[branching_candidates[2]["method"]] / min_boosts[branching_candidates[0]["method"]] >= clear_winner_thresh: # checking if the last candidate holds a chance
            branching_candidates.pop(2)
    except ZeroDivisionError:
        raise Exception(str([inst.objective_value for inst in branching_candidates[0]["instances"]]) + " " + str([inst.objective_value for inst in branching_candidates[1]["instances"]]) + " " + str([inst.objective_value for inst in branching_candidates[2]["instances"]]) + " " + str(initial_instance.objective_value) )
    # at this point, all remaining candidates have a similar smallest boost, other factors must be taken into account 
    # second class ranking factors : number of instances created and their respective boost
    # we like having only one instance, but that considerably slows down solving process (integer fixing): we must find a trade-off
    branching_candidates = sorted( branching_candidates, key = lambda candidate : len(candidate["instances"]) )
    for candidate in branching_candidates:
        if len(candidate["instances"])==1 and candidate["method"]!= "integer branching":
            log.write("not intger branching with cut branches wins")
            return candidate # smallest amount of instances without slowing down solving time: perfect! (happens for instance when index branching has one of its branches unfeasible)
    m = sum( 1 for b in initial_instance.id if b==-1 ) # number of fixed integers
    if m<=6 and random()>0.6: #we can still fix integers without too much trouble
        for candidate in branching_candidates: 
            if len(candidate["instances"])==1:
                log.write("not too many integer branching")
                return candidate # this will always only return integer_branching if it is still in the remaining candidates
    # now we will simply assess if the remaining candidates have highly unbalanced instances, which is good. If so, we choose them, else we will simply take that with least amount of instances
    highly_unbalanced_candidates = []
    for candidate in branching_candidates:
        if len(candidate["instances"])>1 and max( (abs(candidate["instances"][j].objective_value-initial_instance.objective_value))/max((candidate["instances"][i].objective_value-initial_instance.objective_value),0.00001) for i in range(len(candidate["instances"])) for j in range(len(candidate["instances"])) if i!=j ) > 10 :
            highly_unbalanced_candidates.append(candidate)
    if len(highly_unbalanced_candidates)==1:
        log.write("unique highly unbalanced is winner")
        return highly_unbalanced_candidates[0]
    if len(highly_unbalanced_candidates)>=2: #can only be equal to 1 or 2
        log.write("smallest highly unbalanced is winner")
        return min( highly_unbalanced_candidates, key = lambda candidate : len(candidate["instances"]) )
    log.write("smallest balanced is winner")
    return branching_candidates[0]
示例#2
0
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
示例#3
0
def cpp_constraint_branching_candidate(instance,branching_strategy=constraint_branching_strategy):
    NoOfCustomers,Demand,CAP,NoOfEdges,EdgeX,EdgeHead,EdgeTail,QMin = cpp.convert_for_cvrpsep(instance)
    sets = cpp.cvrpsep.branching(NoOfCustomers,Demand,CAP,NoOfEdges,EdgeX,EdgeHead,EdgeTail)
    sets = order_best_sets(sets,instance)
    if sets == []:
        log.write_answer("no branching set were found")
        return {"instances":None,"score":None, "method":"constraint branching"}

    log.write_awaiting_answer("candidate sets: "+str(len(sets)))

    if branching_strategy == "simple":
        branch_set = sets[0]
        instance.branching_sets.append(branch_set)
        log.write_awaiting_answer(", best set: "+str(branch_set)+", demand: "+str(round(sum(instance.demands[i] for i in branch_set)/instance.capacity.value,4))+", coboundary: "+str(coboundary(branch_set,instance))) 
        candidate = branch_over_constraint(branch_set,instance)
        if candidate == []:
            return {"instances":None,"score":None, "method":"constraint branching"}
        increment_depth_and_id(candidate,"constraint branching")
        return {"instances":candidate,"score":candidate_score(candidate,instance.objective_value), "method":"constraint branching"}

    if branching_strategy == "exhaustive":
        branch_sets = sets[0:max_constraint_branching_candidates]
        best_candidate,best_set,best_score = [],0,0
        for branch_set in branch_sets:
            candidate = branch_over_constraint(branch_set,instance)
            score = candidate_score(candidate,instance.objective_value)
            if score > best_score:
                best_candidate,best_set,best_score = candidate,branch_set,score
            else:
                for inst in candidate:
                    del(inst)
        if best_candidate == []:
            log.write_answer("no branching over constraint could be found")
            return {"instances":None,"score":None, "method":"constraint branching"}
        for inst in best_candidate:
            inst.branching_sets.append(best_set)
        increment_depth_and_id(best_candidate,"constraint branching")
        log.write_awaiting_answer("branching set: "+str(best_set)+", demand: "+str(round(sum(instance.demands[i] for i in branch_set)/instance.capacity.value,4))+", coboundary: "+str(coboundary(best_set,instance)))
        return {"instances":best_candidate, "score":best_score, "method":"constraint branching"}
    
    raise NameError("enter a valid constraint_branching_strategy") 
示例#4
0
def integer_branching_candidates(instance,strategy = integer_branching_strategy):
    # returns candidates for integer_branching
    indexes = order_best_indexes(instance)
    if len(indexes)==0:
        return {"instances":None,"score":None, "method":"integer branching"}
    if strategy == "simple": 
        index = indexes[0]
        log.write_awaiting_answer("branching index: "+str(index)+", value "+str(round(instance.x[index].value,4))) 
        instance0 = instance.clone()
        instance0.branching_indexes.append(index)
        instance0.x[index].domain = pyo.NonNegativeIntegers
        if not(solve(instance0,silent=True)):
            return {"instances":None,"score":None, "method":"integer branching"}
        candidate = [instance0]
        score = candidate_score(candidate,instance.objective_value)
        increment_depth_and_id(candidate,"integer branching")
        return {"instances":candidate, "score":score, "method":"integer branching"}

    if strategy == "exhaustive":
        candidate_indexes = indexes[:max_index_branching_candidates] 
        best_score,best_instance,best_index = 0,None, (-1,-1)
        for ind in candidate_indexes:
            instance0 = instance.clone()
            instance0.x[ind].domain = pyo.NonNegativeIntegers
            if not(solve(instance0,silent=True)):
                continue
            score = candidate_score([instance0],instance.objective_value)
            if score > best_score:
                best_score,best_instance,best_index = score,instance0.clone(),ind
            del(instance0)
        if best_index == (-1,-1): #should never happen
            log.write_answer("no branching index found, branch must be cut")
            return {"instances":None,"score":None, "method":"integer branching"}
        log.write_awaiting_answer("branching index: "+str(best_index)+", value "+str(round(instance.x[best_index].value,4))) 
        best_instance.branching_indexes.append(best_index)
        increment_depth_and_id([best_instance],"integer branching")
        return {"instances":[best_instance],"score":best_score, "method":"integer branching"}
示例#5
0
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