Skip to content
Snippets Groups Projects
cap_constraint.py 6.79 KiB
Newer Older
Victor Ruelle's avatar
Victor Ruelle committed
# -*- 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
Victor Ruelle's avatar
Victor Ruelle committed

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):
		#...
Victor Ruelle's avatar
Victor Ruelle committed

	return success, len(instance.c_cap)-start #second variable gives the number of cuts found during add_c_cap
Victor Ruelle's avatar
Victor Ruelle committed



##################
### 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