def test_calculate_cost_calculator_with_one_cars(): solution = Solution(vehicle_routes=np.array([[2, 1, 0], [0, 0, 0]])) node_distances = NodeDistances( distances=np.array([[0, 1, 2], [3, 0, 5], [6, 7, 0]])) calculator = SolutionRestrictionsCalculator(node_distances=node_distances, vehicle_allowed_distances=None) cost = calculator.calculate_cost(solution=solution) assert 12 == cost, "All only one route should sum 12"
def test_get_vehicle_consumed_distance(): solution = Solution(vehicle_routes=np.array([[2, 1, 3, 0], [0, 0, 0, 0]])) node_distances = NodeDistances(distances=np.array( [[0, 7, 3, 1], [2, 0, 4, 6], [3, 6, 0, 9], [5, 10, 15, 0]])) calculator = SolutionRestrictionsCalculator(node_distances=node_distances, vehicle_allowed_distances=None) cost = calculator.get_vehicle_consumed_distance(solution=solution, vehicle=0) assert 20 == cost, "All only one route should sum 20"
def test_remaining_distance_for_vehicle_returns_negative(): solution = Solution(vehicle_routes=np.array([[2, 1, 0], [0, 0, 0]])) node_distances = NodeDistances( distances=np.array([[0, 1, 2], [3, 0, 5], [6, 7, 0]])) vehicle_allowed_distances = VehicleAllowedDistances(distances=[10, 7, 0]) calculator = SolutionRestrictionsCalculator( node_distances=node_distances, vehicle_allowed_distances=vehicle_allowed_distances) remaining_distance = calculator.get_vehicle_remaining_distance( solution=solution, vehicle=0) assert -2 == remaining_distance, "-2 should be the remaining distance"
def test_update_cost_solution_when_2_routes_change(): solution1=Solution(vehicle_routes=np.array([[2, 0 , 0, 0],[1,3, 0,0],[0,0,0,0]])) solution2=Solution(vehicle_routes=np.array([[2, 0 , 0,0],[1, 0, 0,0],[3,0,0,0]])) node_distances=NodeDistances(distances=np.array([[0, 1, 2,20],[3, 0, 5,21],[6, 7, 0,22],[9,1,1,0]])) vehicle_allowed_distances=VehicleAllowedDistances(distances=[14, 20, 15]) calculator=SolutionRestrictionsCalculator(node_distances=node_distances,vehicle_allowed_distances=vehicle_allowed_distances) cost=Neighborhood(solution_restrictions_calculator=calculator). \ _update_solution_cost(old_solution=solution1,new_solution=solution2,vehicles_involved=[1,2]) assert cost==41 , "Update cost should be 41"
def execute_single_instance_once(self,path_to_node_distances:str, path_to_max_vehicle_distances:str, metaheuristic:str)->(): """ Execute a single instance of a problem, just only once. The result is a tuple containing: (solution, elapsed_time) """ node_distances=NodeDistances(distances=Reader.read_distances_between_nodes_in_file(path_to_file=path_to_node_distances)) vehicle_allowed_distances=VehicleAllowedDistances(distances=Reader.read_vehicle_allowed_distances_in_file(path_to_file=path_to_max_vehicle_distances)) solution_restrictions_calculator=SolutionRestrictionsCalculator(node_distances=node_distances, vehicle_allowed_distances=vehicle_allowed_distances) metaheu_obj=MetaheuristicFactory.create_metaheuristic(metaheuristic=metaheuristic, solution_restrictions_calculator=solution_restrictions_calculator, \ init=self.init, search_type=self.search_type, number_iterations=self.number_iterations, memory_size=self.memory_size, max_running_secs=self.max_running_secs) tic=time.time() solution=metaheu_obj.run() tac=time.time() elapsed_time=tac-tic return (solution,elapsed_time)
def test_join_customers_from_one_route_to_another(): vehicle_routes = np.array([[1, 2, 3, 4, 5, 0, 0, 0], [11, 12, 13, 14, 15, 0, 0, 0]]) vehicle_allowed_distances = VehicleAllowedDistances( distances=np.array([300, 200])) solution = Solution(vehicle_routes=vehicle_routes) solution_restrictions_calculator = SolutionRestrictionsCalculator( node_distances=None, vehicle_allowed_distances=vehicle_allowed_distances) join_customer = JoinCustomers( solution_restrictions_calculator=solution_restrictions_calculator) new_solution = join_customer._operation_between_vehicle_routes( solution=solution, vehicle1=0, vehicle2=1) route1 = new_solution.vehicle_routes[0] route2 = new_solution.vehicle_routes[1] assert route1[0] == 1, "r1: 1st element should be 1" assert route1[1] == 2, "r1: 2nd element should be 2" assert route1[2] == 3, "r1: 3rd element should be 3" assert route1[3] == 4, "r1: 4th element should be 4" assert route1[4] == 5, "r1: 5th element should be 5" assert route1[5] == 11, "r1: 6th element should be 11" assert route1[6] == 12, "r1: 7th element should be 12" assert route1[7] == 13, "r1: 8th element should be 13" assert route2[0] == 0, "r2: 1st element should be 0" assert route2[1] == 0, "r2: 2nd element should be 0" assert route2[2] == 0, "r2: 3rd element should be 0" assert route2[3] == 14, "r2: 4th element should be 14" assert route2[4] == 15, "r2: 5th element should be 15" assert route2[5] == 0, "r2: 6th element should be 0" assert route2[6] == 0, "r2: 7th element should be 0" assert route2[7] == 0, "r2: 8th element should be 0"
def __init__(self, node_distances: NodeDistances, vehicle_allowed_distances: VehicleAllowedDistances): self.node_distances = node_distances self.vehicle_allowed_distances = vehicle_allowed_distances self.solution_restrictions_calculator=SolutionRestrictionsCalculator(node_distances=node_distances, \ vehicle_allowed_distances=vehicle_allowed_distances)
class SolutionInitializer(object): """ Initializes a solution. """ def __init__(self, node_distances: NodeDistances, vehicle_allowed_distances: VehicleAllowedDistances): self.node_distances = node_distances self.vehicle_allowed_distances = vehicle_allowed_distances self.solution_restrictions_calculator=SolutionRestrictionsCalculator(node_distances=node_distances, \ vehicle_allowed_distances=vehicle_allowed_distances) def init_randomly(self) -> Solution: """ For each customer we randomly select a vehicle. If the fleet contains vehicles which cannot performe a certain trip, it may lead to unfeasible solutions. We reject invalid solutions, so we will try till a valid ones is obtained randomly. """ return self.__init_solution( initializing_function=self.__assign_nodes_randomly) def init_one_node_to_one_vehicle(self) -> Solution: """ We assign randomly one node to one vehicle. This should lead to a valid solution. However we will check it and look for another solution in case we found a no-valid one. """ return self.__init_solution( initializing_function=self.__assign_one_node_to_one_vehicle) def init_vehicles_with_group_nodes_sequentially(self) -> Solution: """ We assign nodes sequeantially to vehicles till they reach their restriction. This should lead to a valid solution. However we will check it and look for another solution in case we found a no-valid one. """ return self.__init_solution( initializing_function=self. __assign_group_nodes_sequentially_to_vehicles) def __init_solution(self, initializing_function) -> Solution: """ Initializing the solution, using the function passed as parameter to create the vehicle routes. After 100 attempts we consider an error must be happening and a exception is raised. """ attempt = 0 solution_is_valid = False while solution_is_valid == False: if attempt >= 100: raise Exception("Initial VALID solution not found after " + str(attempt) + " attempts.") attempt = attempt + 1 vehicle_routes = initializing_function() solution = Solution(vehicle_routes=vehicle_routes) solution_is_valid = self.solution_restrictions_calculator.check_if_solution_is_valid( solution=solution) solution.is_valid = True solution.cost = self.solution_restrictions_calculator.calculate_cost( solution=solution) return solution def __assign_nodes_randomly(self) -> np.ndarray: """ Assign nodes randomly """ num_vehicles = self.vehicle_allowed_distances.get_number_of_vehicles() num_nodes = self.node_distances.get_number_of_nodes() vehicle_routes = np.zeros([num_vehicles, num_nodes], dtype=int) for node in range(1, num_nodes): vehicle = random.randint(0, num_vehicles - 1) route = vehicle_routes[vehicle] available_indexes = np.where(route == 0)[0] index = available_indexes[0] vehicle_routes[vehicle][index] = node return vehicle_routes def __assign_group_nodes_sequentially_to_vehicles(self) -> np.ndarray: """ We assign nodes sequeantially to vehicles till they reach their restriction. This will lead to a valid solution. """ allowed_distances = self.vehicle_allowed_distances.distances num_nodes = self.node_distances.get_number_of_nodes() vehicle_routes = np.zeros([len(allowed_distances), num_nodes], dtype=int) vehicle = 0 index_node = 0 node_list = list(range(1, num_nodes)) random.shuffle(node_list) for node in node_list: #we suffle from 1 in order to ignore depot (=0) #Asign and check if valid vehicle_routes[vehicle][index_node] = node remaining_distance=self.solution_restrictions_calculator.get_remaining_distance(route=vehicle_routes[vehicle], \ node_distances=self.node_distances, allowed_distance=allowed_distances[vehicle]) #If no valid, undo the assignmment and move to next vehicle. if remaining_distance < 0: vehicle_routes[vehicle][index_node] = 0 vehicle = vehicle + 1 index_node = 0 vehicle_routes[vehicle][index_node] = node #Move to next index. index_node = index_node + 1 return vehicle_routes def __assign_one_node_to_one_vehicle(self) -> np.ndarray: """ As Clark and Wright, we assign one node to one vehicle. """ num_vehicles = self.vehicle_allowed_distances.get_number_of_vehicles() num_nodes = self.node_distances.get_number_of_nodes() vehicle_routes = np.zeros([num_vehicles, num_nodes], dtype=int) suffled_customers = np.array(range(1, num_nodes), dtype=int) random.shuffle(suffled_customers) for index in range(0, num_vehicles): vehicle_routes[index][0] = suffled_customers[index] return vehicle_routes