How to create upper bound on many variables w/ lpsolve in R?

835 views Asked by At

I have a dataset with 135 foods that I am using to solve the diet problem: minimizing cost and maximizing nutritional value. I would like to create a model that includes a diversity of foods, rather than one that, for example, tells to me only to eat 80 servings of potatoes and 50 servings of spinach each week. I would like to either:

1) Set an upper bound on the number of servings of foods (i.e. maximum of 10 servings of each food), without changing my upper and lower bounds for other variables (such as food groups)

2) Be able to specify the minimum number of foods(/variables) I want in my model

Right now, I am writing out all the variables in the model, in addition to specifying mins and maxs for fiber, calories, oz. fruits, oz. vegs, etc.:

minCost <- lp("min", SNAP$costPerServ,
           rbind(SNAP$protPerServ, SNAP$protPerServ, SNAP$fatPerServ,
 SNAP$fatPerServ, SNAP$costPerServ, SNAP$costPerServ, SNAP$sodiumPerServ,
 SNAP$sodiumPerServ, SNAP$fiberPerServ, SNAP$fiberPerServ, SNAP$sugarPerServ,
 SNAP$sugarPerServ, SNAP$calsPerServ, SNAP$calsPerServ, SNAP$fruit,     SNAP$vegs,
 SNAP$grains, SNAP$grains, SNAP$meatProtein, SNAP$dairy, SNAP$X1, SNAP$X2,
 SNAP$X3, SNAP$X4, SNAP$X5, SNAP$X6, SNAP$X7, SNAP$X8, SNAP$X9, ... [more foods
 here] ..., SNAP$X135),
           c(">=", "<=", ">=", "<=", ">=", "<=", ">=", "<=", ">=", "<=", ">=",
 "<=", ">=", "<=", ">=", ">=", ">=", "<=", ">=", ">=",
 "<=", "<=", "<=", "<=", "<=", "<=", "<=", "<=", "<=",
 "<=", ...[more "<="s here]..., "<="),
           c(input$prot[1]*7, input$prot[2]*7, input$fat[1]*7, input$fat[2]*7,
 input$budget[1], input$budget[2], input$sodium[1]*7, input$sodium[2]*7,
 input$fiber[1]*7, input$fiber[2]*7, input$sugar[1]*7, input$sugar[2]*7,
 input$cals[1]*7, input$cals[2]*7, 16, 28, 9, 25, 6.4, 24, input$serv,
 input$serv, input$serv, input$serv, input$serv, input$serv, input$serv,
 input$serv, input$serv, input$serv, ...[more input$servs here]...,
 input$serv))

I used the shiny package for this, so that's why it's "input$serv" rather than a concrete number. The user can choose what the maximum number of servings is using a slider widget, and the default is 10.

The foods' nutritional information that the model is based off of is in a separate csv file.

glimpse(SNAP)
Observations: 135
Variables:
$ food (fctr) Coca-Cola, Sacramento Tomato Juice, Tropicana Trop50 Orange Juice, V8 Veg...
$ foodGroup (fctr) Beverage, Beverage, Beverage, Beverage, Dairy, Dairy, Dairy, Dairy, Dairy...
$ calsPerServ (dbl) 140.0, 35.0, 50.0, 50.0, 90.0, 90.0, 102.4, 150.0, 90.0, 90.0, 113.0, 50.0...
$ ozPerServ (dbl) 12.000000, 6.000000, 8.000000, 8.000000, 2.500000, 4.070000, 8.000000, 8.0...
$ fatPerServ (dbl) 0.00, 0.00, 0.00, 0.00, 5.00, 1.00, 0.24, 8.00, 0.00, 0.00, 9.00, 3.00, 7....
$ protPerServ (dbl) 0.0, 1.0, 1.0, 2.0, 8.0, 16.0, 7.2, 8.0, 6.0, 3.0, 7.0, 4.0, 2.0, 2.0, 6.0...
$ sodiumPerServ (dbl) 45.00, 560.00, 10.00, 590.00, 80.00, 360.00, 120.80, 120.00, 100.00, 60.00...
$ fiberPerServ (dbl) 0.0, 1.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,...
$ sugarPerServ (dbl) 39.00, 4.90, 10.00, 8.00, 0.00, 3.00, 11.20, 11.00, 12.00, 14.00, 0.00, 1....
$ costPerServ (dbl) 0.4800000, 0.2400000, 0.5600000, 0.4737500, 0.1750000, 0.4884000, 0.240000...
$ grains (dbl) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
$ oilsFats (dbl) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
$ fruit (dbl) 0.00000, 0.00000, 0.00000, 0.00000, 0.00000, 0.00000, 0.00000, 0.00000, 0....
$ sugar (dbl) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
$ meatProtein (dbl) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
$ bev (int) 12, 6, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0...
$ vegs (dbl) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
$ dairy (dbl) 0.000000, 0.000000, 0.000000, 0.000000, 2.500000, 4.070000, 8.000000, 8.00...
$ X1 (int) 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
$ X2 (int) 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
$ X3 (int) 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...

1

There are 1 answers

0
Michael Tiemann On BEST ANSWER

Well, without the source file for the SNAP data, I came up with my own food/nutrition matrix. It should be easy to see how to adapt to your problem. This is a very basic simplex linear optimization problem. We define each food item in terms of cost, min and max servings per capita, and then nutritional content (presently limited to Vitamin A and kCals). With 2 nutritional components there are 5 columns. With 42 nutritional components, there would be 45 columns.

A second matrix, min and max Recommended Daily Allowances(RDA), has a row for each min/max RDA.

Here's the R code for four foods, corn, milk, bread, and soylent:

library(lpSolveAPI)

# Diet options (index, cost, min servings, max servings, vitA, calories)
food.corn <- c(0.18, 0, 10, 107, 72)
food.milk <- c(0.23, 0, 10, 500, 121)
food.bread <- c(0.05, 0, 10, 0, 65)
food.soylent <- c(0.50, 0, 10, 625, 250)

foods <- matrix(c(food.corn, food.milk, food.bread, food.soylent), 4, 5, byrow=TRUE)

# Recommended Daily Allowance (min, max)
rda.vitA <- c(5000, 50000)
rda.cal <- c(2000, 2250)

rdas <- matrix(c(rda.vitA, rda.cal), 2, 2, byrow=TRUE)

nr <- length(foods[,1])
varcount <- nr
const.count <- 0
lp <- make.lp (0, varcount, verbose="normal")
lp.control (lp, sense="min")
set.objfn (lp, foods[,1])

for (i in length(rdas[,1])) {
    add.constraint (lp, foods[,(3+i)], ">=", as.double(rdas[i,1]))
    add.constraint (lp, foods[,(3+i)], "<=", as.double(rdas[i,2]))
    const.count <- const.count + 1
}

for (i in nr) {
    add.constraint (lp, c(rep(0,i-1), 1, rep(0,nr-i)), ">=", as.double(foods[i,2]))
    add.constraint (lp, c(rep(0,i-1), 1, rep(0,nr-i)), "<=", as.double(foods[i,3]))
    const.count <- const.count + 1
}

set.bounds (lp, lower=foods[,2], upper=foods[,3])
set.type (lp, 1:varcount, type="integer")
resize.lp (lp, const.count, varcount)

if (solve (lp) == 0) {
    lps.out <- list(solution=get.variables(lp), objective=get.objective(lp))
} else {
    print ("No feasible solution")
    lps.out <- NA
}

Season to taste...