node-or-tools icon indicating copy to clipboard operation
node-or-tools copied to clipboard

how to minimize each vehicle time in vrp with or-tools

Open alirezatheh opened this issue 8 years ago • 11 comments

i want minimize network time in other words: each vehicle time should be minimized as possible 1- this means force all vehicle to service locations 2- and balance number of locations serviced per vehicle 3- and minimizes the time the last vehicle will arrive to its end node @daniel-j-h what you said about forcing all vehicle works fine but others not working at all do you have any idea to do number 3?

alirezatheh avatar Dec 26 '17 01:12 alirezatheh

Number three is ticketed in https://github.com/mapbox/node-or-tools/issues/12.

I don't have time to run this out - if you want to give it a try it mainly should be a matter of adding a new parameter and then passing it through to the solver (as described in the ticket).

daniel-j-h avatar Dec 27 '17 14:12 daniel-j-h

i actually used the #12 and the makespan not yet minimized here is my whole code in python:

import math
from ortools.constraint_solver import pywrapcp
from ortools.constraint_solver import routing_enums_pb2


def distance(x1, y1, x2, y2):
    dist = math.ceil(math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2))

    return dist


class CreateDistanceCallback(object):
    """Create callback to calculate distances between points."""

    def __init__(self, locations):
        """Initialize distance array."""
        size = len(locations)
        self.matrix = {}

        for from_node in range(size):
            self.matrix[from_node] = {}
            for to_node in range(size):
                if from_node == size - 1 or to_node == size - 1:
                    # Define the distance from the depot to any node to be 0.
                    self.matrix[from_node][to_node] = 0
                else:
                    x1 = locations[from_node][0]
                    y1 = locations[from_node][1]
                    x2 = locations[to_node][0]
                    y2 = locations[to_node][1]
                    self.matrix[from_node][to_node] = distance(x1, y1, x2, y2)

    def Distance(self, from_node, to_node):
        return int(self.matrix[from_node][to_node])


# Demand callback
class CreateDemandCallback(object):
    """Create callback to get demands at each location."""

    def __init__(self, demands):
        self.matrix = demands

    def Demand(self, from_node, to_node):
        return self.matrix[from_node]


# Service time (proportional to demand) callback.
class CreateServiceTimeCallback(object):
    """Create callback to get time windows at each location."""

    def __init__(self, demands, time_per_demand_unit):
        self.matrix = demands
        self.time_per_demand_unit = time_per_demand_unit

    def ServiceTime(self, from_node, to_node):
        return int(self.matrix[from_node] * self.time_per_demand_unit)


# Create the travel time callback (equals distance divided by speed).
class CreateTravelTimeCallback(object):
    """Create callback to get travel times between locations."""

    def __init__(self, dist_callback, speed):
        self.dist_callback = dist_callback
        self.speed = speed

    def TravelTime(self, from_node, to_node):
        travel_time = self.dist_callback(from_node, to_node) / self.speed
        return int(travel_time)


# Create total_time callback (equals service time plus travel time).
class CreateTotalTimeCallback(object):
    """Create callback to get total times between locations."""

    def __init__(self, service_time_callback, travel_time_callback):
        self.service_time_callback = service_time_callback
        self.travel_time_callback = travel_time_callback

    def TotalTime(self, from_node, to_node):
        service_time = self.service_time_callback(from_node, to_node)
        travel_time = self.travel_time_callback(from_node, to_node)
        return service_time + travel_time


def main():
    # Create the data.
    data = create_data_array()
    locations = data[0]
    demands = data[1]
    num_locations = len(locations)
    start_locations = [0, 1]
    end_locations = [10, 10]
    num_vehicles = 2

    # Create routing model.
    if num_locations > 0:
        routing = pywrapcp.RoutingModel(num_locations, num_vehicles, start_locations, end_locations)
        search_parameters = pywrapcp.RoutingModel_DefaultSearchParameters()

        # Callback to the distance function.
        dist_between_locations = CreateDistanceCallback(locations)
        dist_callback = dist_between_locations.Distance
        routing.SetArcCostEvaluatorOfAllVehicles(dist_callback)

        # Add time dimension.
        time_per_demand_unit = 5
        fix_start_cumul_to_zero = True
        horizon = 500
        time = "Time"
        speed = 1

        service_times = CreateServiceTimeCallback(demands, time_per_demand_unit)
        service_time_callback = service_times.ServiceTime

        travel_times = CreateTravelTimeCallback(dist_callback, speed)
        travel_time_callback = travel_times.TravelTime

        total_times = CreateTotalTimeCallback(service_time_callback, travel_time_callback)
        total_time_callback = total_times.TotalTime

        routing.AddDimension(total_time_callback,  # total time function callback
                             horizon,
                             horizon,
                             fix_start_cumul_to_zero,
                             time)

        for vehicle in range(routing.vehicles()):
            routing.AddVariableMinimizedByFinalizer(routing.CumulVar(routing.End(vehicle), time))

        for vehicle_nbr in range(num_vehicles):
            start_var = routing.NextVar(routing.Start(vehicle_nbr))
            for node_index in range(routing.Size(), routing.Size() + routing.vehicles()):
                start_var.RemoveValue(node_index)

        # Solve, displays a solution if any.
        assignment = routing.SolveWithParameters(search_parameters)
        if assignment:
            # Display solution.
            # Solution cost.
            print("Total distance of all routes: " + str(assignment.ObjectiveValue()) + "\n")
            time_dimension = routing.GetDimensionOrDie(time)

            for vehicle_nbr in range(num_vehicles):
                index = routing.Start(vehicle_nbr)
                index_next = assignment.Value(routing.NextVar(index))
                route = ''
                route_dist = 0

                while not routing.IsEnd(index_next):
                    node_index = routing.IndexToNode(index)
                    node_index_next = routing.IndexToNode(index_next)
                    time_var = time_dimension.CumulVar(index)
                    route += str(locations[node_index]) + str(assignment.Min(time_var)) + " to " + str(
                        assignment.Max(time_var)) + " -> "
                    # Add the distance to the next node.
                    route_dist += dist_callback(node_index, node_index_next)
                    index = index_next
                    index_next = assignment.Value(routing.NextVar(index))

                node_index = routing.IndexToNode(index)
                node_index_next = routing.IndexToNode(index_next)
                time_var = time_dimension.CumulVar(index)
                route += str(locations[node_index]) + str(assignment.Min(time_var)) + " to " + str(
                    assignment.Max(time_var))
                route_dist += dist_callback(node_index, node_index_next)
                print("Route for vehicle " + str(vehicle_nbr) + ":\n\n" + route + "\n")
                print("Distance of route " + str(vehicle_nbr) + ": " + str(route_dist))
        else:
            print('No solution found.')
    else:
        print('Specify an instance greater than 0.')

def create_data_array():
    locations = [[9, 9], [11, 11], [10, 2], [10, 18], [2, 10], [18, 10], [2, 2], [18, 2], [2, 18], [18, 18],
                 [1000, 1000]]
    demands = [0 for _ in range(2)] + [1 for _ in range(8)] + [0]
    data = [locations, demands]
    return data


if __name__ == '__main__':
    main()

what is the problem?

alirezatheh avatar Dec 27 '17 18:12 alirezatheh

Hi Please answer my question in issuesit is very necessary for please!!

On Wednesday, December 27, 2017 5:50 PM, Daniel J. H. <[email protected]> wrote:

Number three is ticketed in #12.I don't have time to run this out - if you want to give it a try it mainly should be a matter of adding a new parameter and then passing it through to the solver (as described in the ticket).— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or mute the thread.

alirezatheh avatar Jan 02 '18 04:01 alirezatheh

@AlirezaH320 I currently don't have the time to look into this. Try to minimize your example and if you then can't get it to work ask around for pointers.

daniel-j-h avatar Jan 02 '18 09:01 daniel-j-h

@AlirezaH320 couple of comments.

  1. Why do you think that your solution is not minimised by overall time? Your arccostevaluator indirectly is doing this.
  2. I feel that this project is not completely relative to your code, because mapbox is creating a bridge for javascript, while your code is purely ortools-python. Maybe you can try to find an answer in google's ortools discussion group (in Google groups) or in google ortools issues on github.

emakarov avatar Jan 02 '18 12:01 emakarov

@emakarov couple of comments.

  1. Because that gave me following result:
Total distance of all routes: 64

Route for vehicle 0:

[9, 9]0 to 0 -> [10, 2]8 to 8 -> [18, 2]21 to 21

Distance of route 0: 16
Route for vehicle 1:

[11, 11]0 to 0 -> [18, 10]8 to 8 -> [18, 18]21 to 21 -> [10, 18]34 to 34 -> [2, 18]47 to 47 -> [2, 10]60 to 60 -> [2, 2]73 to 73

Distance of route 1: 48

and as you see is not minimized 2. I tried to find any answer there but no one did reply

alirezatheh avatar Jan 02 '18 15:01 alirezatheh

you dont use any metaheuristic thats why you have only first local minimum, and your solution is not optimized in general

emakarov avatar Jan 02 '18 15:01 emakarov

i used this:

search_parameters.local_search_metaheuristic = (
            routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)
        search_parameters.time_limit_ms = 30000

but it gave me same result can you give me an example @emakarov ?

alirezatheh avatar Jan 02 '18 17:01 alirezatheh

  • 30 seconds can be too small
  • you need to use kScalingFactor for integer programming

emakarov avatar Jan 02 '18 17:01 emakarov

What you want to use is SetGlobalSpanCoefficient to a distance/time dimension

I'm working on OR-Tools devsite/gh to add an example about this... WorkInProgress: https://github.com/google/or-tools/tree/mizux/doc/doc#globalspan-on-distance-dimension

note1: this try to minimize max{cumul(end_i)}-min{cumul(start_i)} so while solver try to reduce the longest/biggest quantity, it won't perform a balancing (more a side effect sometime) note2: you still need SetArcCost... for the first search so solver can find local minimum sometime playing with the vehicle capacity could change the result e.g. https://github.com/google/or-tools/blob/mizux/doc/doc/vrpgs.py#L129 try to replace 3000 by 4000 ;)

ps: thanks for the js binding, hope to provide it directly in or-tools but low priority, no time currently....

Mizux avatar Apr 16 '18 06:04 Mizux

@AlirezaH320 as you are using start_locations I have an issue in the nodes order at the solution I get as here: https://github.com/google/or-tools/issues/717 but I did not add any time concertinas yet..

Did you try to see if you change the start locations order in the locations list at create_data_array would results into different solution? (which I think should not happen)

hopewise avatar Jun 26 '18 13:06 hopewise