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

具有二进制调度变量的R:成本函数优化?,r,optimization,linear-programming,nonlinear-optimization,lpsolve,R,Optimization,Linear Programming,Nonlinear Optimization,Lpsolve,下面详细介绍了一个优化问题的简化版本,我很难解决这个问题 目标是使通过卡车运送水的组织的成本函数最小化,并使用该等式生成最小化成本的卡车运送计划 该组织全年向约10000个家用水箱供水 储罐的最大容量为300加仑,最低要求限值为100加仑——也就是说,储罐在低于100加仑之前应加满300加仑 例如,如果第2周的油箱容量为115加仑,第3周预计使用20加仑,则需要在第3周重新加注 成本包括: 每次送货费10美元 每周卡车的费用。一辆卡车每周的费用是1000美元。因此,如果在一周内交付200次,成本

下面详细介绍了一个优化问题的简化版本,我很难解决这个问题

目标是使通过卡车运送水的组织的成本函数最小化,并使用该等式生成最小化成本的卡车运送计划

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

储罐的最大容量为300加仑,最低要求限值为100加仑——也就是说,储罐在低于100加仑之前应加满300加仑

例如,如果第2周的油箱容量为115加仑,第3周预计使用20加仑,则需要在第3周重新加注

成本包括:

  • 每次送货费10美元

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

  • 不同家庭和不同周的用水情况不同。夏季是用水高峰。如果我们盲目地遵循规则,在达到100加仑的最低限值之前再加油,那么,如果在夏季的“路肩”上进行配送,那么卡车的峰值数量很可能会高于所需数量

    我已经为每个家庭创建了每周用水量的估算。此外,我还将家庭分组,以减少优化问题的规模(将约10000户家庭减少到8组)

    重申目标:该优化器的输出应该是:在一年中的52周内,为每个家庭群体交付或不交付

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

    成本函数示例

    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
    

    重要的是,
    重新填充
    值将是使成本函数最小化并使
    水平保持在100以上的任何值。

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

    y = ceiling(x)
    
    作为


    我不担心这一点。

    对于爬山优化器来说,使用天花板功能似乎是一个难题。我认为遗传算法更合适。每个家庭每周是否交付的矩阵构成了一个很好的基因组

    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)
    }
    
    库(dplyr)
    #原始给定样本输入数据。
    
    用法我看到很多问题,其中大多数是逻辑问题。首先写下数学模型。只有当你对数学模型的正确性有信心时才开始编码。我只是简单地回顾了这个答案,但它看起来很神奇。我现在就给悬赏金,悬赏金即将到期,如果我以后有问题,我会回来的!谢谢你,鲍勃。
    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
    
    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"))
    
    
     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
    
    y = ceiling(x)
    
    x <= y <= x+1
    y integer
    
    x+0.0001 <= y <= x+1
    y integer
    
    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)
    }