Previously we have coded the algorithm with nloptr function. A few issues have popped up: some diet do not fall within the constraints. Possible reasons:
overly progressive goal of reducing GHGE by 20% (0.8 times original)
numeric issues that do not treat each constraint equally (e.g. prioritizes energy, ignores GHGE which we care the most)
convergence of the current package
Steps taken so far (2023.08.27)
re-scale the values for different categories, run again
set a moderate reduction of GHGE, see if it works
identify what parameters to adjust
different scaling to prioritize (so far used standard deviation)
use soft rather hard constraints
different values for lower/upper boundary of optimized diet
…
library(data.table)foods <-read.csv('data/foods.csv', sep =',')setDT(foods) # use data.table format# start with 3 foodsfd <- foods[food %in%c('Bread', 'Vegetables', 'Red meat')]fd
We compute the constraints for 3 foods together. Note that the consumption for each of the 28 food groups are different, hence we can not use 3/28 times the total energy, protein…; we need to use a weighted average.
The cosntraints can be set to any positive number that make sense. In the optimization problem, we want to have a range (lower, upper) for the metrics. We can assume that the lower range is 90% of the maximum.
# original constraint for all foods (maximum)const_max_allfoods <-c(9314.3, 98.2, 85.8, 234.7, 39.2, 8.6, 3.8)# 3 foods contribution (maximum)const_max_3foods <-t(as.matrix(current_diet)) %*%as.matrix(contrib_pergram)const_max_3foods
energy protein fat carbs sugar alcohol ghge
[1,] 3099.047 38.6252 22.8452 86.7278 1.1238 0 1.8588
# exclude sugar, alcoholconst_max_3foods <- const_max_3foods[, c('energy', 'protein', 'fat', 'carbs', 'ghge')]# range of target: 0.9-1 of the maximum# this is one thing that we could adjust: hard threshould to softconst_lwrupr <-rbind(const_max_3foods*0.9, const_max_3foods*1)rownames(const_lwrupr) <-c('lwr', 'upr')const_lwrupr <-data.table(const_lwrupr)const_lwrupr
For example, the total energy for the 3 foods together should be within [2789.14, 3099.05].
Standardize food contribution per gram
Since the range of constraints for 5 categories differ hugely, it could affect the numeric evaluation. We want them to be on comparative scales.
The current solution is to standardize the contribution in each category (e.g. energy) by its original value divided by the standard deviation. For example,
the contribution of energy per food becomes (2.25, 0.33, 1.76) rather than (10.69, 1.56, 8.34)
the contribution of ghge per food becomes (0.14, 0.14, 1.87) rather than (0.001, 0.001, 0.013)
Similarly, the upper and lower limit of the constraints also need to be re-scaled. After rescaling, the target will be on a range of hundreds, rather than 3000 vs 1.8.
# find sd for each category: energy, protein.. ghge# divide by these coefcontrib_pergram <- contrib_pergram[, c('energy', 'protein', 'fat', 'carbs', 'ghge')]contrib_pergram
const_lwrupr_std <-sweep(const_lwrupr, MARGIN =2, 1/sd_coef, FUN ='*')const_lwrupr_std
energy protein fat carbs ghge
1 588.3423 439.9282 293.0966 329.5946 241.4652
2 653.7137 488.8091 325.6629 366.2162 268.2947
In the future, different scaling factor can be applied; but it should be a positive number after scaling. Could try dividing the difference between max and min of this variable.
Run optimization
Now we are going to solve the optimization problem using nloptr. The details of the algorithm please refer to part 3.
The objective function remains the same for 3 foods. We want to minimize the difference between the current and optimized diet.
We optimize using 5 categories (10 inequality constraints):
energy
protein
fat
carbs
ghge
We show two examples: one reduces ghge to 90% what the current diet produces; the other reduces ghge to 85%.
Example 1: reduce ghge to 90%
First we reduce the GHGE to 90%. (This is feasible, in contrast to the 80% reduction)
cstr <-copy(const_lwrupr_std) # this line is important: it keeps a copy of the original valuescstr$ghge <- cstr$ghge *0.9# reduce to 0.9cstr
energy protein fat carbs ghge
1 588.3423 439.9282 293.0966 329.5946 217.3187
2 653.7137 488.8091 325.6629 366.2162 241.4652
# use the standardized food contribution per gram # contrib_pergram_std# define the inequality constraintsinequalconstr <-function (x) { cps <- contrib_pergram_std # call the values# cstr is used as the last item in each equation constr <-c(# energy- x[1]*cps$energy[1] - x[2]*cps$energy[2] - x[3]*cps$energy[3] + cstr$energy[1], # lower x[1]*cps$energy[1] + x[2]*cps$energy[2] + x[3]*cps$energy[3] - cstr$energy[2], # upper# protein- x[1]*cps$protein[1] - x[2]*cps$protein[2] - x[3]*cps$protein[3] + cstr$protein[1], x[1]*cps$protein[1] + x[2]*cps$protein[2] + x[3]*cps$protein[3] - cstr$protein[2],# fat- x[1]*cps$fat[1] - x[2]*cps$fat[2] - x[3]*cps$fat[3]+ cstr$fat[1], x[1]*cps$fat[1] + x[2]*cps$fat[2] + x[3]*cps$fat[3] - cstr$fat[2],# carbs- x[1]*cps$carbs[1] - x[2]*cps$carbs[2] - x[3]*cps$carbs[3]+ cstr$carbs[1], x[1]*cps$carbs[1] + x[2]*cps$carbs[2] + x[3]*cps$carbs[3] - cstr$carbs[2],# ghge- x[1]*cps$ghge[1] - x[2]*cps$ghge[2] - x[3]*cps$ghge[3]+ cstr$ghge[1], x[1]*cps$ghge[1] + x[2]*cps$ghge[2] + x[3]*cps$ghge[3] - cstr$ghge[2] )return (constr)}
Set other parameters (this part is unchanged, apart from the number of constraints)
# lower and upper bounds of x (3 foods)lb <-c(160, 140, 100)ub <-c(180, 160, 120)# Initial values# (try different ones!)x0 <-c(175, 150, 110)opts <-list( "algorithm"="NLOPT_GN_ISRES","xtol_rel"=1.0e-15,"maxeval"=160000,"tol_constraints_ineq"=rep( 1.0e-10, 10 )) # match number of const# run the algorithmres <- nloptr::nloptr(x0 = x0, # initial value for xeval_f = objective, # objective functionlb = lb, # lower bound for xub = ub, # upper bound for xeval_g_ineq = inequalconstr, # inequality constraintopts = opts # options)print(res)
Call:
nloptr::nloptr(x0 = x0, eval_f = objective, lb = lb, ub = ub,
eval_g_ineq = inequalconstr, opts = opts)
Minimization using NLopt version 2.7.1
NLopt solver status: 5 ( NLOPT_MAXEVAL_REACHED: Optimization stopped because
maxeval (above) was reached. )
Number of Iterations....: 160000
Termination conditions: xtol_rel: 1e-15 maxeval: 160000
Number of inequality constraints: 10
Number of equality constraints: 0
Current value of objective function: 202.054848606764
Current value of controls: 174.3086 153.5102 103.4693
Now we print out the results in a more readable way
res_diet <- res$solution# print with the current diet, and change percentage# also print the boundary, in case it hit boundarydiet_result <-data.frame(name =c('Bread', 'Vegetables', 'Red meat'),current = current_diet, new = res_diet, percent_change =round((res_diet - current_diet)/current_diet, 3),lower_limit = lb, upper_limit = ub)diet_result
name current new percent_change lower_limit upper_limit
1 Bread 175.4 174.3086 -0.006 160 180
2 Vegetables 154.6 153.5102 -0.007 140 160
3 Red meat 117.6 103.4693 -0.120 100 120
You should always check how much it actually deviates from the target constraints.
Example 2: reduce ghge to 85%
This is one example where the optimal constraints can not be reached. You’ll see that the boundaries that we set in the algorithm have been reached; suggesting that if we modify these parameters, the solution could still be found.
Be careful with HOW MUCH it deviates from the target!
cstr <-copy(const_lwrupr_std) # this line is important: it keeps a copy of the original valuescstr$ghge <- cstr$ghge *0.85# reduce to 0.85cstr
energy protein fat carbs ghge
1 588.3423 439.9282 293.0966 329.5946 205.2454
2 653.7137 488.8091 325.6629 366.2162 228.0505
# use the standardized food contribution per gram # contrib_pergram_std# define the inequality constraintsinequalconstr <-function (x) { cps <- contrib_pergram_std # call the values# cstr is used as the last item in each equation constr <-c(# energy- x[1]*cps$energy[1] - x[2]*cps$energy[2] - x[3]*cps$energy[3] + cstr$energy[1], # lower x[1]*cps$energy[1] + x[2]*cps$energy[2] + x[3]*cps$energy[3] - cstr$energy[2], # upper# protein- x[1]*cps$protein[1] - x[2]*cps$protein[2] - x[3]*cps$protein[3] + cstr$protein[1], x[1]*cps$protein[1] + x[2]*cps$protein[2] + x[3]*cps$protein[3] - cstr$protein[2],# fat- x[1]*cps$fat[1] - x[2]*cps$fat[2] - x[3]*cps$fat[3]+ cstr$fat[1], x[1]*cps$fat[1] + x[2]*cps$fat[2] + x[3]*cps$fat[3] - cstr$fat[2],# carbs- x[1]*cps$carbs[1] - x[2]*cps$carbs[2] - x[3]*cps$carbs[3]+ cstr$carbs[1], x[1]*cps$carbs[1] + x[2]*cps$carbs[2] + x[3]*cps$carbs[3] - cstr$carbs[2],# ghge- x[1]*cps$ghge[1] - x[2]*cps$ghge[2] - x[3]*cps$ghge[3]+ cstr$ghge[1], x[1]*cps$ghge[1] + x[2]*cps$ghge[2] + x[3]*cps$ghge[3] - cstr$ghge[2] )return (constr)}# lower and upper bounds of x (3 foods)lb <-c(160, 140, 100)ub <-c(180, 160, 120)# Initial values# (try different ones!)x0 <-c(175, 150, 110)opts <-list( "algorithm"="NLOPT_GN_ISRES","xtol_rel"=1.0e-15,"maxeval"=160000,"tol_constraints_ineq"=rep( 1.0e-10, 10 )) # match number of const# run the algorithmres <- nloptr::nloptr(x0 = x0, # initial value for xeval_f = objective, # objective functionlb = lb, # lower bound for xub = ub, # upper bound for xeval_g_ineq = inequalconstr, # inequality constraintopts = opts # options)print(res)
Call:
nloptr::nloptr(x0 = x0, eval_f = objective, lb = lb, ub = ub,
eval_g_ineq = inequalconstr, opts = opts)
Minimization using NLopt version 2.7.1
NLopt solver status: 5 ( NLOPT_MAXEVAL_REACHED: Optimization stopped because
maxeval (above) was reached. )
Number of Iterations....: 160000
Termination conditions: xtol_rel: 1e-15 maxeval: 160000
Number of inequality constraints: 10
Number of equality constraints: 0
Current value of objective function: 528.456412600945
Current value of controls: 177.753 140 100
Now we check the results for the second problem.
res_diet <- res$solution# print with the current diet, and change percentage# also print the boundary, in case it hit boundarydiet_result <-data.frame(name =c('Bread', 'Vegetables', 'Red meat'),current = current_diet, new = res_diet, percent_change =round((res_diet - current_diet)/current_diet, 3),lower_limit = lb, upper_limit = ub)diet_result
name current new percent_change lower_limit upper_limit
1 Bread 175.4 177.753 0.013 160 180
2 Vegetables 154.6 140.000 -0.094 140 160
3 Red meat 117.6 100.000 -0.150 100 120