Google的OR-Tools

关于 OR-Tools

OR-Tools 是一个用于优化的开源软件套件,专为解决世界上最棘手的车辆路线、流程、整数和线性编程以及约束编程问题而设计。
使用您选择的编程语言建模您的问题后,您可以使用下面五种解法之一:Gurobi 或 CPLEX 等商业求解器,或使用开源解法器(例如 SCIP、GLPK 或 Google 的 GLOP 和获奖的 CP-SAT)。
该软件力求从一组可能的大量解决方案中找出某个问题的最佳解决方案。以下是 OR 工具可以解决的一些问题示例:

  • 车辆路线:根据给定的限制条件(例如,“这辆卡车承载的重量不能超过 20,000 磅”或“所有送货都必须在两小时内完成”)。
  • 调度:为一组复杂的任务找到最佳调度,其中一些任务需要先完成,然后才能在一组固定的机器或其他资源上执行。
  • 箱装:将各种不同尺寸的对象装入具有最大容量的固定数量的箱中。

在大多数情况下,此类问题具有大量可能的解决方案,计算机过多无法搜索所有解决方案。为了解决这个问题,OR-Tools 使用了先进的算法来缩小搜索集的范围,以寻找最佳(或最接近)的解决方案。
OR-Tools 包含以下解决方案:

  • 约束编程
    用于寻找可行解的方法,用于解决以约束条件表示的问题(例如,无法同时将房间用于两个事件,或者与剪裁内容的距离必须小于软管的长度,或者一次最多只能录制五个电视节目)。
  • 线性和混合整数编程
    Glop 线性优化器会在指定一组线性不等式(例如,为人员分配作业,或在尽量降低费用的情况下找到一组资源的最佳分配)后,找出线性目标函数的最佳值。也可以通过 Google Apps 脚本优化服务使用 Glop 和混合整数编程软件 SCIP。
  • 车辆路线
    用于在满足指定限制条件的情况下确定最佳车辆路线的专用库。
  • 图表算法
    此代码用于查找图表、最短费用流、最大流量和线性总和分配中的最短路径。

安装 OR-Tools

Google 使用 C++ 创建了 OR-Tools,但您也可以将其用于 Python、Java 或 C#(在 .NET 平台上)。

适用于 C++ 的 OR-Tools

您可以通过二进制文件分发或源代码安装 C++ 版 OR-Tools。二进制选项更简单,因此除非您计划修改源代码,否则我们建议您采用该选项。

在ubuntu上安装二进制发行版 C++ 版 OR-Tools

具体步骤参考:https://developers.google.com/optimization/install/cpp/binary_linux?hl=zh-cn#ubuntu_2

在ubuntu上源代码安装适用于 C++ 的 OR-Tools

具体步骤参考:
https://developers.google.com/optimization/install/cpp/source_linux?hl=zh-cn

使用OR-Tools解决车辆路线问题

概览

最常见的优化任务之一是车辆路由,其目标是为访问一组位置的车辆找到最佳路线。通常,“最佳”表示总距离或费用最低的路线。 以下是路由问题的一些示例:

  • 一家包裹配送公司希望为司机分配路线,以便快递员送货。
  • 一家有线电视公司希望为技术人员分配路线,以便拨打住宅服务电话。
  • 一家拼车公司希望为司机分配路线,以便他们接载乘客。

最著名的路线问题是旅行推销员问题 (TSP):对于需要到不同地点拜访客户并返回起点的销售人员,请找到最短路线。TSP 可以由图表表示,其中节点对应于位置,而边缘(或弧线)表示位置之间的直接行程。例如,下图显示的 TSP 只有四个位置,分别标记为 A、B、C 和 D。任意两个位置之间的距离由连接它们的边缘旁边的数字指定。
在这里插入图片描述
通过计算所有可能路线的距离,您可以看到最短的路线是 ACDBA,其总距离为 35 + 30 + 15 + 10 = 90。
位置越多,问题就越严重。上面的示例中只有六条路线。但是,如果有 10 个位置(不计算起点),路线数量为 362880。对于 20 个营业地点,该数量将跳转到 2432902008176640000。 对所有可能的路线进行详尽搜索可以保证找到最短,但对于几乎所有位置,这都缺乏计算能力。对于较大的问题,需要优化技术才能智能地搜索解决方案空间并找到最佳(或近乎最佳)解决方案。
更广义的 TSP 是车辆路由问题 (VRP),其中有多个车辆。在大多数情况下,VRP 都有限制:例如,车辆的承载能力或车辆承载能力可能取决于车辆的最大容量,或者驾驶员可能需要在客户要求的指定时间段内造访营业地点。

OR-Tools 可以解决许多类型的 VRP,其中包括

  • 旅行推销员问题 - 是一种经典的路由问题,其中只有一个车辆。
  • 车辆路由问题,即用多辆车对 TSP 进行泛化。
  • 具有容量限制的 VRP:车辆的最大承载能力。
  • 带有时间范围的 VRP:车辆必须在指定的时间间隔内到访相应的地点。
  • 具有资源限制条件的 VRP,例如空间或人员在停靠处的车辆上加载和卸载(路线的起点)。
  • 访问量下降的 VRP:在这种情况下,车辆并非必须访问所有地点,但必须对每个未到访的行程支付罚款。

旅行推销员问题

本部分将通过一个示例介绍如何为下方地图中显示的营业地点解决旅行推销员问题 (TSP)。
在这里插入图片描述

创建数据

下面的代码会为该问题创建数据。

struct DataModel {
  const std::vector<std::vector<int64_t>> distance_matrix{
      {0, 2451, 713, 1018, 1631, 1374, 2408, 213, 2571, 875, 1420, 2145, 1972},
      {2451, 0, 1745, 1524, 831, 1240, 959, 2596, 403, 1589, 1374, 357, 579},
      {713, 1745, 0, 355, 920, 803, 1737, 851, 1858, 262, 940, 1453, 1260},
      {1018, 1524, 355, 0, 700, 862, 1395, 1123, 1584, 466, 1056, 1280, 987},
      {1631, 831, 920, 700, 0, 663, 1021, 1769, 949, 796, 879, 586, 371},
      {1374, 1240, 803, 862, 663, 0, 1681, 1551, 1765, 547, 225, 887, 999},
      {2408, 959, 1737, 1395, 1021, 1681, 0, 2493, 678, 1724, 1891, 1114, 701},
      {213, 2596, 851, 1123, 1769, 1551, 2493, 0, 2699, 1038, 1605, 2300, 2099},
      {2571, 403, 1858, 1584, 949, 1765, 678, 2699, 0, 1744, 1645, 653, 600},
      {875, 1589, 262, 466, 796, 547, 1724, 1038, 1744, 0, 679, 1272, 1162},
      {1420, 1374, 940, 1056, 879, 225, 1891, 1605, 1645, 679, 0, 1017, 1200},
      {2145, 357, 1453, 1280, 586, 887, 1114, 2300, 653, 1272, 1017, 0, 504},
      {1972, 579, 1260, 987, 371, 999, 701, 2099, 600, 1162, 1200, 504, 0},
  };
  const int num_vehicles = 1;
  const RoutingIndexManager::NodeIndex depot{0};
};

距离矩阵是一个数组,其 i、j 条目是从位置 i 到位置 j(以英里为单位)的距离,其中数组索引与位置对应的顺序如下:

0. New York - 1. Los Angeles - 2. Chicago - 3. Minneapolis - 4. Denver - 5. Dallas
- 6. Seattle - 7. Boston - 8. San Francisco - 9. St. Louis - 10. Houston - 11. Phoenix - 12. Salt Lake City

注意:距离矩阵中位置的顺序是任意的,与 TSP 的任何解决方案中的位置顺序无关。
这些数据还包括:

  • 出现问题的车辆数量,为 1,因为这是 TSP。(对于车辆路线问题 (VRP),车辆数量可以大于 1。)
  • 车站:路线的起点和终点。在本例中,仓库为 0,对应于纽约。

注意:由于路由求解器会使用整数执行所有计算,因此距离回调必须针对任意两个位置返回一个整数距离。如果 data[‘distance_matrix’] 的任何条目不是整数,则需要将矩阵条目或回调的返回值四舍五入为整数。

创建距离矩阵的其他方法
在此示例中,程序中明确定义了距离矩阵。 您也可以使用函数计算位置之间的距离:例如,平面平面上点的欧几里得公式。但是,与在运行时计算相比,预先计算位置之间的所有距离并将其存储在矩阵中会更加高效。

创建路由模型

程序主部分中的以下代码会创建索引管理器 (manager) 和路由模型 (routing)。方法 manager.IndexToNode 会将求解器的内部索引(可以放心地忽略)转换为位置的数字。位置编号对应于距离矩阵的索引。

DataModel data;
RoutingIndexManager manager(data.distance_matrix.size(), data.num_vehicles,
                            data.depot);
RoutingModel routing(manager);

RoutingIndexManager 的输入如下:

  • 距离矩阵的行数,即位置数量(包括车站)。
  • 出现问题的车辆数量。
  • 与仓库对应的节点。

注意:在其他问题中,您可以为路由指定不同的起始位置和结束位置。为此,您需要传入两个矢量:第一个矢量包含开始位置,第二个矢量包含结束位置,而不是单个 depot。如需查看示例,请参阅为路线设置开始和结束位置。

创建距离回调

如需使用路由求解器,您需要创建一个距离(或公交)回调:该函数可接受任意一对位置并返回它们之间的距离。最简单的方法是使用距离矩阵。
以下函数会创建回调并将其作为 transit_callback_index 向求解器注册。

const int transit_callback_index = routing.RegisterTransitCallback(
    [&data, &manager](int64_t from_index, int64_t to_index) -> int64_t {
      // Convert from routing variable Index to distance matrix NodeIndex.
      auto from_node = manager.IndexToNode(from_index).value();
      auto to_node = manager.IndexToNode(to_index).value();
      return data.distance_matrix[from_node][to_node];
    });
  

回调接受两个索引,from_index 和 to_index,并返回距离矩阵的相应条目。

设定旅行费用

arc cost evaluator 告诉求解器如何计算任意两个位置之间的行程成本——换句话说,就是在问题的图中连接它们的边(或弧)的成本。以下代码设置arc cost evaluator。

routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index);

在此示例中,弧线费用评估器是 transit_callback_index,它是求解器对距离回调的内部引用。这意味着,任何两个位置之间的行程费用只是它们之间的距离。不过,总体而言,费用还可能会涉及到其他因素。
此外,您还可以使用 routing.SetArcCostEvaluatorOfVehicle() 方法定义多个弧形评估器,以评估车辆在营业地点之间行驶的情况。例如,如果车辆的速度不同,那么您可以将不同地点之间的行程费用定义为距离除以车辆速度(也就是行程时间)。

设置搜索参数

以下代码会设置默认搜索参数和用于查找第一个解决方案的启发法:

RoutingSearchParameters searchParameters = DefaultRoutingSearchParameters();
searchParameters.set_first_solution_strategy(
    FirstSolutionStrategy::PATH_CHEAPEST_ARC);

上述代码会将第一个解决方案策略设置为 PATH_CHEAPEST_ARC,它会通过添加权重最低且不指向之前访问过的节点(仓库除外)的边缘来为求解器创建初始路线。

添加解决方案打印器

显示求解器所返回解决方案的函数如下所示。 函数从解决方案中提取路线并将其输出到控制台。

//! @brief Print the solution.
//! @param[in] manager Index manager used.
//! @param[in] routing Routing solver used.
//! @param[in] solution Solution found by the solver.
void PrintSolution(const RoutingIndexManager& manager,
                   const RoutingModel& routing, const Assignment& solution) {
  // Inspect solution.
  LOG(INFO) << "Objective: " << solution.ObjectiveValue() << " miles";
  int64_t index = routing.Start(0);
  LOG(INFO) << "Route:";
  int64_t distance{0};
  std::stringstream route;
  while (routing.IsEnd(index) == false) {
    route << manager.IndexToNode(index).value() << " -> ";
    int64_t previous_index = index;
    index = solution.Value(routing.NextVar(index));
    distance += routing.GetArcCostForVehicle(previous_index, index, int64_t{0});
  }
  LOG(INFO) << route.str() << manager.IndexToNode(index).value();
  LOG(INFO) << "Route distance: " << distance << "miles";
  LOG(INFO) << "";
  LOG(INFO) << "Advanced usage:";
  LOG(INFO) << "Problem solved in " << routing.solver()->wall_time() << "ms";
}

该函数会显示最佳路线及其距离,该距离由 ObjectiveValue() 提供。

求解并打印解决方案

最后,您可以调用求解器并输出解决方案:

const Assignment* solution = routing.SolveWithParameters(searchParameters);
PrintSolution(manager, routing, *solution);

此操作会返回解决方案并显示最佳路线。

运行程序

运行程序时,它们会显示以下输出。
在这里插入图片描述
在此示例中,只有一条路由,因为它是 TSP。但在较为常见的车辆路由问题中,该解决方案包含多个路线。

将路线保存到列表或数组

作为直接输出解决方案的替代方案,您可以将路由(或 VRP 的路由)保存到列表或数组中。这样做的好处是,您可以在日后需要时利用这些路线。例如,您可以使用不同的参数多次运行程序,并将返回的解决方案中的路线保存到文件中进行比较。

以下函数会将解决方案中的路线保存为列表 (Python) 或数组 (C++) 中的任何 VRP(可能有多个车辆)。

std::vector<std::vector<int>> GetRoutes(const Assignment& solution,
                                        const RoutingModel& routing,
                                        const RoutingIndexManager& manager) {
  // Get vehicle routes and store them in a two dimensional array, whose
  // i, j entry is the node for the jth visit of vehicle i.
  std::vector<std::vector<int>> routes(manager.num_vehicles());
  // Get routes.
  for (int vehicle_id = 0; vehicle_id < manager.num_vehicles(); ++vehicle_id) {
    int64_t index = routing.Start(vehicle_id);
    routes[vehicle_id].push_back(manager.IndexToNode(index).value());
    while (!routing.IsEnd(index)) {
      index = solution.Value(routing.NextVar(index));
      routes[vehicle_id].push_back(manager.IndexToNode(index).value());
    }
  }
  return routes;
}

您可以使用这些函数获取“路由”部分中的任何 VRP 示例中的路由。

以下代码显示路由。

const std::vector⟨std::vector⟨int⟩⟩
    routes = GetRoutes(*solution,
                        routing,
                        manager);
// Display the routes.
for (int vehicle_id = 0; vehicle_id < routes.size(); ++vehicle_id) {
  LOG(INFO) << "Route " << vehicle_id;
  for (int j = 1; j < routes[vehicle_id].size(); ++j) {
    LOG(INFO) << routes[vehicle_id][j];
  }
}

对于当前示例,此代码会返回以下路由:
在这里插入图片描述

完整程序

完整的 TSP 计划如下所示。

#include <cmath>
#include <cstdint>
#include <sstream>
#include <vector>

#include "ortools/constraint_solver/routing.h"
#include "ortools/constraint_solver/routing_enums.pb.h"
#include "ortools/constraint_solver/routing_index_manager.h"
#include "ortools/constraint_solver/routing_parameters.h"

namespace operations_research {
struct DataModel {
  const std::vector<std::vector<int64_t>> distance_matrix{
      {0, 2451, 713, 1018, 1631, 1374, 2408, 213, 2571, 875, 1420, 2145, 1972},
      {2451, 0, 1745, 1524, 831, 1240, 959, 2596, 403, 1589, 1374, 357, 579},
      {713, 1745, 0, 355, 920, 803, 1737, 851, 1858, 262, 940, 1453, 1260},
      {1018, 1524, 355, 0, 700, 862, 1395, 1123, 1584, 466, 1056, 1280, 987},
      {1631, 831, 920, 700, 0, 663, 1021, 1769, 949, 796, 879, 586, 371},
      {1374, 1240, 803, 862, 663, 0, 1681, 1551, 1765, 547, 225, 887, 999},
      {2408, 959, 1737, 1395, 1021, 1681, 0, 2493, 678, 1724, 1891, 1114, 701},
      {213, 2596, 851, 1123, 1769, 1551, 2493, 0, 2699, 1038, 1605, 2300, 2099},
      {2571, 403, 1858, 1584, 949, 1765, 678, 2699, 0, 1744, 1645, 653, 600},
      {875, 1589, 262, 466, 796, 547, 1724, 1038, 1744, 0, 679, 1272, 1162},
      {1420, 1374, 940, 1056, 879, 225, 1891, 1605, 1645, 679, 0, 1017, 1200},
      {2145, 357, 1453, 1280, 586, 887, 1114, 2300, 653, 1272, 1017, 0, 504},
      {1972, 579, 1260, 987, 371, 999, 701, 2099, 600, 1162, 1200, 504, 0},
  };
  const int num_vehicles = 1;
  const RoutingIndexManager::NodeIndex depot{0};
};

//! @brief Print the solution.
//! @param[in] manager Index manager used.
//! @param[in] routing Routing solver used.
//! @param[in] solution Solution found by the solver.
void PrintSolution(const RoutingIndexManager& manager,
                   const RoutingModel& routing, const Assignment& solution) {
  // Inspect solution.
  LOG(INFO) << "Objective: " << solution.ObjectiveValue() << " miles";
  int64_t index = routing.Start(0);
  LOG(INFO) << "Route:";
  int64_t distance{0};
  std::stringstream route;
  while (routing.IsEnd(index) == false) {
    route << manager.IndexToNode(index).value() << " -> ";
    int64_t previous_index = index;
    index = solution.Value(routing.NextVar(index));
    distance += routing.GetArcCostForVehicle(previous_index, index, int64_t{0});
  }
  LOG(INFO) << route.str() << manager.IndexToNode(index).value();
  LOG(INFO) << "Route distance: " << distance << "miles";
  LOG(INFO) << "";
  LOG(INFO) << "Advanced usage:";
  LOG(INFO) << "Problem solved in " << routing.solver()->wall_time() << "ms";
}

void Tsp() {
  // Instantiate the data problem.
  DataModel data;

  // Create Routing Index Manager
  RoutingIndexManager manager(data.distance_matrix.size(), data.num_vehicles,
                              data.depot);

  // Create Routing Model.
  RoutingModel routing(manager);

  const int transit_callback_index = routing.RegisterTransitCallback(
      [&data, &manager](int64_t from_index, int64_t to_index) -> int64_t {
        // Convert from routing variable Index to distance matrix NodeIndex.
        auto from_node = manager.IndexToNode(from_index).value();
        auto to_node = manager.IndexToNode(to_index).value();
        return data.distance_matrix[from_node][to_node];
      });

  // Define cost of each arc.
  routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index);

  // Setting first solution heuristic.
  RoutingSearchParameters searchParameters = DefaultRoutingSearchParameters();
  searchParameters.set_first_solution_strategy(
      FirstSolutionStrategy::PATH_CHEAPEST_ARC);

  // Solve the problem.
  const Assignment* solution = routing.SolveWithParameters(searchParameters);

  // Print solution on console.
  PrintSolution(manager, routing, *solution);
}

}  // namespace operations_research

int main(int /*argc*/, char* /*argv*/[]) {
  operations_research::Tsp();
  return EXIT_SUCCESS;
}

参考链接:link

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值