最佳线性化解决p色散(maxmin)问题?

时间:2019-07-01 08:08:24

标签: r optimization mathematical-optimization linear-programming quadratic-programming

与我的其他问题here部分相关。

在我的案例中,“原始”目标是从N = 292中选择n = 50个对象,以使所选对象之间的所有成对距离的总和最大化(maxsum或p色散总和)。

由于提供了建议的用户,我做了一些进一步的阅读,现在我了解到,问题的确以最简单的形式二次出现,像CPLEX这样的求解器也许可以解决它。

但是,库比的this article指出maxsum结果不能保证不会存在彼此非常接近的对象;确实,通过对模拟的较小案例进行的蛮力测试,我发现maxsum很高的解决方案有时包含非常接近的对象。

所以现在我在考虑p分散(maxmin)方法可能更适合我想要实现的目标。这最初也是一个二次问题。

由于我还没有CPLEX,因此无法尝试二次公式,因此我研究了线性化方法。这2篇文章对我来说似乎很有趣:
Franco, Uchoa
Sayah, 2015

后者指向另一篇文章,我也觉得很有趣:
Pisinger, 2006

下一步是尝试以下操作:

  1. 根据Kuby / Erkut线性化的p色散,其中N个对象是二进制变量,而1个连续变量是最大最小距离,位于距离矩阵的最小和最大距离之间
  2. 用力,从N中枚举n个对象的所有组合,并找到具有最大最小距离的对象
  3. 类似于1,但是使用Sayah / Pisinger的方法为连续变量设置更严格的上限
  4. 根据Sayah线性化p色散,对象具有N个二进制变量,成对距离具有N *(N-1)/ 2个附加二进制变量

我没有尝试收紧下限或增加更多的不平等,因为文章中建议的方法超出了我的数学水平。

让我感到困惑的是,方法4应该是“紧凑的”,实际上具有大量的二进制变量和随之而来的约束,在我进行的测试中,它的性能比方法1和2差得多。另一方面,上限产生了很大的影响,实际上,目前的方法2是唯一似乎能够在合理的时间内解决大型问题的方法。
但是,我确实没有完全实现Sayah论文中的方法,所以也许我的观察是无效的。

问题:您如何看待这些文章中介绍的各种线性化方法?你能建议更好的吗?您认为像Kuby的公式那样将最大最小距离保持为连续变量比像Sayah的公式那样将其“量化”更好吗?

实际上,与此同时,进一步的复杂性和发展出现了。是否存在“强迫”对象以及需要为每个对象使用分数,但我想首先解决上述问题。

我将其粘贴到用于测试此代码的R代码下面。

谢谢!

#Test of linearized methods for the solution of p-dispersion (maxmin) problems
#-----------------------------------------------------------------------------

#Definitions

#Given N objects, whose distance matrix 'distmat' is available:
#p-dispersion (maxmin): select n (n >= 2, n < N) objects such that the minimal distance between any two objects is maximised
#p-dispersion sum (maxsum): select n (n >= 2, n < N) objects such that the sum of all the pairwise distances between them is maximised

#Literature

#Kuby, 1987:  https://onlinelibrary.wiley.com/doi/abs/10.1111/j.1538-4632.1987.tb00133.x
#Pisinger, 1999: https://pdfs.semanticscholar.org/1eb3/810077c0af9d46ed5ff2b0819d954c97dcae.pdf
#Pisinger, 2006: http://yalma.fime.uanl.mx/~roger/work/teaching/clase_tso/docs_project/problems/PDP/cor-2006-Pisinger.pdf
#Franco, Uchoa: https://pdfs.semanticscholar.org/4092/d2c98cdb46d5d625a580bac08fcddc4c1e60.pdf
#Sayah, 2015: https://download.uni-mainz.de/RePEc/pdf/Discussion_Paper_1517.pdf

#Initialization
require(Matrix)
if (length(find.package(package="Rsymphony",quiet=TRUE))==0) install.packages("Rsymphony")
require(Rsymphony)
par(mfrow = c(2,2))

#0. Choose N, n and which methods to run

N = 20
n = ceiling(0.17*N)
run_PD_Erkut = TRUE
run_PD_brute_force = TRUE
run_PD_Erkut_UB_Sayah = TRUE
run_PD_Sayah = TRUE

#1. Make random distance matrix for testing

set.seed(1)

coords <- cbind(runif(N,-5,5),runif(N,-5,5))
distmat <- t(as.matrix(dist(coords,diag=T)))
distmat[lower.tri(distmat)] <- 0
distmat <- Matrix(distmat,sparse=T)

N.i <- NROW(distmat)
colnames(distmat) <- paste("j",1:N.i,sep="_")
rownames(distmat) <- paste("i",1:N.i,sep="_")

#2. Make a 2D representation of the points using classic multidimensional scaling

cmds <- cmdscale(as.dist(t(distmat)))

#3. Link the pairwise distances to the rows and columns of the distmat

distmat_summary <- summary(distmat)
N.ij <- NROW(distmat_summary)
distmat_summary["ID"] <- 1:(N.ij)
i.mat <- xtabs(~ID+i,distmat_summary,sparse=T)
j.mat <- xtabs(~ID+j,distmat_summary,sparse=T)

ij.mat <- cbind(i.mat,0)+cbind(0,j.mat)
colnames(ij.mat)[[N.i]] <- as.character(N.i)

zij.mat <- .sparseDiagonal(n=N.ij,x=1)

#4. MaxMin task by Kuby/Erkut (N binary variables + 1 continuous variable for max Dmin)

if (run_PD_Erkut == TRUE) {

  #4a. Building the constraint matrix (mat), direction (dir), right-hand-side (rhs) and objective (obj) for the LP task
  dij <- distmat_summary$x
  M <- max(dij)
  m <- min(dij)
  #Erkut's condition: for each i,j i<j, D (min distance to maximise) + M*xi + M*xj <= 2*M + dij
  constr.dij <- cbind("D"=1,ij.mat*M)
  dir.dij <- rep("<=",N.ij)
  rhs.dij <- 2*M+dij
  constr.D <- c(1,rep(0,N.i))
  dir.DM <- "<="
  rhs.DM <- M
  dir.Dm <- ">="
  rhs.Dm <- m
  #constraining the total number of objects to be n
  constr.n <- c(0,rep(1,N.i))
  dir.n <- "=="
  rhs.n <- n
  #assembling the constraints
  mat <- rbind(constr.n,constr.dij,constr.D,constr.D)
  dir <- c(dir.n,dir.dij,dir.DM,dir.Dm)
  rhs <- c(rhs.n,rhs.dij,rhs.DM,rhs.Dm)
  #objective
  obj <- setNames(c(1,rep(0,N.i)), c("D",colnames(ij.mat)))

  #4.b. Solution
  st <- system.time(LP.sol <- Rsymphony_solve_LP(obj,mat,dir,rhs,types=c("C",rep("B",N.i)),max=TRUE,verbosity = -2, time_limit = 5*60))
  ij.sol <- names(obj[-1])[as.logical(LP.sol$solution[-1])]
  items.sol <- rownames(distmat)[as.numeric(ij.sol)]
  Dmin <- LP.sol$solution[1]

  #4.c. Plotting the results
  plot(cmds,main=paste(c("p-dispersion (Erkut), N =",N,", n =",n,"\nUB =",round(M,2),", time =",round(st[3],2),"s, Dmin =",round(Dmin,2)),collapse=" ") )
  points(cmds[as.numeric(ij.sol),],pch=16,col="red")
  text(cmds[as.numeric(ij.sol),],ij.sol,cex=0.9,col="red",adj=c(0,1))

}

#5. MaxMin task by brute force

if (run_PD_brute_force == TRUE) {

  if (choose(N,n) <= 200000) {

    st <- system.time({combs <- as.data.frame(t(combn(N,n)))
    combs["maxmin"] <- apply(combs, 1, function(x) {min(distmat_summary[(distmat_summary$j %in% x) & (distmat_summary$i %in% x),"x"])})
    combs["maxsum"] <- apply(combs, 1, function(x) {sum(distmat_summary[(distmat_summary$j %in% x) & (distmat_summary$i %in% x),"x"])})
    combs_maxmin_max <- combs[combs$maxmin == max(combs$maxmin),][1,]})
    ij.sol <- as.character(combs_maxmin_max[,1:n])
    items.sol <- rownames(distmat)[as.numeric(ij.sol)]
    Dmin <- combs_maxmin_max[1,"maxmin"]
    plot(cmds,main=paste(c("p-dispersion (brute force), N =",N,", n =",n,"\ntime =",round(st[3],2),"s, Dmin =",round(Dmin,2)),collapse=" ") )
    points(cmds[as.numeric(ij.sol),],pch=16,col="red")
    text(cmds[as.numeric(ij.sol),],ij.sol,cex=0.9,col="red",adj=c(0,1))
  }

}

#6. MaxMin task by Erkut with Sayah's upper bound

if (run_PD_Erkut_UB_Sayah == TRUE) {

  #6a. Building the constraint matrix (mat), direction (dir), right-hand-side (rhs) and objective (obj) for the LP task
  m <- min(distmat_summary$x)
  M <- sort(sapply(1:(N.i), function(it) {min((sort(distmat_summary[(distmat_summary$i == it) | (distmat_summary$j == it),"x"],decreasing = TRUE)[1:(n-1)]))}),decreasing=TRUE)[n]

  #Erkut's condition: for each i,j i<j, D (min distance to maximise) + M*xi + M*xj <= 2*M + dij
  constr.dij <- cbind("D"=1,ij.mat*M)
  dir.dij <- rep("<=",N.ij)
  rhs.dij <- 2*M+dij
  constr.D <- c(1,rep(0,N.i))
  dir.DM <- "<="
  rhs.DM <- M
  dir.Dm <- ">="
  rhs.Dm <- m
  #constraining the total number of objects to be n
  constr.n <- c(0,rep(1,N.i))
  dir.n <- "=="
  rhs.n <- n
  #assembling the constraints
  mat <- rbind(constr.n,constr.dij,constr.D,constr.D)
  dir <- c(dir.n,dir.dij,dir.DM,dir.Dm)
  rhs <- c(rhs.n,rhs.dij,rhs.DM,rhs.Dm)
  #objective
  obj <- setNames(c(1,rep(0,N.i)), c("D",colnames(ij.mat)))

  #6.b. Solution
  st <- system.time(LP.sol <- Rsymphony_solve_LP(obj,mat,dir,rhs,types=c("C",rep("B",N.i)),max=TRUE,verbosity = -2, time_limit = 5*60))
  ij.sol <- names(obj[-1])[as.logical(LP.sol$solution[-1])]
  items.sol <- rownames(distmat)[as.numeric(ij.sol)]
  Dmin <- LP.sol$solution[1]

  #6.c. Plotting the results

  plot(cmds,main=paste(c("p-dispersion (Erkut, UB by Sayah), N =",N,", n =",n,"\nUB =",round(M,2),", time =",round(st[3],2),"s, Dmin =",round(Dmin,2)),collapse=" ") )
  points(cmds[as.numeric(ij.sol),],pch=16,col="red")
  text(cmds[as.numeric(ij.sol),],ij.sol,cex=0.9,col="red",adj=c(0,1))

}

#7. MaxMin task by Sayah (N binary variables + binary variables from unique values of dij)

if (run_PD_Sayah == TRUE) {

  #7a. Building the constraint matrix (mat), direction (dir), right-hand-side (rhs) and objective (obj) for the LP task
  #7a.1. Finding the upper (M) and lower (m) bound for the minimal distance
  m <- min(distmat_summary$x)
  M <- sort(sapply(1:(N.i), function(it) {min((sort(distmat_summary[(distmat_summary$i == it) | (distmat_summary$j == it),"x"],decreasing = TRUE)[1:(n-1)]))}),decreasing=TRUE)[n]
  dijs <- unique(sort(distmat_summary$x))
  dijs <- dijs[dijs <= M]
  N.dijs <- length(dijs)
  z.mat <- .sparseDiagonal(N.dijs,1)

  #Sayah's formulation:

  #applying z[k] <= z[k-1]
  constr.z <- cbind(rep(0,N.i*(N.dijs-1)),cbind(0,z.mat[-1,-1])-z.mat[-NROW(z.mat),])
  dir.z <- rep("<=",N.dijs-1)
  rhs.z <- rep(0,N.dijs-1)
  #applying x[i]+x[j]+z[k] <= 2
  constr.ijk <- NULL
  for (k in 2:N.dijs) {
    IDs <- distmat_summary[distmat_summary$x < dijs[k],"ID"]
    constr.ijk <- rbind(constr.ijk,cbind(ij.mat[IDs,,drop=F],z.mat[rep(k,length(IDs)),,drop=F]))
  }
  dir.ijk <- rep("<=",NROW(constr.ijk))
  rhs.ijk <- rep(2,NROW(constr.ijk))
  #constraining the total number of objects to be n
  constr.n <- c(rep(1,N.i),rep(0,N.dijs))
  dir.n <- "=="
  rhs.n <- n
  #assembling the constraints
  mat <- rbind(constr.n,constr.z,constr.ijk)
  dir <- c(dir.n,dir.z,dir.ijk)
  rhs <- c(rhs.n,rhs.z,rhs.ijk)
  #objective
  obj <- setNames(c(rep(0,N.i),1,diff(dijs)), c(colnames(ij.mat),paste("z",1:N.dijs,sep="_")))

  #7.b. Solution
  st <- system.time(LP.sol <- Rsymphony_solve_LP(obj,mat,dir,rhs,types="B",max=TRUE,verbosity = -2, time_limit = 5*60))
  ij.sol <- names(obj[1:N.i])[as.logical(LP.sol$solution[1:N.i])]
  items.sol <- rownames(distmat)[as.numeric(ij.sol)]
  Dmin <- sum(LP.sol$solution[(1+N.i):(N.dijs+N.i)]*obj[(1+N.i):(N.dijs+N.i)])

  #7.c. Plotting the results
  plot(cmds,main=paste(c("p-dispersion (Sayah), N =",N,", n =",n,"\nUB =",round(M,2),", time =",round(st[3],2),"s, Dmin =",round(Dmin,2)),collapse=" ") )
  points(cmds[as.numeric(ij.sol),],pch=16,col="red")
  text(cmds[as.numeric(ij.sol),],ij.sol,cex=0.9,col="red",adj=c(0,1))

}

1 个答案:

答案 0 :(得分:0)

您没有提到是否可以容忍非最佳解决方案。但是您应该能够,因为您不能期望能够通常找到针对此问题的最佳解决方案。在这种情况下,存在2因子近似值。

Let V be the set of nodes/objects
Let i and j be two nodes at maximum distance
Let p be the number of objects to choose
p = set([i,j])
while size(P)<p:
  Find a node v in V-P such that min_{v' in P} dist(v,v') is maximum
  \That is: find the node with the greatest minimum distance to the set P
  P = P.union(v)
Output P

保证该近似算法可以找到不超过最优值两倍的值,除非P = NP,否则多项式时间启发式算法都不能提供更好的性能保证。

White (1991)Ravi et al. (1994)中证明了最优边界。后者证明了启发式方法是最好的。

作为参考,我为p = 50,n = 400运行了完整的MIP。 6000s之后,最佳差距仍然是568%。逼近算法需要0.47s的时间才能获得100%(或更少)的最优间隙。

逼近算法的Python(对不起,我没有在R中建模)表示如下:

#!/usr/bin/env python3

import numpy as np

p = 50
N = 400

print("Building distance matrix...")
d = np.random.rand(N,N) #Random matrix
d = (d + d.T)/2             #Make the matrix symmetric

print("Finding initial edge...")
maxdist  = 0
bestpair = ()
for i in range(N):
  for j in range(i+1,N):
    if d[i,j]>maxdist:
      maxdist = d[i,j]
      bestpair = (i,j)

P = set()
P.add(bestpair[0])
P.add(bestpair[1])

print("Finding optimal set...")
while len(P)<p:
  print("P size = {0}".format(len(P)))
  maxdist = 0
  vbest = None
  for v in range(N):
    if v in P:
      continue
    for vprime in P:
      if d[v,vprime]>maxdist:
        maxdist = d[v,vprime]
        vbest   = v
  P.add(vbest)

print(P)

Gurobi Python表示可能如下所示:

#!/usr/bin/env python
import numpy as np
import gurobipy as grb

p = 50
N = 400

print("Building distance matrix...")
d = np.random.rand(N,N) #Random matrix
d = (d + d.T)/2             #Make the matrix symmetric

m = grb.Model(name="MIP Model")

used  = [m.addVar(vtype=grb.GRB.BINARY) for i in range(N)]

objective = grb.quicksum( d[i,j]*used[i]*used[j] for i in range(0,N) for j in range(i+1,N) )

m.addConstr(
  lhs=grb.quicksum(used),
  sense=grb.GRB.EQUAL,
  rhs=p
)

# for maximization
m.ModelSense = grb.GRB.MAXIMIZE
m.setObjective(objective)

# m.Params.TimeLimit = 3*60

# solving with Glpk
ret = m.optimize()