#r #linear-programming #lpsolve
#r #линейное программирование #lpsolve
Вопрос:
Я хорошо разбираюсь в использовании LPSolve
для задач линейной оптимизации, но один аспект зашел в тупик. Я хотел бы создать ограничение для суммы нескольких столбцов. Например, у меня есть ограничение, которое запрещает любому из четырех определенных столбцов быть больше 3. Однако я требую, чтобы любой один из четырех столбцов был равен 3.
РАБОЧИЙ ПРИМЕР
В этом примере я готовлю блюда, чтобы оптимизировать «ценность», сохраняя при этом 5 отдельных блюд и 40 долларов в стоимости. У меня также есть четыре разные группы продуктов — мясо, овощи, фрукты, крахмал — и я требую, чтобы в блюде было не более четырех блюд из любой одной группы, но в любой одной группе должно быть 3 блюда (вот тут я и зашел в тупик).
Ниже приведен код, который дает мне желаемый результат, за исключением последнего ограничения:
## Choose 5 food items remaining under $40 and maximizing Value ##
## There can be no more than 3 items from the same group chosen, but **there must be 3 items from at least one group**(??) ##
library(dplyr)
library(lpSolve)
# Constraints
totalItems <- 5
totalCost <- 40
maxAllGroups <- 3
# Setup problem
food <- c('Chicken', 'Beef', 'Lamb', 'Fish', 'Pork', 'Carrot', 'Lettuce', 'Asparagus', 'Beats', 'Broccoli', 'Orange', 'Apple', 'Pear', 'Banana', 'Watermelon', 'Potato', 'Corn', 'Beans', 'Bread', 'Pasta')
group <- c('Meat', 'Meat', 'Meat', 'Meat', 'Meat', 'Veggie', 'Veggie', 'Veggie', 'Veggie', 'Veggie', 'Fruit', 'Fruit', 'Fruit', 'Fruit', 'Fruit', 'Starch', 'Starch', 'Starch', 'Starch', 'Starch')
cost <- round(runif(length(food), 1, 20), 0)
value <- round(runif(length(food), 20, 60), 0)
df <- data.frame(food, group, cost, value, stringsAsFactors = FALSE) %>%
mutate(Total = 1)
# Value to be maximized
Value <- df$value
# Create constraint vectors
ConVec_Cost <- df$cost
ConVec_Items <- df$Total
# Make `Group` dummy variables
groups <- unique(df$group)
ConVec_Groups <- data.frame(row.names = 1:nrow(df))
for(i in 1:length(groups)){
currGroup <- groups[i]
vec <- df %>%
mutate(isGroup = (group == currGroup)*1) %>%
select(isGroup)
colnames(vec) <- currGroup
ConVec_Groups <- cbind(ConVec_Groups, vec)
}
# ConVec_AnyGroupEqual3 <- ???
ConVec_All <- t(cbind(ConVec_Cost, ConVec_Items, ConVec_Groups))
# Create constraint directions
ConDir_Cost <- "<="
ConDir_Items <- "=="
ConDir_Groups <- rep("<=", ncol(ConVec_Groups))
# ConDir_AnyGroupEqual3 <- "=="
ConDir_All <- c(ConDir_Cost, ConDir_Items, ConDir_Groups)
# Create constraint values
ConVal_Cost <- totalCost
ConVal_Items <- totalItems
ConVal_Groups <- rep(maxAllGroups, ncol(ConVec_Groups))
# ConVal_AnyGroupEqual3 <- 1 #1 group should have 3
ConVal_All <- c(ConVal_Cost, ConVal_Items, ConVal_Groups)
# Solve
sol <- lpSolve::lp("max",
objective.in = Value,
const.mat = ConVec_All,
const.dir = ConDir_All,
const.rhs = ConVal_All,
all.bin = TRUE
)
# Solution
df[sol$solution == 1,]
Если бы мне нужно было, чтобы в определенной группе продуктов было 3, тогда это было бы легко, но тот факт, что мне нужно, чтобы в любой из групп было 3, затрудняет это. Есть ли способ сделать это, не прибегая к LPSolveAPI
(о котором я, по общему признанию, мало знаю)?
Ответ №1:
Я понял это, проанализировав чей-то ответ на аналогичный вопрос некоторое время назад. По сути, вам нужно добавить 5 «фиктивных строк» (по одной для каждой продовольственной группы) со значением -3 по диагонали. Затем вам нужно добавить еще один столбец со всеми значениями, равными 0, за исключением того, что последние 5 строк равны 1 (строки, которые вы только что добавили в матрицу). Вы заставляете новый столбец иметь значение не менее 1, что означает, что должна быть выбрана одна из этих последних 5 строк.
Поскольку будет выбрана одна из этих строк, и эта строка добавляет -3 к любой группе продуктов, в которую она входит, и у вас есть ограничения, которые заставляют каждую группу продуктов принимать значение не менее 0, то выбранная строка -3 заставит этот столбец группы продуктов выбрать 3, чтобы сумма была > = 0.
НОВЫЙ КОД
## Choose 5 food items remaining under $40 and maximizing Value ##
## There can be no more than 3 items from the same group chosen, but **there must be 3 items from at least one group**(??) ##
library(dplyr)
library(lpSolve)
# Constraints
totalItems <- 5
totalCost <- 40
minAllGroups <- 0
atLeastNFrom1 <- 3
# Setup problem
food <- c('Chicken', 'Beef', 'Lamb', 'Fish', 'Pork', 'Carrot', 'Lettuce', 'Asparagus', 'Beats', 'Broccoli', 'Orange', 'Apple', 'Pear', 'Banana', 'Watermelon', 'Potato', 'Corn', 'Beans', 'Bread', 'Pasta')
group <- c('Meat', 'Meat', 'Meat', 'Meat', 'Meat', 'Veggie', 'Veggie', 'Veggie', 'Veggie', 'Veggie', 'Fruit', 'Fruit', 'Fruit', 'Fruit', 'Fruit', 'Starch', 'Starch', 'Starch', 'Starch', 'Starch')
cost <- round(runif(length(food), 1, 20), 0)
value <- round(runif(length(food), 20, 60), 0)
df <- data.frame(food, group, cost, value, stringsAsFactors = FALSE) %>%
mutate(Total = 1)
# Create constraint vectors
ConVec_Cost <- df$cost
ConVec_Items <- df$Total
# Make `Group` dummy variables
groups <- unique(df$group)
ConVec_Groups <- data.frame(row.names = 1:nrow(df))
for(i in 1:length(groups)){
currGroup <- groups[i]
vec <- df %>%
mutate(isGroup = (group == currGroup)*1) %>%
select(isGroup)
colnames(vec) <- currGroup
ConVec_Groups <- cbind(ConVec_Groups, vec)
}
# New vector for atleast
ConVec_AtLeastN <- 0
ConVec_All <- cbind(ConVec_Cost, ConVec_Items, ConVec_Groups, ConVec_AtLeastN)
# Add the negative values to the matrix
ConVec_All <- rbind(ConVec_All, c(0,0,-atLeastNFrom1,0,0,0,1))
ConVec_All <- rbind(ConVec_All, c(0,0,0,-atLeastNFrom1,0,0,1))
ConVec_All <- rbind(ConVec_All, c(0,0,0,0,-atLeastNFrom1,0,1))
ConVec_All <- rbind(ConVec_All, c(0,0,0,0,0,-atLeastNFrom1,1))
# Create constraint directions
ConDir_Cost <- "<="
ConDir_Items <- "=="
ConDir_Groups <- rep(">=", ncol(ConVec_Groups))
ConDir_AtLeastN <- ">="
ConDir_All <- c(ConDir_Cost, ConDir_Items, ConDir_Groups, ConDir_AtLeastN)
# Create constraint values
ConVal_Cost <- totalCost
ConVal_Items <- totalItems
ConVal_Groups <- rep(minAllGroups, ncol(ConVec_Groups))
ConVal_AtLeastN <- 1
ConVal_All <- c(ConVal_Cost, ConVal_Items, ConVal_Groups, ConVal_AtLeastN)
# Value to be maximized
Value <- c(df$value, rep(0, nrow(ConVec_All) - length(df$value)))
# Solve
sol <- lpSolve::lp("max",
objective.in = Value,
const.mat = t(ConVec_All),
const.dir = ConDir_All,
const.rhs = ConVal_All,
all.bin = TRUE
)
# Solution
df[sol$solution[1:nrow(df)] == 1,]
Комментарии:
1. Я запустил этот пример, и похоже, что ограничение максимальной цены не соблюдается.