Search code examples
rmathematical-optimizationlinear-programmingompr

How to force minimum number of groups in linear programming problem using ompr


The following problem seeks to maximize the weight across any 3 items while being under a cost of 20. I gave group "a" a large weight so that the model will only select the 3 items from group "a". How do I force the model to include at least 2 groups?

library(ompr)
library(ROI.plugin.glpk)
library(ompr.roi)
library(dplyr)

set.seed(1)
d <- data.frame(
  id = seq_len(10),
  weight = c(rep(100, 3), runif(7, 1, 50)),
  cost = runif(10, 1, 10),
  group = c(rep("a", 3), rep("b", 3), rep("c", 3), "d")
)

m <- ompr::MIPModel() %>%
  
  ompr::add_variable(x[i],
                     i = d$id,
                     type = "binary") %>%
  
  # set objective to maximize the weight
  ompr::set_objective(
    ompr::sum_over(d$weight[i] * x[i],
                   i = d$id), "max"
  ) %>%
  
  # cost must be less than 20
  ompr::add_constraint(
    ompr::sum_over(d$cost[i] * x[i],
                   i = d$id) <= 20
  ) %>% 
  
  # can only include 3 items
  ompr::add_constraint(
    ompr::sum_over(
      x[i],
      i = d$id
    ) == 3
  )

res <- ompr::solve_model(m, ompr.roi::with_ROI(solver = "glpk"))

res %>%
  ompr::get_solution(x[i]) %>% 
  dplyr::filter(.data$value > 0) %>% 
  dplyr::inner_join(d, by = c("i" = "id"))
#>   variable i value weight     cost group
#> 1        x 1     1    100 6.947180     a
#> 2        x 2     1    100 6.662026     a
#> 3        x 3     1    100 1.556076     a

Created on 2023-02-05 with reprex v2.0.2


Solution

  • Here is one way to do it:

    library(ompr)
    library(ROI.plugin.glpk)
    library(ompr.roi)
    library(dplyr)
    
    set.seed(1)
    d <- data.frame(
      id = seq_len(10),
      weight = c(rep(100, 3), runif(7, 1, 50)),
      cost = runif(10, 1, 10),
      group = c(rep("a", 3), rep("b", 3), rep("c", 3), "d")
    )
    
    m <- ompr::MIPModel() %>%
      
      ompr::add_variable(x[i],
                         i = d$id,
                         type = "binary") %>%
      
      ompr::add_variable(group[j],
                         j = unique(d$group),
                         type = "binary") %>%
      
      # set objective to maximize the weight
      ompr::set_objective(
        ompr::sum_over(d$weight[i] * x[i],
                       i = d$id, j = unique(d$group)), "max"
      ) %>%
      
      # cost must be less than 20
      ompr::add_constraint(
        ompr::sum_over(d$cost[i] * x[i],
                       i = d$id) <= 20
      ) %>% 
      
      # can only include 3 items
      ompr::add_constraint(
        ompr::sum_over(
          x[i],
          i = d$id
        ) == 3
      ) %>% 
      
      # force group binary variables to be 1 if item is in group
      ompr::add_constraint(
        ompr::sum_over(
          x[i],
          i = d$id[which(d$group == j)]
        ) - 10000 * group[j] <= 0,
        j = unique(d$group)
      ) %>% 
      
      # force at least one binary variable for item inclusion to be 1 across all items in group
      # if group binary is 1
      ompr::add_constraint(
        group[j] - 10000 * ompr::sum_over(
          x[i],
          i = d$id[which(d$group == j)]
        )  <= 0,
        j = unique(d$group)
      ) %>%
      
      # force at least two groups
      ompr::add_constraint(
        ompr::sum_over(
          group[j],
          j = unique(d$group)
        ) >= 2
      )
    
    res <- ompr::solve_model(m, ompr.roi::with_ROI(solver = "glpk"))
    
    res %>%
      ompr::get_solution(x[i]) %>% 
      dplyr::filter(.data$value > 0) %>% 
      dplyr::inner_join(d, by = c("i" = "id"))
    #>   variable  i value    weight     cost group
    #> 1        x  1     1 100.00000 6.947180     a
    #> 2        x  3     1 100.00000 1.556076     a
    #> 3        x 10     1  47.28909 7.458567     d
    

    Created on 2023-02-05 with reprex v2.0.2

    The key is to link group[j] variables to the decision variables, x[i], by setting constraints such that if any x[i] inside group[j] is 1 then group[j] is 1. And when group[j] is one, then at least one x[i] in that group must also be 1. Then it's straightforward to set another constraint that the sum of group[j] is greater than or equal to 2.