Skip to content
Snippets Groups Projects
cvrpGoogleOpt.py 13.5 KiB
Newer Older
Victor Ruelle's avatar
Victor Ruelle committed
"""Capacitated Vehicle Routing Problem"""
from __future__ import print_function
from six.moves import xrange
from ortools.constraint_solver import pywrapcp
from ortools.constraint_solver import routing_enums_pb2
from math import sqrt
import time
from globals import *
from support import pprint

#calculating upper_bound of problem

###########################
# Problem Data Definition #
###########################
class Vehicle():
	"""Stores the property of a vehicle"""
	def __init__(self,cap):
		"""Initializes the vehicle properties"""
		self._capacity = cap

	@property
	def capacity(self):
		"""Gets vehicle capacity"""
		return self._capacity

class DataProblem():
	"""Stores the data for the problem"""
	def __init__(self,instance_and_locations,n=-1,k=-1):
		if type(instance_and_locations)==int:
			dimension, capacity, number_of_vehicles, locations, demands = import_data(n,k)
			self._vehicle = Vehicle(capacity)
			self._num_vehicles = number_of_vehicles
			self._locations = locations
			self._depot = 0
			self._demands = demands

		else:
			self._vehicle = Vehicle(instance_and_locations[0].capacity.value)
			self._num_vehicles = instance_and_locations[0].number_of_vehicles.value
			self._locations = instance_and_locations[1]
			self._depot = 0
			self._demands = instance_and_locations[0].demands

	@property
	def vehicle(self):
		"""Gets a vehicle"""
		return self._vehicle

	@property
	def num_vehicles(self):
		"""Gets number of vehicles"""
		return self._num_vehicles

	@property
	def locations(self):
		"""Gets locations"""
		return self._locations

	@property
	def num_locations(self):
		"""Gets number of locations"""
		return len(self.locations)

	@property
	def depot(self):
		"""Gets depot location index"""
		return self._depot

	@property
	def demands(self):
		"""Gets demands at each location"""
		return self._demands

#######################
# Problem Constraints #
#######################

def euclidian_distance(position_1, position_2):
	return sqrt(pow(position_1[0]-position_2[0],2) + pow(position_1[1]-position_2[1],2))

class CreateDistanceEvaluator(object): # pylint: disable=too-few-public-methods
	"""Creates callback to return distance between points."""
	def __init__(self, data):
		"""Initializes the distance matrix."""
		self._distances = {}

		# precompute distance between location to have distance callback in O(1)
		for from_node in xrange(data.num_locations):
			self._distances[from_node] = {}
			for to_node in xrange(data.num_locations):
				if from_node == to_node:
					self._distances[from_node][to_node] = 0
				else:
					self._distances[from_node][to_node] = (
						euclidian_distance(
							data.locations[from_node],
							data.locations[to_node]))

	def distance_evaluator(self, from_node, to_node):
		"""Returns the manhattan distance between the two nodes"""
		return self._distances[from_node][to_node]

class CreateDemandEvaluator(object): # pylint: disable=too-few-public-methods
	"""Creates callback to get demands at each location."""
	def __init__(self, data):
		"""Initializes the demand array."""
		self._demands = data.demands

	def demand_evaluator(self, from_node, to_node):
		"""Returns the demand of the current node"""
		del to_node
		return self._demands[from_node]

def add_capacity_constraints(routing, data, demand_evaluator):
	"""Adds capacity constraint"""
	capacity = "Capacity"
	routing.AddDimension(
		demand_evaluator,
		0, # null capacity slack
		data.vehicle.capacity, # vehicle maximum capacity
		True, # start cumul to zero
		capacity)

		
def calculate_distance(data,routing,assignment):
	total_dist = 0
	for vehicle_id in xrange(data.num_vehicles):
		index = routing.Start(vehicle_id)
		route_dist = 0
		while not routing.IsEnd(index):
			node_index = routing.IndexToNode(index)
			next_node_index = routing.IndexToNode(assignment.Value(routing.NextVar(index)))
			route_dist += euclidian_distance(data.locations[node_index],data.locations[next_node_index])
			index = assignment.Value(routing.NextVar(index))
		node_index = routing.IndexToNode(index)
		total_dist += route_dist
	return total_dist
	
'''
###########
# Printer #
###########
class ConsolePrinter():
	"""Print solution to console"""
	def __init__(self, data, routing, assignment):
		"""Initializes the printer"""
		self._data = data
		self._routing = routing
		self._assignment = assignment

	@property
	def data(self):
		"""Gets problem data"""
		return self._data

	@property
	def routing(self):
		"""Gets routing model"""
		return self._routing

	@property
	def assignment(self):
		"""Gets routing model"""
		return self._assignment

	def print(self):
		"""Prints assignment on console"""
		# Inspect solution.
		total_dist = 0
		for vehicle_id in xrange(self.data.num_vehicles):
			index = self.routing.Start(vehicle_id)
			#plan_output = 'Route for vehicle {0}:\n'.format(vehicle_id)
			route_dist = 0
			route_load = 0
			while not self.routing.IsEnd(index):
				node_index = self.routing.IndexToNode(index)
				next_node_index = self.routing.IndexToNode(
					self.assignment.Value(self.routing.NextVar(index)))
				route_dist += euclidian_distance(
					self.data.locations[node_index],
					self.data.locations[next_node_index])
				route_load += self.data.demands[node_index]
				#plan_output += ' {0} Load({1}) -> '.format(node_index, route_load)
				index = self.assignment.Value(self.routing.NextVar(index))

			node_index = self.routing.IndexToNode(index)
			total_dist += route_dist
			#plan_output += ' {0} Load({1})\n'.format(node_index, route_load)
			#plan_output += 'Distance of the route: {0}m\n'.format(route_dist)
			#plan_output += 'Load of the route: {0}\n'.format(route_load)
			#print(plan_output)
		print('Total Distance of all routes: {0}m'.format(total_dist))
'''
def import_data(n,k):
	file = "M-n"+str(n)+"-k"+str(k)+".vrp"
	with open("C:\\Users\\GVKD1542\\Documents\\python\\Vrp-Set-X\\X\\"+file,"r") as f:
		"""reading .dat data file"""
		lines = f.readlines()	
		#values are seperated either by space or indent
		sep = ' ' if ' ' in lines[7] else '\t'		
		#number of vehicles
		line = lines[0]
		line = line.split(":")
		line = line[1]
		line = line.split("-")
		word = line[2]
		number_of_vehicles = int(sanitze(word[1:]))			
		#dimension
		line = lines[3]
		line = line.split(":")
		dimension = int(sanitze(line[1]))			
		#capacity
		line = lines[5]
		line = line.split(":")
		capacity = int(sanitze(line[1]))			
		locations_start_index = 7
		demands_start_index = locations_start_index + dimension + 1 #skip over title			
		#locations
		locations = {}
		for i in range(dimension):
			line = lines[locations_start_index+i]
			line = line.split(sep)
			try:
				n,i,j = int(sanitze(line[0]))-1,int(sanitze(line[1])),int(sanitze(line[2]))#dans les fichiers vrp, l'indexation commence à 1
			except IndexError:
				print("mauvaise indentation dans locations avec : ")
				print(line)
				print(n)
				print(i)
				print(j)
				print(len(line))
			locations[n] = i,j
		
		#demands
		demands = []
		for i in range(dimension):
			line = lines[demands_start_index+i]
			line = line.split(sep)
			try :
				demands.append(int(sanitze(line[1])))
			except ValueError:
				print("error while adding demands with : ")
				print(demands_start_index+i)
				print(line)
				print(line[1])
				print(sanitze(line[1]))
	return dimension, capacity, number_of_vehicles, locations, demands

def sanitze(str):
	chars = ['\t','\n',";"]
	for c in chars:
		while c in str:
			i = str.index(c)
			try :
				str = str[:i]+str[i+len(c):]
			except IndexError:
				print("sanitze out of range with : ")
				print(str + "c = " + c + " i = "+ str(i) + "len = " + str(len(c)) )
	return str

########
# Main #
########


def upper_bound(instance,locations):
	"""Entry point of the program"""
	# Instantiate the data problem.
	data = DataProblem((instance,locations))
	model_parameters = pywrapcp.RoutingModel.DefaultModelParameters()
	# Create Routing Model
	routing = pywrapcp.RoutingModel(data.num_locations, data.num_vehicles, data.depot,model_parameters)
	# Define weight of each edge
	distance_evaluator = CreateDistanceEvaluator(data).distance_evaluator
	routing.SetArcCostEvaluatorOfAllVehicles(distance_evaluator)
	# Add Capacity constraint
	demand_evaluator = CreateDemandEvaluator(data).demand_evaluator
	add_capacity_constraints(routing, data, demand_evaluator)
	# Setting first solution heuristic (cheapest addition).
	search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters()	
	search_parameters.first_solution_strategy = (
		routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
	# Disabling Large Neighborhood Search, (this is the default behaviour)
	search_parameters.time_limit_ms = 3000
	search_parameters.local_search_operators.use_path_lns = False
	search_parameters.local_search_operators.use_inactive_lns = False
	# Routing: forbids use of TSPOpt neighborhood,
	search_parameters.local_search_operators.use_tsp_opt = False	
	search_parameters.use_light_propagation = False
	# Solve the problem.
	assignment = routing.SolveWithParameters(search_parameters)
	t = type(empty())
	count = 0
	while type(assignment)==t:
		data._num_vehicles += 1 
		count +=1
		routing = pywrapcp.RoutingModel(data.num_locations, data.num_vehicles, data.depot,model_parameters)
		routing.SetArcCostEvaluatorOfAllVehicles(distance_evaluator)
		add_capacity_constraints(routing, data, demand_evaluator)
		assignment = routing.SolveWithParameters(search_parameters) 
	return calculate_distance(data, routing, assignment)

def lower_bound(instance,locations):
	"""Entry point of the program"""
	# Instantiate the data problem.
	data = DataProblem((instance,locations))
	model_parameters = pywrapcp.RoutingModel.DefaultModelParameters()
	# Create Routing Model
	routing = pywrapcp.RoutingModel(data.num_locations, data.num_vehicles, data.depot,model_parameters)
	# Define weight of each edge
	distance_evaluator = CreateDistanceEvaluator(data).distance_evaluator
	routing.SetArcCostEvaluatorOfAllVehicles(distance_evaluator)
	# Add Capacity constraint
	demand_evaluator = CreateDemandEvaluator(data).demand_evaluator
	# add_capacity_constraints(routing, data, demand_evaluator)
	# Setting first solution heuristic (cheapest addition).
	search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters()	
	search_parameters.first_solution_strategy = (
		routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
	# Disabling Large Neighborhood Search, (this is the default behaviour)
	search_parameters.time_limit_ms = 3000
	search_parameters.local_search_operators.use_path_lns = False
	search_parameters.local_search_operators.use_inactive_lns = False
	# Routing: forbids use of TSPOpt neighborhood,
	search_parameters.local_search_operators.use_tsp_opt = False	
	search_parameters.use_light_propagation = False
	# Solve the problem.
	assignment = routing.SolveWithParameters(search_parameters)
	t = type(empty())
	count = 0
	while type(assignment)==t:
		data._num_vehicles += 1 
		count +=1
		routing = pywrapcp.RoutingModel(data.num_locations, data.num_vehicles, data.depot,model_parameters)
		routing.SetArcCostEvaluatorOfAllVehicles(distance_evaluator)
		add_capacity_constraints(routing, data, demand_evaluator)
		assignment = routing.SolveWithParameters(search_parameters) 
	return calculate_distance(data, routing, assignment)


def main(n,k):
	"""Entry point of the program"""
	# Instantiate the data problem.
	data = DataProblem(-1,n,k)
	start = time.time()
	model_parameters = pywrapcp.RoutingModel.DefaultModelParameters()
	# Create Routing Model
	routing = pywrapcp.RoutingModel(data.num_locations, data.num_vehicles, data.depot,model_parameters)
	# Define weight of each edge
	distance_evaluator = CreateDistanceEvaluator(data).distance_evaluator
	routing.SetArcCostEvaluatorOfAllVehicles(distance_evaluator)
	# Add Capacity constraint
	demand_evaluator = CreateDemandEvaluator(data).demand_evaluator
	add_capacity_constraints(routing, data, demand_evaluator)
	# Setting first solution heuristic (cheapest addition).
	search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters()	
	search_parameters.first_solution_strategy = (
		routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
	search_parameters.local_search_metaheuristic = (
		routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)
	# Disabling Large Neighborhood Search, (this is the default behaviour)
	search_parameters.local_search_operators.use_path_lns = False
	search_parameters.local_search_operators.use_inactive_lns = False
	# Routing: forbids use of TSPOpt neighborhood,
	search_parameters.local_search_operators.use_tsp_opt = False	
	search_parameters.use_light_propagation = False
	# Solve the problem.
	search_parameters.time_limit_ms = 1000*1000
	# search_parameters.solution_limit = 1
	assignment = routing.SolveWithParameters(search_parameters) 
	t = type(empty())
	count = 0
	while type(assignment)==t:
		data._num_vehicles += 1 
		count +=1
		routing = pywrapcp.RoutingModel(data.num_locations, data.num_vehicles, data.depot,model_parameters)
		routing.SetArcCostEvaluatorOfAllVehicles(distance_evaluator)
		add_capacity_constraints(routing, data, demand_evaluator)
		assignment = routing.SolveWithParameters(search_parameters) 
	print(calculate_distance(data, routing, assignment))

	pprint("solved upper_bound estimate after adding "+str(count)+" vehicles to solve problem")
	# printer = ConsolePrinter(data, routing, assignment)
	# printer.print()
	# print("elapsed time : " + str(round(time.time()-start,2)))

def empty():
	return 

if __name__ == '__main__':
	# list = [(101,25),(125,30),(190,8),(110,13),(106,14)]
	# for n,k in list:
		# main(n,k)
		# in_name = "X-n"+str(n)+"-k"+str(k)+".sol"
		# with open("C:\\Users\\GVKD1542\\Documents\\python\\Vrp-Set-X\\X\\"+in_name,"r") as f:
			# lines = f.readlines()
			# val = lines[0]
		# print("optimal : "+val)
	main(121,7)