-
Victor Ruelle authoredVictor Ruelle authored
cap_constraint.py 6.79 KiB
# -*- coding: utf-8 -*-
"""
Created on Tue Jun 26 13:39:47 2018
@author: GVKD1542
"""
from numpy.random import choice
import pyomo.environ as pyo
from numpy import ceil
from numpy import Infinity
from globals import *
from time import time
from itertools import combinations as comb
debug = False
###################
### ADDING CUTS ###
###################
successive_connectected_comp_fails = 0
def add_c_cap(instance):
start = len(instance.c_cap)
#1) connected components heuristic
success = False
# for i in range(len(connected_threshold.vals)):
# success_temp,components_single_demand_constrainted = add_c_cap_connectedcomps(instance)
# connected_threshold.update() #after each call to connectedcomps we update the associated threshold with a given strategy to optimise the next iteration's success
# success = success or success_temp
success,components_single_demand_constrainted = add_c_cap_connectedcomps(instance)
connected_threshold.update()
global successive_connectected_comp_fails
if not(success):
successive_connectected_comp_fails += 1
else:
successive_connectected_comp_fails = 0
if successive_connectected_comp_fails > len(connected_threshold.vals):
success = False
else:
success = True
#2 other heuristics if first one failed
#if not(success):
#...
return success, len(instance.c_cap)-start #second variable gives the number of cuts found during add_c_cap
##################
### HEURISTICS ###
##################
def add_c_cap_connectedcomps(instance):
if debug:
print()
print("starting connectedcomps heuristic")
print()
'''heurisitque des connected components
on calcule les routes connectées (hormis le dépot)
et on calcule les contraintes de capacité pour les
sous graphes ainsi générés ainsi que pour leur complémentaire
'''
count = 0 #number of cuts
comp = find_components(instance)
demand_of_non_connected_to_depot = 0
passage_of_non_connected_to_depot = 0
components_single_demand_constrainted = []
for c in comp:
demand = 0
passage = 0 #attention on a enlevé infini, si aucun passage il faut couper aussi non?
for node in c:
demand += instance.demands[node]
for i in instance.nodes:
if not(i in c):
passage += instance.x[max(node,i),min(node,i)]
lb = ceil(demand/instance.capacity)*2
if pyo.value(passage) < lb:
instance.c_cap.add(passage>=lb)
count+=1
if lb == 2 :
components_single_demand_constrainted.append(c) #will be used for next heuristic
if instance.x[c[-1],0].value<eps: # this component is not connected to the depot
demand_of_non_connected_to_depot += demand
passage_of_non_connected_to_depot += passage
c_complementary = [i for i in instance.nodes if not(i in c+[0])] #0 ne doit pas être dedans!!
demand = 0
passage = 0
for node in c_complementary:
demand+= instance.demands[node]
for i in instance.nodes:
if not(i in c_complementary):
passage += instance.x[max(node,i),min(node,i)]
lb = ceil(demand/instance.capacity)*2
if pyo.value(passage) < lb:
instance.c_cap.add(passage>=lb)
count+=1
if lb == 2 :
components_single_demand_constrainted.append(c_complementary) #will be used for next heuristic
lb = ceil(demand_of_non_connected_to_depot/instance.capacity)*2
if pyo.value(passage_of_non_connected_to_depot) < lb :
instance.c_cap.add(passage_of_non_connected_to_depot>= lb)
del(passage,demand,lb,passage_of_non_connected_to_depot,demand_of_non_connected_to_depot,comp)
return count>0, components_single_demand_constrainted
''' H2+H3+H4 :
H2 : adapted max-flow
1) we shrink the graph
rule : try sets S of cardinal 2,3 ; those whose lower bound value is 1 and that already have
a generated cap constraint ; those for which x(delta(S)) = 2 imposed during branching
2) max-flow problem with lower bound capacity inequalities
to find cuts (subtilty to find more than one cut
3) repeat 2) 3 times with previous cuts in memory
H3 : greedy construnction
1) shrinking
2)
H4 : ??
'''
###############
### SUPPORT ###
###############
''' CONNETED COMPONENTS CUTS SUPPORT '''
class Components:
def __init__(self,n):
self.r = [i for i in range(1,n)]
self.c = []
def free(self,i):
return i in self.r
def take(self,i,k):
self.r.remove(i)
self.c[k].append(i)
def find_components(instance):
comps = Components(instance.n.value)
for i in instance.nodes:
if comps.free(i) :
comps.c.append([])
k = len(comps.c)-1
explore_component(instance,i,comps,k)
return comps.c
def explore_component(instance,i,comps,k):
comps.take(i,k)
for j in comps.r:
if connected(instance,i,j):
explore_component(instance,j,comps,k)
def connected(instance,i,j):
a,b = max(i,j),min(i,j)
return instance.x[a,b].value>connected_threshold.value
''' GRAPH SHRINKING '''
def shrink_graph(instance,components_single_demand_constrainted):
#takes an instance and returns a new instance with shrunk support graph
#we chose to disable branches between nodes of a new super-node by fixing the value of the related x variable
#other option is : to set the value of the costs of the edge binding them to +inf
shrunk_instance = instance.clone()
start = time()
while time()-start < max_time_for_shrinking :
#we are going to start with sets of 2. Later implementations will look at sets of 3,
#those which need one truck and have had a capacity constraint generated
#those for which x(δ(S)) = 2 has been imposed during branching (in the later version of branching
for c in components_single_demand_constrainted :
c = c.sort(reverse=True) #we don't care about the order and sorting them will allow for easier indexing later on
if verify_safe_shrinking(shrunk_instance,c):
shrink_set(shrunk_instance,c)
i = choice(shrunk_instance.nodes)
j = choice(list(shrunk_instance.nodes)[:i])
if shrunk_instance.x[i,j].value>=1:
shrink_set(shrunk_instance,[(i,j)])
return shrunk_instance
def outgoing_passage(instance,set):
total = 2 * len(set)
for i,j in comb(set,2):
total -= instance.x[set[i],set[j]].value
return total
def verify_safe_shrinking(instance,set):
if len(set) > max_size_for_shrinking : #we don't want to have to check all subsets of sets that are too large
return False
if outgoing_passage(instance,set) > 2 :
return False
for i in range(2,len(set)):
sub_sets = com(set,i)
for sub in sub_sets :
if outgoing_passage(instance,sub) < 2:
return False
return True
def shrink_set(instance,set):
demand = outgoing_passage(instance,set)
super_node = set[0]
instance.demands[super_node] = demand
for node in instance.nodes:
if node not in set:
instance.costs[max(node,super_node),min(node,super_node)] = sum( instance.costs[max(node,i),min(node,i)] for i in set )
for node in set[1:]:
for node2 in instance.nodes:
instance.x[max(node,node2),min(node,node2)].value = 0
instance.x[max(node,node2),min(node,node2)].fixed = True