0

Problem set: I have 3 different groups that need to be assigned to 6 different locations.

Objective: I need to minimize the unused space in each location.

Constraints:

  1. For each location j, the sum of individuals assigned to that location from all groups should be less than or equal to the location capacity of j. (e.g. Each location has a capacity which cannot be exceeded.)

  2. The total number of Group A, Group B, and Group C assigned to each location should not exceed the corresponding total numbers available. (e.g. Each group has a size that cannot be exceeded.)

  3. The groups cannot mix. For example, Group A cannot be in the same camp as Group B or Group C, Group B cannot be in the same location as Group A or Group C, and so on.

The issue I am having is with the third constraint.

Below is the R script that works for the first two constraints:

library(lpSolve)

# Define the data
n_groups <- 3
n_locations <- 6
n_group_a <- 130
n_group_b <- 40
n_group_c <- 120
l_capacities <- c(60, 50, 40, 30, 20, 10)

# Define the decision variables
vars <- matrix(0, nrow = n_groups * n_locations, ncol = 1)

for (i in 1:n_groups) {
  for (j in 1:n_locations) {
    vars[(i-1)*n_locations+j] <- 1
  } 
}

# Maximize the used spaces
obj <- rep(1, n_locations*n_groups)

# Define the constraints
rhs <- c(l_capacities, n_group_a, n_group_b, n_group_c)
dir <- c(rep("<=", n_locations + n_groups))

con <- matrix(0, nrow = n_locations + n_groups, ncol = n_groups * n_locations)

# Constraint 1: For each location j, the sum of individuals assigned to that location from all groups should be less than or equal to the location capacity of j.
for (j in 1:n_locations) {
  for (i in 1:n_groups) {
    con[j, (i-1)*n_locations+j] <- 1
  }
}

# Constraint 2: The total number of Group A, Group B, and Group C assigned to each location should not exceed the corresponding total numbers available.
for (j in 1:n_groups) {
  con[n_locations+j, seq((j-1)*n_locations+1, j*n_locations)] <- c(rep(1, n_locations))#, rep(0, 5 * n_groups))
  # con[n_locations+j, seq((j-1)*n_locations+1, j*n_locations)] <- c(rep(1, n_locations))#, rep(0, 5 * n_groups))
  # con[n_locations+n_groups+j, seq((j-1)*n_groups+1, j*n_groups)] <- c(rep(1, n_groups)) #c(rep(0, n_groups), rep(1, n_groups), rep(0, n_groups))
  # con[n_locations+2*n_groups+j, seq((j-1)*n_groups+1, j*n_groups)] <- c(rep(0, 2 * n_groups), rep(1, n_groups))
}

# Solve the problem
result <- lp(direction = "max", objective.in = obj, const.mat = con, const.dir = dir, const.rhs = rhs)

# Print the optimal assignment
assignment <- matrix(result$solution[1:(n_groups*n_locations)], nrow = n_locations)
rownames(assignment) <- paste0("Location ", 1:n_locations)
colnames(assignment) <- c("Group A", "Group B", "Group C")
print(assignment)

I am having difficulty building the third constraint. Below is the code I have been playing with but would like some help adjusting (currently gives me an "subscript out of bounds" error):

# Constraint 3: Each individual can only be assigned to one location.
#  for (i in 1:n_groups) {
#   con[n_locations+2*n_groups+i, seq((i-1)*n_locations+1, i*n_locations)] <- rep(1, n_locations)
# }

Any help appreciated. Thanks!

  • When you say "Group A cannot be in the same camp as Group B or Group C", by camp are you meaning location? In your example, Group A & Group B have a 20 in Location 3 and constraint three is where you're attempting to limit this to 0? – On_an_island May 09 '23 at 13:09
  • correction: not limit it to 0 but you're wanting solutions where only one group (ex, Group A) can use up the capacity for a given location (ex, Location 3). – On_an_island May 09 '23 at 13:33
  • @On_an_island - you are correct; camp is the same as location and that only one group can use the space in a given location (Group A or Group B can be in Location 3 but not both). Thanks for the assist! – user21861449 May 09 '23 at 13:47

3 Answers3

0

for the 3rd constraint you may need binary variables indexed by the group g & location l with z[g,l] =1 if x[g,l] is a positive number of members grom g at location l. So you can have bounds like
z[g,l] <= x[g,l] <= Gz[g,l] where G is total number of members of group g
and
sum(z[g,l] over g) <= 1 for l in locations

Sutanu
  • 193
  • 6
  • can you take a look at my response and see if the constraint matrix makes sense. I'm getting a weird solution that more than likely has to do with how I coded the matrix than the problem itself. – On_an_island May 12 '23 at 13:14
0

So I attempted to give this a go but I keep getting a solution that don't seem correct. Because I can't put this as a comment to Sutanu, I'm adding it as an answer and will edit or delete when necessary. It's not clear to me if that's because I coded the constraint 3 incorrectly (it's more than likely the case) or if it has something to do with the problem itself.

library(lpSolve)
library(tidyverse)

## same inputs but in a df
n_groups <- 3
n_locations <- 6
n_group_a <- 130
n_group_b <- 40
n_group_c <- 120
l_capacities <- c(60, 50, 40, 30, 20, 10)

lp_df <- data.frame(group = rep(letters[1:3], each = 3),
                    locations = rep(1:n_locations, 3),
                    obj = 1) %>%
  dplyr::arrange(group, locations) %>%
  dplyr::mutate_all(., ~as.factor(.))

## make the constraint matrix
cols <- c("locations","group")
con <- cbind(
  model.matrix(object = ~ .,
               data = lp_df[,cols],
               contrasts.arg = lapply(subset(lp_df[,cols], select = c(locations,group) ), contrasts, contrasts=FALSE)
  )
)[,-1]

## mat 2 will contain constraint 3 (only one group may use up the locations capacity)
mat2 <- rbind(matrix(data = 0, nrow = nrow(con), n_locations), con[,1:6])
con <- rbind(con, matrix(data = 0, nrow = nrow(mat2)-nrow(con), ncol = ncol(con)))
con <- cbind(con,mat2)

## naming the constraints in the con matrix for better readibility
con <- as.data.frame.matrix(con)
rownames(con)[1:18] <- paste0("group_",lp_df$group,"_loc_",lp_df$locations)
rownames(con)[19:nrow(con)] <- paste0("group_",lp_df$group,"_loc_",lp_df$locations,"_max")
colnames(con)[10:ncol(con)] <- paste0(colnames(con)[10:ncol(con)],"_max")
con <- t(con)

obj <- lp_df$obj

## constraint 1 and 2 rhs/lhs
rhs <- c(l_capacities, n_group_a, n_group_b, n_group_c)
dir <- c(rep("<=", n_locations + n_groups))

## add constraint 3 rhs/lhs
dir <- c(dir, rep('<=', times = n_locations))
rhs <- c(rhs, rep(1, times = n_locations))

result <- lp(direction = "max", objective.in = obj, const.mat = con, const.dir = dir, const.rhs = rhs)

## Print the optimal assignment
assignment <- matrix(result$solution[1:(n_groups*n_locations)], nrow = n_locations)
rownames(assignment) <- paste0("Location ", 1:n_locations)
colnames(assignment) <- c("Group A", "Group B", "Group C")
print(assignment)

However, this gives the following solution:

                                   Group A Group B Group C
Location 1 1000000000000000019924668064446       0       0
Location 2                               0       0       0
Location 3                               0       0       0
Location 4                               0       0       0
Location 5                               0       0       0
Location 6                               0       0       0

The constraint matrix looks like this:

                  locations1 locations2 locations3 locations4 locations5 locations6 groupa groupb groupc locations1_max locations2_max locations3_max locations4_max locations5_max locations6_max
group_a_loc_1              1          0          0          0          0          0      1      0      0              0              0              0              0              0              0
group_a_loc_2              0          1          0          0          0          0      1      0      0              0              0              0              0              0              0
group_a_loc_3              0          0          1          0          0          0      1      0      0              0              0              0              0              0              0
group_a_loc_4              0          0          0          1          0          0      1      0      0              0              0              0              0              0              0
group_a_loc_5              0          0          0          0          1          0      1      0      0              0              0              0              0              0              0
group_a_loc_6              0          0          0          0          0          1      1      0      0              0              0              0              0              0              0
group_b_loc_1              1          0          0          0          0          0      0      1      0              0              0              0              0              0              0
group_b_loc_2              0          1          0          0          0          0      0      1      0              0              0              0              0              0              0
group_b_loc_3              0          0          1          0          0          0      0      1      0              0              0              0              0              0              0
group_b_loc_4              0          0          0          1          0          0      0      1      0              0              0              0              0              0              0
group_b_loc_5              0          0          0          0          1          0      0      1      0              0              0              0              0              0              0
group_b_loc_6              0          0          0          0          0          1      0      1      0              0              0              0              0              0              0
group_c_loc_1              1          0          0          0          0          0      0      0      1              0              0              0              0              0              0
group_c_loc_2              0          1          0          0          0          0      0      0      1              0              0              0              0              0              0
group_c_loc_3              0          0          1          0          0          0      0      0      1              0              0              0              0              0              0
group_c_loc_4              0          0          0          1          0          0      0      0      1              0              0              0              0              0              0
group_c_loc_5              0          0          0          0          1          0      0      0      1              0              0              0              0              0              0
group_c_loc_6              0          0          0          0          0          1      0      0      1              0              0              0              0              0              0
group_a_loc_1_max          0          0          0          0          0          0      0      0      0              1              0              0              0              0              0
group_a_loc_2_max          0          0          0          0          0          0      0      0      0              0              1              0              0              0              0
group_a_loc_3_max          0          0          0          0          0          0      0      0      0              0              0              1              0              0              0
group_a_loc_4_max          0          0          0          0          0          0      0      0      0              0              0              0              1              0              0
group_a_loc_5_max          0          0          0          0          0          0      0      0      0              0              0              0              0              1              0
group_a_loc_6_max          0          0          0          0          0          0      0      0      0              0              0              0              0              0              1
group_b_loc_1_max          0          0          0          0          0          0      0      0      0              1              0              0              0              0              0
group_b_loc_2_max          0          0          0          0          0          0      0      0      0              0              1              0              0              0              0
group_b_loc_3_max          0          0          0          0          0          0      0      0      0              0              0              1              0              0              0
group_b_loc_4_max          0          0          0          0          0          0      0      0      0              0              0              0              1              0              0
group_b_loc_5_max          0          0          0          0          0          0      0      0      0              0              0              0              0              1              0
group_b_loc_6_max          0          0          0          0          0          0      0      0      0              0              0              0              0              0              1
group_c_loc_1_max          0          0          0          0          0          0      0      0      0              1              0              0              0              0              0
group_c_loc_2_max          0          0          0          0          0          0      0      0      0              0              1              0              0              0              0
group_c_loc_3_max          0          0          0          0          0          0      0      0      0              0              0              1              0              0              0
group_c_loc_4_max          0          0          0          0          0          0      0      0      0              0              0              0              1              0              0
group_c_loc_5_max          0          0          0          0          0          0      0      0      0              0              0              0              0              1              0
group_c_loc_6_max          0          0          0          0          0          0      0      0      0              0              0              0              0              0              1
const.dir.num              1          1          1          1          1          1      1      1      1              1              1              1              1              1              1
const.rhs                 60         50         40         30         20         10    130     40    120              1              1              1              1              1              1

Columns locations1_max to locations6_max are constraint 3 and are set to <= (const.dir.num) to 1 to limit the number of groups per location to no more than 1.

On_an_island
  • 387
  • 3
  • 16
0

So after some rewriting of the script, the answer I was looking for is below. I point out where changes were made from the original and the addition of two new constraints. I realize that the script does not show if there is any unused space at a given location nor if there is any whole or part of a group that is unassigned to a given location (for example, all of Group B is never assigned to a location). I plan on adding those parts to the script later. Appreciate the comments and hope this is helpful to someone.

`library(lpSolve)

## Define the data
n_groups <- 3
n_locations <- 6
n_group_a <- 130
n_group_b <- 40
n_group_c <- 120
l_capacities <- c(60, 50, 40, 30, 20, 10)

# **CHANGED** REMOVED Define the decision variables
# vars <- matrix(0, nrow = n_groups * n_locations, ncol = 1)
# 
# for (i in 1:n_groups) {
#   for (j in 1:n_locations) {
#     vars[(i-1)*n_locations+j] <- 1
#   } 
# }

# Maximize the used spaces
obj <- rep(1, n_locations*n_groups)
# **NEW** Add in additional space in objective function
obj <- c(obj, rep(0,n_groups*n_locations))

# **CHANGED** Define the constraints
#rhs <- c(l_capacities, n_group_a, n_group_b, n_group_c)
rhs <- c(l_capacities, n_group_a, n_group_b, n_group_c, rep(0,n_groups*n_locations), rep(1,n_locations))

# **CHANGED** Direction of the constraints which includes Constraint 3 & 4
#dir <- c(rep("<=", n_locations + n_groups))
dir <- c(rep("<",n_groups),rep("<",n_locations),rep("<",n_groups*n_locations), rep("=",n_locations))

## **CHANGED** Establish the constraint Mmatrix shell
#con <- matrix(0, nrow = n_locations + n_groups, ncol = n_groups * n_locations)
con <- matrix(0,nrow = n_locations+n_groups+n_groups*n_locations+n_locations, ncol=length(obj))

# Constraint 1: For each location j, the sum of individuals assigned to that location from all groups should be less than or equal to the location capacity of j.
for (j in 1:n_locations) {
  for (i in 1:n_groups) {
    con[j, (i-1)*n_locations+j] <- 1
  }
}

# **CHANGED** Constraint 2: The total number of Group A, Group B, and Group C assigned to each location should not exceed the corresponding total numbers available.
# for (j in 1:n_groups) {
#   con[n_locations+j, seq((j-1)*n_locations+1, j*n_locations)] <- c(rep(1, n_locations))#, rep(0, 5 * n_groups))
# }
for (i in 1:n_groups) {
  for (j in 1:n_locations) {
    con[n_locations+i, (i-1)*n_locations+j] <- 1
  }
}

##  Constraint 3: Build out the binary matrix and add in the U entries on the second half of the matrix
for (i in 1:(n_groups*n_locations)) {
  con[n_locations+n_groups+i, i] <- 1
}
for (i in 1:(n_groups*n_locations)) {
  con[n_locations+n_groups+i, (n_groups*n_locations)+i] <- U
}

## Constraint 4: Add in the population location constraint i.e. only one group per location.
for (j in 1:n_locations) {
  for (i in 1:n_groups) {
    con[n_locations*n_groups+n_groups+n_locations+j, (i-1)*n_locations+j+n_locations*n_groups] <- 1
  }
} 

# **CHANGED** Solve the problem
#result <- lp(direction = "max", objective.in = obj, const.mat = con, const.dir = dir, const.rhs = rhs)
result <-  lp("max", obj, con, dir, rhs, int.vec=1:(n_groups*n_locations), binary.vec=(n_groups*n_locations+1):length(obj), compute.sens = 0)

# Print the optimal assignment
assignment <- matrix(result$solution[1:(n_groups*n_locations)], nrow = n_locations)
rownames(assignment) <- paste0("Location ", 1:n_locations)
colnames(assignment) <- c("Group A", "Group B", "Group C")
print(assignment)`