R:定制旅行商问题

R: Customizing the Travelling Salesman Problem

我正在使用 R 编程语言。

我发现这个(非常好的)教程展示了如何在 R 中实现旅行商问题(针对一组欧洲城市)的遗传算法:https://rstudio-pubs-static.s3.amazonaws.com/132872_620c10f340f348b88453d75ec99960ff.html

library(GA)
data("eurodist", package = "datasets")
D <- as.matrix(eurodist)


tourLength <- function(tour, distMatrix) {
   tour <- c(tour, tour[1])
   route <- embed(tour, 2)[,2:1]
   sum(distMatrix[route])
}

#Firness function to be maximized

tspFitness <- function(tour, ...) 1/tourLength(tour, ...)

GA <- ga(type = "permutation", fitness = tspFitness, distMatrix = D,
          min = 1, max = attr(eurodist, "Size"), popSize = 50, maxiter = 5000,
          run = 500, pmutation = 0.2)

最后,算法 returns 您访问这些城市的顺序(按编号索引):

    summary(GA)

## Solutions              = 
##      x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12 x13 x14 x15 x16 x17 x18 x19
## [1,] 20  7 11  3  4  5 18 13 12   9  14   2  15   8  16  19   1  21  17
## [2,]  6 10 20  7 11  3  4  5 18  13  12   9  14   2  15   8  16  19   1
## [3,] 10  6 17 21  1 19 16  8 15   2  14   9  12  13  18   5   4   3  11
##      x20 x21
## [1,]   6  10
## [2,]  21  17
## [3,]   7  20

我们甚至可以看到算法返回路径的可视化:

我的问题:

是否可以自定义 R 中的遗传算法以在优化旅行商问题时考虑这些“考虑因素”?

扩展我的评论。在处理遗传算法中的约束时,您有两个选择:

  • 在适应度函数中加入条件
  • 确保遗传算子创建可行的解决方案

对于第一种方法,您需要决定如何处理不可行的解决方案(例如惩罚),这完全取决于问题。如果条件难以达到,进化算法在这个过程中创建的大部分解决方案将是不可行的,并且会导致早熟收敛。

哪种适应方法取决于问题,我将向您展示如何针对此问题实施第二种方法。

矩阵变换:

transformMatrix <- function(fixed_points, D){
  
  if(length(fixed_points) == 0) return(D)
  
  p <- integer(nrow(D))
  pos <- match(names(fixed_points), colnames(D))
  p[fixed_points] <- pos 
  p[-fixed_points] <- sample(setdiff(seq_len(nrow(D)), pos))

  D[p, p]
}

此函数的目标是置换矩阵的行和列以使城市到达指定位置:

popSize <- 100
fixed_points <- c(
  "Vienna" = 1,
  "Athens" = 6
)
D_perm <- transformMatrix(fixed_points, D)

可行初始种群

feasiblePopulation <- function(n, size, fixed_points){
  
  positions <- setdiff(seq_len(n), fixed_points)
  
  m <- matrix(0, size, n)
  if(length(fixed_points) > 0){
    
    m[, fixed_points] <- rep(fixed_points, each = size)
    
    for(i in seq_len(size))
      m[i, -fixed_points] <- sample(positions)
    
  } else {
    
    for(i in seq_len(size))
      m[i,] <- sample(positions)
  }
  
  m
}

此函数创建可行的解决方案

变异

mutation <- function(n, fixed_points){
  
  positions <- setdiff(seq_len(n), fixed_points)
  
  function(obj, parent){
    
    vec <- obj@population[parent,]
    if(length(positions) < 2) return(vec) 
    
    indices <- sample(positions, 2)
    replace(vec, indices, vec[rev(indices)])
  }
}

我们需要确保变异算子保持固定位置。这个函数 returns 一个这样的变异算子。

适应度函数

fitness <- function(tour, distMatrix) {
  
  tour <- c(tour, tour[1])
  route <- embed(tour, 2)[,2:1]
  1/sum(distMatrix[route])
}

我们希望最小化距离,因此取倒数。

优化步骤

res <- ga(
  type = "permutation",
  fitness = fitness,
  distMatrix = D_perm,
  lower = 1,
  upper = nrow(D_perm),
  mutation = mutation(nrow(D_perm), fixed_points),
  crossover = gaperm_pmxCrossover,
  suggestions = feasiblePopulation(nrow(D_perm), popSize, fixed_points),
  popSize = popSize,
  maxiter = 5000,
  run = 500,
  pmutation = 0.2
)

gaperm_pmxCrossover 将确保固定位置在交叉过程中保持固定(这就是我没有编写自定义交叉运算符的原因)

解决方案

colnames(D_perm)[res@solution[1,]]
solution_distance <- 1 / fitness(res@solution[1,], D_perm)

同样第一个城市是最后一个(路线)