Как стать автором
Обновить

Комментарии 10

Странно у меня этот код находит только такое решение:

Objective: 5454
Route for vehicle 0:
 0 ->  2 ->  4 ->  5 ->  6 ->  14 ->  10 ->  11 ->  15 -> 16
Time of the route: 101 minutes 

Route for vehicle 1:
 1 ->  3 ->  7 ->  8 ->  9 ->  12 ->  13 -> 17
Time of the route: 68 minutes 

Maximum of the route: 101 minutes

Интересный момент, возможно играет роль версия библиотеки. У меня установлена версия 9.5.2237. Я отредактировал полный код для удобства копирования. Возможно, при копировании появилась оишбка.

import ortools
print("ortools version %s"%ortools.__version__)

ortools version 9.6.2534

Сделай даунгрейд. Просто странно, что у меня решение лучше нашлось.

ortools version 9.5.2237
Objective: 5454
Route for vehicle 0:
 0 ->  2 ->  4 ->  5 ->  6 ->  14 ->  10 ->  11 ->  15 -> 16
Time of the route: 101 minutes 

Route for vehicle 1:
 1 ->  3 ->  7 ->  8 ->  9 ->  12 ->  13 -> 17
Time of the route: 68 minutes 

Maximum of the route: 101 minutes

Ничего не поменялось. От версии не зависит с 9.8.3296 точно так же.

Вы точно не делали изменений в коде? Я просил воспроизвести данное решение на разных os и оно совпадает с моим.

Незначительные, только что бы оно запустилось на python 3.8.10
from typing import Any

from ortools.constraint_solver import pywrapcp
from ortools.constraint_solver.pywrapcp import (
    DefaultRoutingSearchParameters, RoutingDimension, RoutingIndexManager,
    RoutingModel,
)

import ortools
print("ortools version %s"%ortools.__version__)


def create_data_model():
    data = {}
    data["time_matrix"] = [
        [0, 6, 0, 6, 5, 6, 7, 6, 8, 2, 9, 2, 4, 9, 9, 4, 9, 9],
        [6, 0, 6, 0, 2, 2, 2, 1, 3, 3, 4, 3, 7, 11, 4, 4, 12, 12],
        [0, 6, 0, 6, 5, 6, 7, 6, 8, 2, 9, 2, 4, 9, 9, 4, 9, 9],
        [6, 0, 6, 0, 2, 2, 2, 1, 3, 3, 4, 3, 7, 11, 4, 4, 12, 12],
        [6, 0, 6, 0, 0, 2, 3, 2, 4, 4, 5, 3, 7, 10, 5, 3, 12, 12],
        [9, 4, 9, 4, 3, 0, 1, 2, 1, 6, 3, 6, 9, 12, 3, 6, 14, 14],
        [8, 4, 8, 4, 3, 2, 0, 1, 2, 5, 2, 5, 9, 12, 2, 5, 14, 14],
        [7, 2, 7, 2, 2, 0, 1, 0, 1, 4, 3, 4, 8, 11, 3, 4, 13, 13],
        [9, 4, 9, 4, 4, 2, 0, 2, 0, 6, 2, 6, 10, 13, 2, 6, 14, 14],
        [2, 5, 2, 5, 5, 4, 5, 4, 5, 0, 7, 3, 3, 10, 7, 6, 11, 11],
        [8, 4, 8, 4, 3, 2, 0, 1, 2, 5, 0, 5, 9, 12, 0, 5, 14, 14],
        [3, 4, 3, 4, 4, 4, 5, 4, 6, 2, 7, 0, 5, 9, 7, 3, 10, 10],
        [1, 7, 1, 7, 6, 6, 7, 6, 7, 2, 9, 4, 0, 9, 9, 5, 9, 9],
        [10, 11, 10, 11, 11, 13, 14, 13, 14, 10, 16, 10, 11, 0, 16, 9, 6, 6],
        [8, 4, 8, 4, 3, 2, 0, 1, 2, 5, 0, 5, 9, 12, 0, 5, 14, 14],
        [7, 5, 7, 5, 4, 6, 6, 5, 7, 5, 8, 4, 9, 9, 8, 0, 10, 10],
        [10, 12, 10, 12, 12, 14, 15, 14, 14, 10, 17, 10, 12, 4, 17, 10, 0, 0],
        [10, 12, 10, 12, 12, 14, 15, 14, 14, 10, 17, 10, 12, 4, 17, 10, 0, 0],
    ]
    data["num_vehicles"] = 2
    data["starts"] = [0, 1]
    data["ends"] = [16, 17]
    data["pickups_deliveries"] = [(10, 11), (12, 13), (14, 15)]
    data["obligations"] = [
        0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, None, None, 0, 1
    ]
    data["demands"] = [
        0, 0, 3, 3, -1, -1, -1, -1, -1, -1, 1, -1, 1, -1, 1, -1, 0, 0
    ]
    data["vehicle_capacities"] = [8, 8]
    data["point_types"] = [
        "start", "start", "onboard", "onboard", "delivery", "delivery",
        "delivery", "delivery", "delivery", "delivery", "pickup", "delivery",
        "pickup", "delivery", "pickup", "delivery", "finish", "finish",
    ]
    data["time_windows"] = [
        (0, 0), (0, 0), (0, 0), (0, 0), (0, 9), (0, 11), (0, 11), (0, 4),
        (0, 4), (0, 10), (8, 15), (0, 23), (9, 17), (0, 32), (4, 30), (9, 45),
        (0, 79), (0, 119),
    ]
    data["disjunction"] = [(14, 15)]
    data["initial_routes"] = [[2, 4, 6, 5, 10, 11], [3, 8, 7, 9, 12, 13]]
    return data

def add_time_dimension(
    data,# : dict[str, Any], 
    manager: RoutingIndexManager, routing: RoutingModel
) -> RoutingDimension:
    def time_callback(from_index, to_index):
        from_node = manager.IndexToNode(from_index)
        to_node = manager.IndexToNode(to_index)
        #return data["time_matrix"][from_node, to_node]
        return data["time_matrix"][from_node][to_node]

    transit_callback_index = routing.RegisterTransitCallback(time_callback)
    routing.AddDimension(
        evaluator_index=transit_callback_index,
        slack_max=8 * 60,  # allow waiting time
        capacity=8 * 60,  # maximum time per vehicle
        fix_start_cumul_to_zero=False,  # force start cumul to zero
        name="Time",
    )
    time_dimension = routing.GetDimensionOrDie("Time")
    routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
    time_dimension.SetSpanCostCoefficientForAllVehicles(100)
    return time_dimension

def get_index(
    manager: RoutingIndexManager,
    ind: int,
    routing: RoutingModel,
    obligations #: list[int | None],
) -> int:
    if manager.NodeToIndex(ind) != -1:
        index_node = manager.NodeToIndex(ind)
    else:
        index_node = routing.End(obligations[ind])
    return index_node

def time_window_constr(
    data, #: dict[str, Any],
    manager: RoutingIndexManager,
    routing: RoutingModel,
    time_dimension: RoutingDimension,
    penalty: int = 10000
) -> None:
    for ind, (pt, tw) in enumerate(zip(data["point_types"], data["time_windows"])):
        index = get_index(manager, ind, routing, data["obligations"])
        if pt not in ("delivery", "finish"):
            time_dimension.SetCumulVarSoftLowerBound(index, tw[0], int(10e6))
        time_dimension.SetCumulVarSoftUpperBound(index, tw[1], penalty * 2)


def disjunction_constr(
    data, #: dict[str, Any],
    manager: RoutingIndexManager,
    routing: RoutingModel,
    penalty: int = 10000
) -> None:
    for pickup, delivery in data["disjunction"]:
        node_ids = [manager.NodeToIndex(pickup), manager.NodeToIndex(delivery)]
        routing.AddDisjunction(node_ids, penalty, 2)


def pickup_delivery_constr(
    data, #: dict[str, Any],
    manager: RoutingIndexManager,
    routing: RoutingModel,
    time_dimension: RoutingDimension,
) -> None:
    for pickup, delivery in data["pickups_deliveries"]:
        pickup_index = get_index(manager, pickup, routing, data["obligations"])
        delivery_index = get_index(manager, delivery, routing, data["obligations"])
        # Ускоряет оптимизацию
        routing.AddPickupAndDelivery(pickup_index, delivery_index)
        # pickup и delivery на одной машине и активны
        routing.solver().Add(
            routing.ActiveVar(pickup_index) * routing.VehicleVar(pickup_index)
            == routing.ActiveVar(delivery_index) * routing.VehicleVar(delivery_index)
        )
        # pickup меньше или равен delivery по времени
        routing.solver().Add(
            time_dimension.CumulVar(pickup_index)
            <= time_dimension.CumulVar(delivery_index)
        )

def capacity_constr(
    data, # : dict[str, Any],
    manager: RoutingIndexManager,
    routing: RoutingModel
) -> None:
    def demand_callback(from_index):
        from_node = manager.IndexToNode(from_index)
        return data["demands"][from_node]

    demand_callback_index = routing.RegisterUnaryTransitCallback(demand_callback)
    routing.AddDimensionWithVehicleCapacity(
        evaluator_index=demand_callback_index,
        slack_max=0,  # null capacity slack
        vehicle_capacities=data["vehicle_capacities"],  # vehicle maximum capacities
        fix_start_cumul_to_zero=True,  # start cumul to zero
        name="Capacity",
    )


def obligation_constr(
    data, #: dict[str, Any],
    manager: RoutingIndexManager,
    routing: RoutingModel
) -> None:
    for node_idx, obligation in enumerate(data["obligations"]):
        index = get_index(manager, node_idx, routing, data["obligations"])
        if obligation is not None:
            routing.VehicleVar(index).SetValue(obligation)


def get_search_params() -> DefaultRoutingSearchParameters:
    search_parameters = pywrapcp.DefaultRoutingSearchParameters()
    search_parameters.first_solution_strategy = 8
    search_parameters.local_search_metaheuristic = 2
    search_parameters.solution_limit = 500
    search_parameters.time_limit.seconds = 3
    return search_parameters


def print_solution(data, manager, routing, solution):
    print(f"Objective: {solution.ObjectiveValue()}")
    max_route_distance = 0
    for vehicle_id in range(data["num_vehicles"]):
        index = routing.Start(vehicle_id)
        plan_output = f"Route for vehicle {vehicle_id}:\n"
        route_distance = 0
        while not routing.IsEnd(index):
            plan_output += f" {manager.IndexToNode(index)} -> "
            index = solution.Value(routing.NextVar(index))
            route_distance += assignment.Min(time_dimension.CumulVar(index))
        plan_output += f"{manager.IndexToNode(index)}\n"
        plan_output += f"Time of the route: {route_distance} minutes \n"
        print(plan_output)
        max_route_distance = max(route_distance, max_route_distance)
    print(f"Maximum of the route: {max_route_distance} minutes")



if __name__ == "__main__":
    data = create_data_model()
    manager = pywrapcp.RoutingIndexManager(
        len(data["time_matrix"]), data["num_vehicles"], data["starts"], data["ends"]
    )
    routing = pywrapcp.RoutingModel(manager)

    # constraints
    time_dimension = add_time_dimension(data=data, manager=manager, routing=routing)
    time_window_constr(
        data=data,
        manager=manager,
        routing=routing,
        time_dimension=time_dimension,
    )

    pickup_delivery_constr(
        data=data, manager=manager, routing=routing, time_dimension=time_dimension
    )
    capacity_constr(data=data, manager=manager, routing=routing)
    obligation_constr(data=data, manager=manager, routing=routing)
    disjunction_constr(data=data, manager=manager, routing=routing)

    # solutions
    search_parameters = get_search_params()
    # Применяем все объявленные параметры для поиска решения
    routing.CloseModelWithParameters(search_parameters)
    initial_solution = routing.ReadAssignmentFromRoutes(
        data["initial_routes"], True
    )
    assignment = routing.SolveFromAssignmentWithParameters(
        initial_solution, search_parameters
    )
    print_solution(data, manager, routing, assignment)

У вас абсолютно правильный код, это я допустил ошибку в методе

add_time_dimension. Ваш вариант правильный:

return data["time_matrix"][from_node][to_node]

До этого еще я поправил функцию print_solution сейчас она должна быть правильной.
Замените ее и получите правильное решение. Спасибо вам за внимательность!

Спасибо за материал! Проводили тестирование производительности библиотеки?

Целенопавлено не замерял. Библиотека на плюсах написана, но если существенно увеличивать кол-во ТС или ограничений, то без initial_solution лучше не запускать.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории