当前位置: 首页 > 知识库问答 >
问题:

具有二进制调度变量的R:成本函数优化?

慎弘化
2023-03-14

下面详细介绍了我在解决优化问题时遇到的问题的简化版本。

目标是使通过卡车运送水的组织的成本函数最小化,并使用该方程生成一个使成本最小化的卡车运送时间表。

该组织全年向约10000个家用水箱供水。

这些油箱的最大容量为300加仑,最小期望限值为100加仑,也就是说,在油箱低于100加仑之前,应将油箱加满300加仑。

例如,如果储罐在第2周为115加仑,预计在第3周使用20加仑,则需要在第3周重新加注。

费用包括:

>

卡车的周成本。一辆卡车每周的费用是1000美元。因此,如果一周内交付200次,成本为3000美元。如果交付201次,成本将大幅飙升至4010美元(201*10 1000*2)。

用水量因家庭和周而异。用水量峰值出现在夏季。如果我们盲目地遵循规则,在达到100加仑最低限量之前加油,那么如果交付分散到夏季的“肩膀”,卡车的峰值数量很可能会高于需求。

我已经为每个家庭每周的用水量做了估计。此外,我还像家庭一样分组,以减少最佳化问题的规模(~10k家庭减少到8组)。

重申目标:这个优化器的输出应该是:对于每个家庭群体,在一年中的52周中的每一周,交付与否。

简化数据(即8组12周):

df.usage <-  structure(list(reduction.group = c(1, 1, 1, 1, 1, 1, 1, 1, 1, 
                                                1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 
                                                3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 
                                                5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 
                                                7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 
                                                8, 8, 8), week = c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 
                                                                   2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 
                                                                   10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 
                                                                   5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 
                                                                   12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 
                                                                   7, 8, 9, 10, 11, 12), water_usage = c(46, 50, 42, 47, 43, 39, 
                                                                                                         38, 32, 42, 36, 42, 30, 46, 50, 42, 47, 43, 39, 38, 32, 42, 36, 
                                                                                                         42, 30, 46, 50, 43, 47, 43, 39, 38, 32, 42, 36, 42, 30, 46, 50, 
                                                                                                         43, 47, 43, 39, 38, 32, 42, 36, 42, 30, 29, 32, 27, 30, 27, 25, 
                                                                                                         24, 20, 26, 23, 27, 19, 29, 32, 27, 30, 27, 25, 24, 20, 26, 23, 
                                                                                                         27, 19, 29, 32, 27, 30, 28, 25, 25, 21, 27, 23, 27, 19, 29, 32, 
                                                                                                         27, 30, 28, 25, 25, 21, 27, 23, 27, 20), tank.level.start = c(115, 
                                                                                                                                                                        NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 165, NA, NA, NA, 
                                                                                                                                                                        NA, NA, NA, NA, NA, NA, NA, NA, 200, NA, NA, NA, NA, NA, NA, 
                                                                                                                                                                        NA, NA, NA, NA, NA, 215, NA, NA, NA, NA, NA, NA, NA, NA, NA, 
                                                                                                                                                                        NA, NA, 225, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 230, 
                                                                                                                                                                        NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 235, NA, NA, NA, 
                                                                                                                                                                        NA, NA, NA, NA, NA, NA, NA, NA, 240, NA, NA, NA, NA, NA, NA, 
                                                                                                                                                                        NA, NA, NA, NA, NA)), row.names = c(NA, 96L), class = "data.frame")

油箱液位补充规则

以下是一组嵌套的循环,用于通过“重新加注”逻辑确定随时间变化的储罐液位:

library(dplyr)

reduction.groups <- unique(df.usage$reduction.group)
df.after.refill.logic <- list()

for (i in reduction.groups) {

  temp <- df.usage %>% filter(reduction.group == i)
  temp$refilled <- 0
  temp$level <- temp$tank.level.start

  n <- nrow(temp)

  if (n > 1) for (j in 2:n) {
    temp$level[j] <- ( temp$level[j-1] - temp$water_usage[j] )
    if(temp$level[j] < 100) {
      temp$level[j] <- 300
      temp$refilled[j] <- 1
    }
  }
  df.after.refill.logic <- bind_rows(df.after.refill.logic, temp)
}

决策变量

每年每周向每个组交付与否(二进制)

约束条件

无部分卡车:卡车数量必须为整数

卡车容量:卡车交付量/周

油箱不能低于100加仑:液位

传递必须为二进制

常数

1600 # truck_weekly_costs
10 # cost_per_delivery
200 # weekly_delivery_capacity_per_truck

成本函数示例

weekly_cost_function <- function(i){
  cost <- (ceiling(sum(i)/200)) * 1600 + (sum(i) * 10)
  cost
}

**example cost for one week with i = 199 deliveries:**
weekly_cost_function(i = 199)
[1] 3590

尝试使用OMPR对问题进行建模

下面是使用OMPR包创建的模型的开头(尽管可以使用其他包):

我对如何使用上面的数据进行设置感到困惑。三个明显的问题:

  1. 如何在OMPR代码中包含示例成本函数中表示的上限逻辑
  2. 下面的模型没有将数据合并到上面的数据框中(df.usage)。优化器的目标是根据四个变量(reduction.group、week、water\u usage、tank\u level\u start)以及常量生成“refilled”和“level”变量的值
  3. 我在上面的“确定油箱液位”循环中编写的加注逻辑没有包含在内。是否应将其添加为约束?如果是,如何
num_groups <- length(unique(df.usage$reduction.group))
num_weeks <- length(unique(df.usage$week))

MIPModel() %>%
  add_variable(x[i,w],                         # create decision variable: deliver or not by...
               i = 1:num_groups,               # group,
               w = 1:num_weeks,                # in week.
               type = "integer",               # Integers only
               lb = 0, ub = 1) %>%             # between 0 and 1, inclusive 
  set_objective(sum_expr( x[i,w]/200 * 1600 + x[i,w] * 10,
                          i = 1:num_groups, 
                          w = 1:num_weeks),
                sense = "min") %>%
  # add constraint to achieve ceiling(x[i,w]/200), or should this be in the set_objective call?
  add_constraint(???) %>%
  solve_model(with_ROI("glpk"))

所需输出

以下是示例head()输出的样子:


 reduction.group   week   water.usage  refill   level
               1      1            46       0     115
               1      2            50       1     300
               1      3            42       0     258
               1      4            47       0     211
               1      5            43       0     168
               1      6            39       0     129

重要的是,refill值将是最小化代价函数并保持级别高于100的值。

共有2个答案

庄智
2023-03-14

有了上限函数,对于爬山优化器来说,这似乎是一个难题。我认为遗传算法更合适。每周每个房子的交付与否矩阵构成了一个很好的基因组。

library(dplyr)

# Original given sample input data.
df.usage <-  structure(list(reduction.group = c(1, 1, 1, 1, 1, 1, 1, 1, 1, 
                                                1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 
                                                3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 
                                                5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 
                                                7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 
                                                8, 8, 8), week = c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 
                                                                   2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 
                                                                   10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 
                                                                   5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 
                                                                   12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 
                                                                   7, 8, 9, 10, 11, 12), water_usage = c(46, 50, 42, 47, 43, 39, 
                                                                                                         38, 32, 42, 36, 42, 30, 46, 50, 42, 47, 43, 39, 38, 32, 42, 36, 
                                                                                                         42, 30, 46, 50, 43, 47, 43, 39, 38, 32, 42, 36, 42, 30, 46, 50, 
                                                                                                         43, 47, 43, 39, 38, 32, 42, 36, 42, 30, 29, 32, 27, 30, 27, 25, 
                                                                                                         24, 20, 26, 23, 27, 19, 29, 32, 27, 30, 27, 25, 24, 20, 26, 23, 
                                                                                                         27, 19, 29, 32, 27, 30, 28, 25, 25, 21, 27, 23, 27, 19, 29, 32, 
                                                                                                         27, 30, 28, 25, 25, 21, 27, 23, 27, 20), tank.level.start = c(115, 
                                                                                                                                                                       NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 165, NA, NA, NA, 
                                                                                                                                                                       NA, NA, NA, NA, NA, NA, NA, NA, 200, NA, NA, NA, NA, NA, NA, 
                                                                                                                                                                       NA, NA, NA, NA, NA, 215, NA, NA, NA, NA, NA, NA, NA, NA, NA, 
                                                                                                                                                                       NA, NA, 225, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 230, 
                                                                                                                                                                       NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 235, NA, NA, NA, 
                                                                                                                                                                       NA, NA, NA, NA, NA, NA, NA, NA, 240, NA, NA, NA, NA, NA, NA, 
                                                                                                                                                                       NA, NA, NA, NA, NA)), row.names = c(NA, 96L), class = "data.frame")

# Orginal given delivery cost function.
weekly_cost_function <- function(i){
  cost <- (ceiling(sum(i)/200)) * 1600 + (sum(i) * 10)
  cost
}

# Calculate the list of houses (reduction.groups) and number of delivery weeks (weeks).
reduction.groups <- unique(df.usage$reduction.group)
temp             <- df.usage %>% filter(reduction.group == 1)
weeks            <- nrow(temp)

# The genome consists of a matrix representing deliver-or-not to each house each week.
create_random_delivery_schedule <- function(number_of_houses, number_of_weeks, prob = NULL) {
  matrix(sample(c(0, 1), number_of_houses * number_of_weeks, replace = TRUE, prob = prob), number_of_houses)
}

# Generate a population of random genes.
population_size <- 100
schedules <- replicate(population_size, create_random_delivery_schedule(length(reduction.groups), weeks), simplify = FALSE)

# Calculate fitness of an individual.
fitness <- function(schedule) {

  # Fitness is related to delivery cost.
  delivery_cost <- sum(apply(schedule, 2, weekly_cost_function))

  # If the schedule allows a tank level to drop below 100, apply a fitness penalty.
  # Don't make the fitness penalty too large.
  # If the fitness penalty is large enough to be catastrophic (essentially zero children)
  # then solutions that are close to optimal will also be likely to generate children
  # who fall off the catastropy cliff so there will be a selective pressure away from
  # close to optimal solutions.
  # However, if your optimizer generates a lot of infeasible solutions raise the penalty.
  for (i in reduction.groups) {

    temp <- df.usage %>% filter(reduction.group == i)
    temp$level <- temp$tank.level.start

    if (weeks > 1) for (j in 2:weeks) {
      if (1 == schedule[i,j]) {
        temp$level[j] <- 300
      } else {
        temp$level[j] <- ( temp$level[j-1] - temp$water_usage[j] )

        if (100 > temp$level[j]) {
          # Fitness penalty.
          delivery_cost <- delivery_cost + 10 * (100 - temp$level[j])
        }
      }
    }
  }

  # Return one over delivery cost so that lower cost is higher fitness.
  1 / delivery_cost
}

# Generate a new schedule by combining two parents chosen randomly weighted by fitness.
make_baby <- function(population_fitness) {

  # Choose some parents.
  parents <- sample(length(schedules), 2, prob = population_fitness)

  # Get DNA from mommy.
  baby <- schedules[[parents[1]]]

  # Figure out what part of the DNA to get from daddy.
  house_range <- sort(sample(length(reduction.groups), 2))
  week_range  <- sort(sample(weeks, 2))

  # Get DNA from daddy.
  baby[house_range[1]:house_range[2],week_range[1]:week_range[2]] <- schedules[[parents[2]]][house_range[1]:house_range[2],week_range[1]:week_range[2]]

  # Mutate, 1% chance of flipping each bit.
  changes <- create_random_delivery_schedule(length(reduction.groups), weeks, c(0.99, 0.01))
  baby <- apply(xor(baby, changes), c(1, 2), as.integer)
}

lowest_cost <<- Inf

# Loop creating and evaluating generations.
for (ii in 1:100) {
  population_fitness <- lapply(schedules, fitness)
  lowest_cost_this_generation <- 1 / max(unlist(population_fitness))
  print(sprintf("lowest cost = %f", lowest_cost_this_generation))

  if (lowest_cost_this_generation < lowest_cost) {
    lowest_cost <<- lowest_cost_this_generation
    best_baby   <<- schedules[[which.max(unlist(population_fitness))]]
  }

  schedules <<- replicate(population_size, make_baby(population_fitness), simplify = FALSE)
}
阳德润
2023-03-14

上限函数是一个困难的非线性函数(不可微、不连续),应该不惜一切代价避免。然而,它可以很容易地用一般整数变量建模。对于非负变量<代码>x

y = ceiling(x)

作为

x <= y <= x+1
y integer

这是完全线性的,在OMPR(或任何其他LP/MIP工具)中实现起来都很简单。

详细说明。此公式允许模型在假设整数值的特殊情况下选择y=x或y=x 1。如果你想对这种情况挑剔,你可以:

x+0.0001 <= y <= x+1
y integer

我不会为此担心的。

 类似资料:
  •   - r - read : rt_device_ops read_index : rt_ringbuffer read_mirror : rt_ringbuffer reader_queue : rt_pipe_device readers : rt_pipe_device recv_buf : rt_spi_message ref_count : rt_device remaining_tic

  • 这里列出了所有文档化的结构体和联合体的成员变量,并附带结构或联合所属的文件: - r - read : rt_device_ops read_index : rt_ringbuffer read_mirror : rt_ringbuffer reader_queue : rt_pipe_device readers : rt_pipe_device recv_buf : rt_spi_messag

  • 问题内容: 你会怎么做? 或这个 : 最重要的是,我想知道何时在本地变量中存储值更有效,何时进行函数调用更好。 问题答案: 更具可读性更有效。临时表达式和局部变量需要相同的空间,从CPU / JVM的角度看,它们并没有太大区别。JVM将在优化/内嵌方面做得更好。 但是,如果方法调用很昂贵,则将其 缓存 在局部变量中。如果这只是一个简单的方法,无论如何都会内联。你的具体情况也恕我直言,局部变量 是

  • 本文向大家介绍如何基于R数据帧中其他变量的条件创建带有二进制变量的列?,包括了如何基于R数据帧中其他变量的条件创建带有二进制变量的列?的使用技巧和注意事项,需要的朋友参考一下 有时我们需要创建额外的变量以添加有关当前数据的更多信息,因为它可以增加值。这在我们进行特征工程时特别有用。如果我们了解可能影响响应的某些内容,那么我们更愿意将其用作数据中的变量,因此我们将其与已有的数据结合起来。例如,创建另

  • 我需要写一个函数,将二进制分数转换成十进制分数,例如 我所做的:我在R包中搜索相关函数: 我在SOF搜索,发现以下内容: 0.001是: 所以,像内积的总和似乎完成了这个问题,我不知道如何在一般情况下做到这一点。 javascript案例: 如何将二进制分数转换为十进制分数?如何将二进制分数转换为十进制lisp大小写: 将分数从十进制转换为二进制

  • Scala允许您指示可以重复函数的最后一个参数。 这允许客户端将可变长度参数列表传递给函数。 这里,print字符串函数中的args类型(声明为“String *”类型)实际上是Array [String]。 试试下面的程序,这是一个用参数显示函数的简单例子。 例子 (Example) object Demo { def main(args: Array[String]) { p