营养搭配-线性规划

问题描述

假设我们每人每天需要摄入一定的维生素,每种食物维生素的含量不一样,而且每种食物摄入有一定的限制,每种食物的价格均不相同,我们如何决策,能够满足每天的维生素需求,并且最小化购买食物所花费的金额。这个优化问题也可以使用数学规划的方法来建模和求解。

业务调研、数据量化、数学建模

在使用优化技术的时候,需要更详细的调研业务的需求,整理相关的业务逻辑和数据,并量化表示它。然后采用数学规划的方法进行数学建模。

此部分细节较多,可在案例营养搭配中查阅细节,此处我们仅列出数学公式如下,参数部分请点击案例链接查看:

image.svg

其中j代表每种食物,i代表食物中不同的营养种类,c代表获取食物的代价,x代表每天对食物的摄入量,n为对营养需求的上下限。

对应以上的约束有:

  • 摄入的营养介于营养需求的上下限之间

源代码

MindOpt支持多种编程语言或者建模语言来调用。此处仅列出一种供参考:

MindOpt APL建模语言调用

MindOpt APL建模语言的源代码(可在营养搭配上试运行):

##====MindOpt APL 建模语言版本代码====

# diet.mapl

set NUTR := { "A", "B1", "B2", "C" };
set FOOD := {"BEEF", "CHK", "FISH", "HAM", "MCH", "MTL", "SPG", "TUR"} ;
set F:= {"cost", "f_min", "f_max"};
set N:= {"n_min", "n_max"};

param data1[FOOD * F] := 
        | "cost"  , "f_min" , "f_max" |
|"BEEF" |  3.19   ,  0      ,  100    |
|"CHK"  |  2.59   ,  0      ,  100    |
|"FISH" |  2.29   ,  0      ,  100    |
|"HAM"  |  2.89   ,  0      ,  100    |
|"MCH"  |  1.89   ,  0      ,  100    |
|"MTL"  |  1.99   ,  0      ,  100    |
|"SPG"  |  1.99   ,  0      ,  100    |
|"TUR"  |  2.49   ,  0      ,  100    |;

param data2[NUTR * N] :=
      | "n_min", "n_max"|
|"A"  |  700,     10000 |
|"C"  |  700,     10000 |
|"B1" |  700,     10000 |
|"B2" |  700,     10000 |;

param amt[FOOD * NUTR] :=
        | "A",  "C",  "B1",  "B2"|
|"BEEF" |  60,   20,   10,    15 |
|"CHK"  |  8,    0,    20,    20 |
|"FISH" |  8,    10,   15,    10 |
|"HAM"  |  0,    40,   35,    10 |
|"MCH"  |  15,   35,   0,     15 |
|"MTL"  |  70,   30,   15,    0  |
|"SPG"  |  0,    50,   25,    15 |
|"TUR"  |  60,   0,    15,    0  |; 

var x[j in FOOD] >= data1[j, "f_min"] <= data1[j, "f_max"];

minimize Total_Cost:  sum {j in FOOD} : data1[j, "cost"] * x[j];

subto Diet: forall {i in NUTR} 
   data2[i, "n_min"] <= sum {j in FOOD}: amt[j, i] * x[j] <= data2[i, "n_max"];

#------------------------------


print "-----------------用MindOpt求解---------------";
option solver mindopt;     # (可选)指定求解用的求解器,默认是MindOpt
solve;         # 求解

print "-----------------Display---------------";
display;        # 展示结果

print "最低价格是:";
print  sum {<j> in FOOD} data1[j, "cost"] * x[j];

结果和结果用法

不同代码日志打印不一样。部分结果日志打印如下所示:

...
Model summary.
 - Num. variables     : 8
 - Num. constraints   : 4
 - Num. nonzeros      : 25
 - Bound range        : [1.0e+02,1.0e+04]
 - Objective range    : [1.9e+00,3.2e+00]
 - Matrix range       : [8.0e+00,7.0e+01]
...
Simplex method terminated. Time : 0.007s


OPTIMAL; objective 101.01
...
-----------------结果---------------
最低价格是:
101.0110471806674

目标的最优解是:101。更多解的细节,如决策变量的取值、验证约束是否正确,可以通过print命令打印来显示,变量的取值也可以从程序运行写的.sol文件里面获取,还可以调用display获取所有变量的。

如运行如下指令,验证“摄入的营养是没有超过营养需求的上限”这个约束:

forall {i in NUTR } 
 print '维生素{}摄入量为{},摄入上限UB为{}'%i,sum {j in FOOD}: amt[j, i] * x[j],data2[i, 'n_max'] , 
 sum {j in FOOD}: amt[j, i] * x[j]>= data2[i, 'n_max'];

结果如下:

维生素A摄入量为700,摄入上限UB为10000
维生素B1摄入量为699.9999999999999,摄入上限UB为10000
维生素B2摄入量为700,摄入上限UB为10000
维生素C摄入量为700,摄入上限UB为10000

运行如下代码,将输出打印为csv格式,作为营养搭配问题的解决方案:

print "{}, {},{}" % "食物","购买食物的成本", "食物摄入量" : "Results.csv";
close "Results.csv";

forall {<j> in FOOD}
    print "{}, {},{}" % j,data1[j, "cost"] * x[j], x[j]   >> "Results.csv";

close "Results.csv";

运行结果如下:

image.png

即,建议吃的食物是鸡肉(CHK), 芝士通心粉(MCH), 肉饼(MTL), 意大利面 (SPG),满足约束需求后购买食物的最低价格为101。