【问题标题】:Minimization problem with linking constraints in RR中链接约束的最小化问题
【发布时间】:2020-06-01 06:51:52
【问题描述】:

我应该如何将链接约束纳入方程 1 - 4 的优化问题公式中,其中 Y_i 是二元变量?

minimize
    22X11 + 20.5X12 + 21X13 + 19X14 + 21X21 + 21X22 + 18X23 + 17X24 +
    22X31 + 24X32 + 22X33 + 23X34 + 22X41 + 16X42 + 17X43 + 17.5X44 + 
    1500 Y1 + 1700 Y2 + 1500 Y3 + 1400 Y4

subject to constraints

    2.00 X11 + 1.80 X21 + 2.30 X31 + 2.10 X41 >= 750
    2.80 X12 + 2.30 X22 + 2.20 X32 + 2.60 X42 >= 800
    1.70 X13 + 1.75 X23 + 1.60 X33 + 1.90 X43 >= 1000
    2.40 X14 + 1.90 X24 + 2.60 X34 + 2.40 X44 >= 300

    X11 - 1500 Y1 <= 0
    X21 - 2000 Y2 <= 0
    X31 - 1500 Y3 <= 0
    X41 - 1800 Y4 <= 0
    X12 - 500 Y1 >= 0
    X22 - 500 Y2 >= 0
    X32 - 500 Y3 >= 0
    X42 - 500 Y4 >= 0

    Y1 = 1 if X11>0 and 0 otherwise 
    Y1 = 1 if X12>0 and 0 otherwise 
    Y2 = 1 if X21>0 and 0 otherwise 
    Y2 = 1 if X22>0 and 0 otherwise 
    Y3 = 1 if X31>0 and 0 otherwise 
    Y3 = 1 if X32>0 and 0 otherwise 
    Y4 = 1 if X41>0 and 0 otherwise 
    Y4 = 1 if X42>0 and 0 otherwise 

在没有那些链接变量的情况下找到最佳解决方案的 R 代码:

library(lpSolve)
f.obj <-  c(22,20.5,21,19,21,21,18,17,22,24,22,23,22,16,17,17.5,1500,1700,1500,1400) 

const1 = c(2.00,1.80,2.30,2.10)
const2 = c(2.80,2.30,2.20,2.60)
const3 = c(1.70,1.75,1.60,1.90)
const4 = c(2.40,1.90,2.60,2.40)

f.con <- rbind(const1,const2,const3,const4)
f.dir <- c(rep(">=",2), ">=", ">=")
f.rhs <- c(750,800,1000,300)
res <- lp("min", f.obj, f.con,f.dir,f.rhs, binary.vec=c(1,2))

res$solution
res$objective
res$objval 

【问题讨论】:

  • 嘿 chinsoon,Xij 是正数。我尝试运行代码,它为我提供了最佳解决方案,但我需要帮助来满足二进制变量。
  • 但是如果所有的 Yi 都是 0,这是否意味着我的二进制约束无法链接?感谢您的编辑!
  • 让 Ax >= b 成为你的约束。你的 A 是一个 4x4 矩阵,但有 16 个 Xij 而不是 4。即使没有链接变量,公式也确实存在很多问题
  • A 应该是一个 12x20 矩阵
  • 我在约束标志上写错了,我现在修改一下

标签: r matrix linear-programming


【解决方案1】:

我应该如何合并链接约束以满足使用 Binary.vec 的 Y1 = 1、Y2 = 2、Y3 = 3 和 Y4 = 4 的约束?

我相信你的意思是Y[i] = 1 [ for i in 1:4 ]。
binary.vec 参数是一个

像 int.vec 这样的数值向量,给出了二进制变量的索引。

现在这里的索引指的是优化问题。因此我们需要索引来匹配Y[i] [for i in 1:4 ] 的位置。在这种情况下,它是您的目标函数中的后 4 个,因此我们可以将它们的索引设为

binary.vec <- seq(length(f.obj), length(f.obj) - 3)

但是请注意,您当前的问题不是在寻找严格的二进制Y[i],而是在给定其他参数的值的情况下进一步寻找Y[i]。正如它所写的那样,它不是线性约束,因此我们需要做一些诡计。
这里的问题是您需要Y[i] = 1 if X[i,1] &gt; 0 and X[i, 2] &gt; 0, 0 otherwise,而简单地添加Y[i] %in% 0:1 不一定会支持约束。有几种方法可以解决这个问题,但我发现最直观的一种方法是使用 pseudo-parameters 重新表述问题

C * z[i, j] > x[i, j] [ for all i, j ]
z[i, j] %in% 0:1 [ for all i, j ]
C = Inf 
z[i, j] = 1 if X[i, j] > 0, 0 otherwise [ for i in 1:4, j in 1:2 ]
Y[i] <= 0.5 * z[i, 1] + 0.5 * z[i, 2]

这里的诀窍是现在我们只有线性约束。请注意,如果只有z[i, 1] = 1 这些约束会强制Y[i] = 0。
我们增加了相当多的参数数量,但这个问题是线性,因为所有方程都以y = a * x + b 的形式编写。

但是,正如我在对您的previous question 的回答中所描述的,当我们添加我们需要的伪参数时

  1. 扩展我们的目标函数,使其包含这些伪参数(让求解器了解优化函数中的参数)
  2. 扩展我们的约束,以保持尺寸正确(让求解器了解这些对剩余约束的影响)

扩展优化问题

现在这是简单的部分。只需在现有方程的末尾添加参数即可。如果发现难以跟踪,我总是建议命名目标函数

n <- length(f.obj)
names(f.obj)[seq(n - 4)] <- paste0('X[', rep(1:4, 2), ', ', rep(1:2, each = 4), ']')
names(f.obj)[-seq(n - 4)] <- paste0('Y[', 1:4, ']')
f.obj <- c(f.obj, numeric(8))
n <- length(f.obj)
names(f.obj)[seq(n - 7, n)] <- paste0('z[', rep(1:4, 2), ', ', rep(1:2, each = 4), ']')
tail(f.obj, 12)
   Y[1]    Y[2]    Y[3]    Y[4] z[1, 1] z[2, 1] z[3, 1] z[4, 1] z[1, 2] z[2, 2] z[3, 2] z[4, 2] 
   1500    1700    1500    1400       0       0       0       0       0       0       0       0 

请注意,它们的权重都为 0,因为它们的效果被 X[i, j] 和 Y[i] 的效果所捕获。

扩展你的约束

现在这又是棘手的部分。首先请注意,在我的answer 中对上一个问题,您的约束的尺寸必须与您的目标函数相匹配。在这个问题中,你有 length(f.obj) = 28(包括新的伪参数),所以 all 约束需要有 28 列。这里的一般想法是我们(形象地)将sum_[i, j] 0 * X[i, j] + sum_[i] 0 * Y[i] 添加到每个约束。因此你的约束应该是

cons1 <- numeric(n)
names(cons1) <- names(f.obj)
cons12 <- cons11 <- cons10 <- cons9 <- cons8 <- cons7 <- cons6 <- cons5 <- cons4 <- cons3 <- cons2 <- cons1
cons1[1:4] <- c(2, 1.8, 2.3, 2.1)
cons2[5:8] <- c(2.8, 2.3, 2.2, 2.6)
cons3[9:12] <- c(1.7, 1.75, 1.6, 1.9)
cons4[13:16] <- c( 2.4, 1.9, 2.6, 2.4)
#X[i, j] - Cy[i] * Y[i] <= 0
cons5[c('X[1, 1]', 'Y[1]')] <- c(1, -1500)
cons6[c('X[2, 1]', 'Y[2]')] <- c(1, -2000)
cons7[c('X[3, 1]', 'Y[3]')] <- c(1, -1500)
cons8[c('X[4, 1]', 'Y[4]')] <- c(1, -1800)
cons9[c('X[1, 2]', 'Y[1]')] <- c(1, -500)
cons10[c('X[2, 2]', 'Y[2]')] <- c(1, -500)
cons11[c('X[3, 2]', 'Y[3]')] <- c(1, -500)
cons12[c('X[4, 2]', 'Y[4]')] <- c(1, -500)
#Pseudo-constraints
## Start with C * z[i, j] > x[i, j] for some very large C (here i choose 1e12, but could be any number large enough so X[i, j] does not exceed it!)
pseudo_z_1 <- numeric(n)
names(pseudo_z_1) <- names(f.obj)
pseudo_z_8 <- pseudo_z_7 <- pseudo_z_6 <- pseudo_z_5 <- pseudo_z_4 <- pseudo_z_3 <- pseudo_z_2 <- pseudo_z_1
C <- 1e12
pseudo_z_1[c('X[1, 1]', 'z[1, 1]')] <- c(-1, C)
pseudo_z_2[c('X[2, 1]', 'z[2, 1]')] <- c(-1, C)
pseudo_z_3[c('X[3, 1]', 'z[3, 1]')] <- c(-1, C)
pseudo_z_4[c('X[4, 1]', 'z[4, 1]')] <- c(-1, C)
pseudo_z_5[c('X[1, 2]', 'z[1, 2]')] <- c(-1, C)
pseudo_z_6[c('X[2, 2]', 'z[2, 2]')] <- c(-1, C)
pseudo_z_7[c('X[3, 2]', 'z[3, 2]')] <- c(-1, C)
pseudo_z_8[c('X[4, 2]', 'z[4, 2]')] <- c(-1, C)
## Next create pseudo constraints for Y[i] <= 0.5 * Z[i, 1] + 0.5 * Z[i, 2]
pseudo_y_1 <- numeric(n)
names(pseudo_y_1) <- names(f.obj)
pseudo_y_4 <- pseudo_y_3 <- pseudo_y_2 <- pseudo_y_1
pseudo_y_1[c('Y[1]', 'z[1, 1]', 'z[1, 2]')] <- c(1, -0.5, -0.5)
pseudo_y_2[c('Y[2]', 'z[2, 1]', 'z[2, 2]')] <- c(1, -0.5, -0.5)
pseudo_y_3[c('Y[3]', 'z[3, 1]', 'z[3, 2]')] <- c(1, -0.5, -0.5)
pseudo_y_4[c('Y[4]', 'z[4, 1]', 'z[4, 2]')] <- c(1, -0.5, -0.5) 
#Combine them all together
cons <- c(paste0('cons', 1:12), paste0('pseudo_z_', 1:8), paste0('pseudo_y_', 1:4))
cons.mat <- do.call(rbind, mget(cons)) #mget finds all variables given in cons.

呼...然后我们必须再次创建我们的direction 和rhs。我们必须确保binary.vec 将z[i, j] 指定为二进制。让我们按顺序执行此操作

cons.dir <- c(rep('>=', 4), rep('<=', 8), rep('>=' 8), rep('<=', 4))
names(cons.dir) <- cons
cons.rhs <- c(750, 800, 1000, 300, rep(0, 8), rep(0, 8), rep(0, 4))
#Update binary.vec to include z[i, j]
binary.vec <- c(binary.vec, seq(n, n - 7))

现在我们应该已经准备好一切准备好了

lpSolve::lp('min', objective.in = f.obj, 
            const.mat = cons.mat,
            const.dir = cons.dir, 
            const.rhs = cons.rhs, 
            binary.vec = binary.vec)
res$objval
[1] 27483.29
names(res$solution) <- names(f.obj)
options(scipen = 999) #avoid scientific notation. Just to make it readable for us.
round(res$solution[res$solution != 0], 3)
X[4, 1] X[4, 2] X[4, 3] X[3, 4]    Y[4] z[4, 1] z[4, 2] 
357.143 307.692 526.316 115.385   1.000   1.000   1.000 

这似乎符合我们的限制条件。由于复制粘贴到 R 中比较麻烦,我已将代码添加到 this repository,可以加载和执行。

旁注

我在这里说明解决方案的方式是一个非常说明性的分步方法。这使得跟随变得简单,但实际上人们会使用for-loops 或允许更自然地编写约束的软件。如果问题变得比这更复杂,我强烈建议使用替代包,它也可能实现更快的算法。

在满足链接变量之前是否应该先生成最优解?

在您当前的问题中,您没有任何链接变量。链接变量通常是从子优化问题获得的变量,例如

Max sum_i a[i] * y[i] * x[i] [for i in 1:10]
st.
y[i] %in% 0:1
x[i] = argmin{ 
  3 x[i]^2 + 4  
  st.
  x[i] != x[j] [ for all i, j ]
}

在这种情况下,标准优化问题不一定有用。

【讨论】:

  • 好疯狂的问题啊啊啊
  • 它太复杂了,我懒得检查约束
  • 通过约束,我发布的解决方案似乎有 0 个错误(不过我可能是盲人)。所以这似乎是最佳解决方案。
  • 嗨 Oliver 和 Chinsoon,非常感谢你们的回复。我仍在阅读您的代码,因为我对 R 比较陌生。
  • 很高兴我们能提供帮助。随意问,如果有什么似乎没有多大意义。 :-)
【解决方案2】:

这是一个选项。

将 X_ij 转换为小数。我只是使用比这里看到的值大一个数量级。

library(lpSolve)
n <- 20
denom <- 10e3
f.obj <- c(c(22,20.5,21,19,
    21,21,18,17,
    22,24,22,23,
    22,16,17,17.5) / denom,
    1500,1700,1500,1400) 

#identify location of variables for easy identification
vars <- c(paste0("X", c(outer(1:4, 1:4, paste0))), paste0("Y", 1:4))
vars <- setNames(seq_along(vars), vars)

#X11 X21 X31 X41 X12 X22 X32 X42 X13 X23 X33 X43 X14 X24 X34 X44  Y1  Y2  Y3  Y4 
#  1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20 

在适当缩小系数的同时根据 OP 进行约束:

const01 <- replace(rep(0, n), 1:4, c(2.00,1.80,2.30,2.10))
const02 <- replace(rep(0, n), 5:8, c(2.80,2.30,2.20,2.60))
const03 <- replace(rep(0, n), 9:12, c(1.70,1.75,1.60,1.90))
const04 <- replace(rep(0, n), 13:16, c(2.40,1.90,2.60,2.40))

#X11, X12, Y1
const05 <- replace(rep(0, n), c(1,17), c(-1, 1500 / denom))
const06 <- replace(rep(0, n), c(5,17), c(1, -500 / denom))

#X21, X22, Y2
const07 <- replace(rep(0, n), c(2,18), c(-1, 2000 / denom))
const08 <- replace(rep(0, n), c(6,18), c(1, -500 / denom))

#X31, X32, Y3
const09 <- replace(rep(0, n), c(3,19), c(-1, 1500 / denom))
const10 <- replace(rep(0, n), c(7,19), c(1, -500 / denom))

#X41, X42, Y4
const11 <- replace(rep(0, n), c(4,20), c(-1, 1800 / denom))
const12 <- replace(rep(0, n), c(8,20), c(1, -500 / denom))

对于链接变量,使用 X

#X11, X12, Y1
const11 <- replace(rep(0, n), c(1,17), c(-1, 1))
const12 <- replace(rep(0, n), c(5,17), c(-1, 1))

#X21, X22, Y2
const13 <- replace(rep(0, n), c(2,18), c(-1, 1))
const14 <- replace(rep(0, n), c(6,18), c(-1, 1))

#X31, X32, Y3
const15 <- replace(rep(0, n), c(3,19), c(-1, 1))
const16 <- replace(rep(0, n), c(7,19), c(-1, 1))

#X41, X42, Y4
const17 <- replace(rep(0, n), c(4,20), c(-1, 1))
const18 <- replace(rep(0, n), c(8,20), c(-1, 1))

在适当缩小 RHS 的同时调用求解器:

f.con <- do.call(rbind, mget(ls(pattern="^const")))
f.dir <- rep(">=", nrow(f.con))
f.rhs <- c(c(750,800,1000,300) / denom, rep(0, nrow(f.con) - 4))
res <- lp("min", f.obj, f.con, f.dir, f.rhs, binary.vec=17:20)

res$solution
# [1] 0.00000000 0.00000000 0.00000000 0.03571429 0.00000000 0.00000000 0.00000000 0.03076923
# [9] 0.00000000 0.00000000 0.00000000 0.05263158 0.00000000 0.00000000 0.01153846 0.00000000
#[17] 0.00000000 0.00000000 0.00000000 1.00000000

扭转收缩:

(optval <- setNames(replace(res$solution, 1:16, denom * res$solution[1:16]), names(vars)))

以及最优值:

     X11      X21      X31      X41      X12      X22      X32      X42      X13      X23 
  0.0000   0.0000   0.0000 357.1429   0.0000   0.0000   0.0000 307.6923   0.0000   0.0000 
     X33      X43      X14      X24      X34      X44       Y1       Y2       Y3       Y4 
  0.0000 526.3158   0.0000   0.0000 115.3846   0.0000   0.0000   0.0000   0.0000   1.0000 

对象值:

sum(optval * replace(f.obj, 1:16, denom * f.obj[1:16]))
#[1] 27483.29

【讨论】:

  • 附带说明,您的目标值没有考虑denom。看起来应该是sum(optval * f.obj * c(rep(denom, 16), rep(1, 4))) = 41585.71。
  • 并且在最初的问题中似乎至少有 4 个约束中的 1 个没有得到尊重。
  • @Oliver,是的,ls 是按变量名排序的,而不是按预期的顺序排序。现在我们得到了相同的答案
猜你喜欢
  • 2022-01-11
  • 2019-03-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-03
  • 1970-01-01
  • 1970-01-01
  • 2020-08-03
相关资源
最近更新 更多