文章目录
不知大家小时候有没有和我一样的经历,爸妈说我挑食,这不吃那不吃,这个有营养多吃点,哪个吃多了不好…,后来我们长大了,才发现,他们不是不挑食,而且他们买的都是他们喜欢吃的。当然也有跟着爷爷奶奶过日子的时候,爷爷奶奶平时就比较节俭,讲究一个便宜。我们能不能用科学的方法来设定菜单,达到少花钱又营养呢?
本篇将介绍使用MindOpt来优化营养调配问题。(本例数据是假设的,决策值不能作为参考,仅为讲解决策算法用。)
营养调配问题的的目标是利用优化模型来设定每日饮食菜单,在满足各类营养的需求同时更能优化总成本.营养调配问题是可用线性优化来表达
- 决策 变量 为:以下食物调配多少量:起司汉堡 (Cheeseburger)、汉堡 (Hamburger)、火腿三明治 (Ham-sandwich)、鱼肉三民治 (Fish-sandwich)、鸡肉三民治 (Chicken-sandwich)、薯条 (Fries)、香肠比司吉 (Sausage biscuit)、低脂牛乳 (Low-fat milk)、和橙汁 (Orange juice);
- 约束 条件为:卡路里 (Cal.)、碳水化合物 (Carbo.)、蛋白质 (Portien)、维生素A/D (Vit. A/D)、铁 (Iron)和钙质 (Calc.)的每日摄取上/下限制,以及总量 (Volume) 限制;
- 目标 函数则为:总成本的最小化.
问题定义
问题类型
线性优化问题,我们用先用集合和参数标明后面变量的取值关联信息。
集合
- J : = { Cheeseburger, HamSandwich, Hamburger, FishSandwich, J := \{ \text{Cheeseburger, HamSandwich, Hamburger, FishSandwich, } J:={Cheeseburger, HamSandwich, Hamburger, FishSandwich,
ChickenSandwich, Fries, SausageBiscuit, LowfatMilk, OrangeJuice } \text{ChickenSandwich, Fries, SausageBiscuit, LowfatMilk, OrangeJuice} \} ChickenSandwich, Fries, SausageBiscuit, LowfatMilk, OrangeJuice}
- I : = { Cal , Carbo , Protein , VitA , VitC , Calc , Iron , Volume } . I := \left \{ \text{Cal}, \text{Carbo}, \text{Protein}, \text{VitA}, \text{VitC}, \text{Calc}, \text{Iron}, \text{Volume} \right \}. I:={Cal,Carbo,Protein,VitA,VitC,Calc,Iron,Volume}.
参数
- c j ∀ j ∈ J : 食物 j 的单位成本 . c_j~\forall j \in J: \text{食物}~j~\text{的单位成本}. cj ∀j∈J:食物 j 的单位成本.
- a i j ∀ i ∈ I , ∀ j ∈ J : 每单位的食物 j 中所富含的营养 i 总量 . a_{ij}~\forall i \in I, \forall j \in J: \text{每单位的食物}~j~\text{中所富含的营养}~i~\text{总量}. aij ∀i∈I,∀j∈J:每单位的食物 j 中所富含的营养 i 总量.
- l i and u i , ∀ i ∈ I : 营养 i 的单日摄取上限和下限 . l_i~\text{and}~u_i, \forall i \in I: \text{营养 } i~\text{的单日摄取上限和下限}. li and ui,∀i∈I:营养 i 的单日摄取上限和下限.
- v j ∀ j ∈ J : 食物 j 的容积 . v_j~\forall j \in J: \text{食物}~j~\text{的容积}. vj ∀j∈J:食物 j 的容积.
- v : 所有食物的容积总量上界 . v: \text{所有食物的容积总量上界}. v:所有食物的容积总量上界.
然后这个线性规划问题定义如下:
决策变量
- x j ∀ j ∈ J : 食物 j 所摄取的单位量 . x_j~\forall j \in J: \text{食物}~j~\text{ 所摄取的单位量}. xj ∀j∈J:食物 j 所摄取的单位量.
目标函数
- min j ∈ J c j x j : 总成本最小化 . \min_{j \in J} c_j x_j: \text{总成本最小化}. minj∈Jcjxj:总成本最小化.
约束条件
- l i ≤ ∑ j ∈ J a i j x j ≤ u i , ∀ i ∈ I : 营养 i 的摄取上下界 . l_i \leq \sum_{j \in J} a_{ij} x_j \leq u_i, \forall i \in I: \text{营养 } i \text{ 的摄取上下界}. li≤∑j∈Jaijxj≤ui,∀i∈I:营养 i 的摄取上下界.
- ∑ j ∈ J v j x j ≤ v : 食物 j 摄取总量的上界 . \sum_{j \in J} v_{j} x_j \leq v: \text{食物 }j \text{ 摄取总量的上界}. ∑j∈Jvjxj≤v:食物 j 摄取总量的上界.
- x j ≥ 0 , ∀ j ∈ J : 食物 j 的攝取下界 (不可為負數) . x_j \geq 0, \forall j \in J: \text{食物}~j~\text{的攝取下界 (不可為負數)}. xj≥0,∀j∈J:食物 j 的攝取下界 (不可為負數).
总结下这个模型的数学公式为:
数据
使用MindOpt求解器的API
直接采用求解器的API,需要查阅API文档来理解API的意思,没有建模语言可读性高。请参阅https://solver.damo.alibaba.com/doc/html/API%20reference/API-python/index.html来查看PythonAPI的使用说明。
关于Python的例子,在达摩院 MindOpt优化中文社区中有Python的示例。这里是个LP的问题,我们可以参考:https://developer.aliyun.com/article/1090860?spm=a2c6h.26396819.creator-center.8.14fb3e182y9iZl
下面我们分三种方式描述在本平台环境中的运行方法:
方法1:cell中直接输入代码运行
示例:进入官网>进入线上平台>创建项目>输入代码
请运行下面cell中的代码,点击本窗口上面的播放△运行,或者摁shift+enter键运行:
# LP_2_diet.py
"""
/**
* example_2_py1.py
* Description
* -----------
*
* Linear optimization (diet problem).
*
* The goal is to select foods that satisfy daily nutritional requirements while minimizing the total cost.
* The constraints in this problem limit the number of calories, the volume of good consumed, and the amount of
* vitamins, protein, carbohydrates, calcium, and iron in the diet.
*
* Note
* ----
*
* The model below will be inputted in a row-wise order.
*
* Formulation
* -----------
*
* Minimize
* Obj: 1.840000000 Cheeseburger + 2.190000000 HamSandwich + 1.840000000 Hamburger + 1.440000000 FishSandwich +
* 2.290000000 ChickenSandwich + 0.770000000 Fries + 1.290000000 SausageBiscuit + 0.600000000 LowfatMilk +
* 0.720000000 OrangeJuice
* Subject To
* Cal: 510 Cheeseburger + 370 HamSandwich + 500 Hamburger + 370 FishSandwich +
* 400 ChickenSandwich + 220 Fries + 345 SausageBiscuit + 110 LowfatMilk + 80 OrangeJuice >= 2000
* Carbo: 34 Cheeseburger + 35 HamSandwich + 42 Hamburger + 38 FishSandwich + 42 ChickenSandwich +
* 26 Fries + 27 SausageBiscuit + 12 LowfatMilk + 20 OrangeJuice <= 375
* Carbo_low: 34 Cheeseburger + 35 HamSandwich + 42 Hamburger + 38 FishSandwich + 42 ChickenSandwich +
* 26 Fries + 27 SausageBiscuit + 12 LowfatMilk + 20 OrangeJuice >= 350
* Protein: 28 Cheeseburger + 24 HamSandwich + 25 Hamburger + 14 FishSandwich + 31 ChickenSandwich +
* 3 Fries + 15 SausageBiscuit + 9 LowfatMilk + OrangeJuice >= 55
* VitA: 15 Cheeseburger + 15 HamSandwich + 6 Hamburger + 2 FishSandwich + 8 ChickenSandwich +
* 4 SausageBiscuit + 10 LowfatMilk + 2 OrangeJuice >= 100
* VitC: 6 Cheeseburger + 10 HamSandwich + 2 Hamburger + 15 ChickenSandwich +
* 15 Fries + 4 LowfatMilk + 120 OrangeJuice >= 100
* Calc: 30 Cheeseburger + 20 HamSandwich + 25 Hamburger + 15 FishSandwich +
* 15 ChickenSandwich + 20 SausageBiscuit + 30 LowfatMilk + 2 OrangeJuice >= 100
* Iron: 20 Cheeseburger + 20 HamSandwich + 20 Hamburger + 10 FishSandwich +
* 8 ChickenSandwich + 2 Fries + 15 SausageBiscuit + 2 OrangeJuice >= 100
* Volume: 4 Cheeseburger + 7.500000000 HamSandwich + 3.500000000 Hamburger + 5 FishSandwich +
* 7.300000000 ChickenSandwich + 2.600000000 Fries + 4.100000000 SausageBiscuit + 8 LowfatMilk + 12 OrangeJuice <= 75
* Bounds
* End
*/
"""
from mindoptpy import *
if __name__ == "__main__":
MDO_INFINITY = MdoModel.get_infinity()
req = \
{
# requirement: ( lower bound, upper bound)
"Cal" : ( 2000, MDO_INFINITY),
"Carbo" : ( 350, 375),
"Protein" : ( 55, MDO_INFINITY),
"VitA" : ( 100, MDO_INFINITY),
"VitC" : ( 100, MDO_INFINITY),
"Calc" : ( 100, MDO_INFINITY),
"Iron" : ( 100, MDO_INFINITY),
"Volume" : (-MDO_INFINITY, 75)
}
food = \
{
# food : ( lower bound, upper bound, cost)
"Cheeseburger" : ( 0, MDO_INFINITY, 1.84),
"HamSandwich" : ( 0, MDO_INFINITY, 2.19),
"Hamburger" : ( 0, MDO_INFINITY, 1.84),
"FishSandwich" : ( 0, MDO_INFINITY, 1.44),
"ChickenSandwich" : ( 0, MDO_INFINITY, 2.29),
"Fries" : ( 0, MDO_INFINITY, 0.77),
"SausageBiscuit" : ( 0, MDO_INFINITY, 1.29),
"LowfatMilk" : ( 0, MDO_INFINITY, 0.60),
"OrangeJuice" : ( 0, MDO_INFINITY, 0.72)
}
req_value = \
{
# (requirement, food ) : value
( "Cal", "Cheeseburger" ) : 510,
( "Cal", "HamSandwich" ) : 370,
( "Cal", "Hamburger" ) : 500,
( "Cal", "FishSandwich" ) : 370,
( "Cal", "ChickenSandwich" ) : 400,
( "Cal", "Fries" ) : 220,
( "Cal", "SausageBiscuit" ) : 345,
( "Cal", "LowfatMilk" ) : 110,
( "Cal", "OrangeJuice" ) : 80,
( "Carbo", "Cheeseburger" ) : 34,
( "Carbo", "HamSandwich" ) : 35,
( "Carbo", "Hamburger" ) : 42,
( "Carbo", "FishSandwich" ) : 38,
( "Carbo", "ChickenSandwich" ) : 42,
( "Carbo", "Fries" ) : 26,
( "Carbo", "SausageBiscuit" ) : 27,
( "Carbo", "LowfatMilk" ) : 12,
( "Carbo", "OrangeJuice" ) : 20,
( "Protein", "Cheeseburger" ) : 28,
( "Protein", "HamSandwich" ) : 24,
( "Protein", "Hamburger" ) : 25,
( "Protein", "FishSandwich" ) : 14,
( "Protein", "ChickenSandwich" ) : 31,
( "Protein", "Fries" ) : 3,
( "Protein", "SausageBiscuit" ) : 15,
( "Protein", "LowfatMilk" ) : 9,
( "Protein", "OrangeJuice" ) : 1,
( "VitA", "Cheeseburger" ) : 15,
( "VitA", "HamSandwich" ) : 15,
( "VitA", "Hamburger" ) : 6,
( "VitA", "FishSandwich" ) : 2,
( "VitA", "ChickenSandwich" ) : 8,
( "VitA", "Fries" ) : 0,
( "VitA", "SausageBiscuit" ) : 4,
( "VitA", "LowfatMilk" ) : 10,
( "VitA", "OrangeJuice" ) : 2,
( "VitC", "Cheeseburger" ) : 6,
( "VitC", "HamSandwich" ) : 10,
( "VitC", "Hamburger" ) : 2,
( "VitC", "FishSandwich" ) : 0,
( "VitC", "ChickenSandwich" ) : 15,
( "VitC", "Fries" ) : 15,
( "VitC", "SausageBiscuit" ) : 0,
( "VitC", "OrangeJuice" ) : 4,
( "VitC", "LowfatMilk" ) : 120,
( "Calc", "Cheeseburger" ) : 30,
( "Calc", "HamSandwich" ) : 20,
( "Calc", "Hamburger" ) : 25,
( "Calc", "FishSandwich" ) : 15,
( "Calc", "ChickenSandwich" ) : 15,
( "Calc", "Fries" ) : 0,
( "Calc", "SausageBiscuit" ) : 20,
( "Calc", "LowfatMilk" ) : 30,
( "Calc", "OrangeJuice" ) : 2,
( "Iron", "Cheeseburger" ) : 20,
( "Iron", "HamSandwich" ) : 20,
( "Iron", "Hamburger" ) : 20,
( "Iron", "FishSandwich" ) : 10,
( "Iron", "ChickenSandwich" ) : 8,
( "Iron", "Fries" ) : 2,
( "Iron", "SausageBiscuit" ) : 15,
( "Iron", "LowfatMilk" ) : 0,
( "Iron", "OrangeJuice" ) : 2,
( "Volume", "Cheeseburger" ) : 4,
( "Volume", "HamSandwich" ) : 7.5,
( "Volume", "Hamburger" ) : 3.5,
( "Volume", "FishSandwich" ) : 5,
( "Volume", "ChickenSandwich" ) : 7.3,
( "Volume", "Fries" ) : 2.6,
( "Volume", "SausageBiscuit" ) : 4.1,
( "Volume", "LowfatMilk" ) : 8,
( "Volume", "OrangeJuice" ) : 12
}
# ----Step 1. Create a model and change the parameters.
model = MdoModel()
try:
# ----Step 2. Input model.
# Change to minimization problem.
model.set_int_attr("MinSense", 1)
# Add variables.
var = {}
for food_name, food_data in food.items():
var[food_name] = model.add_var(food_data[0], food_data[1], food_data[2], None, food_name, False)
# Add constraints.
cons = {}
for req_name, req_data in req.items():
expr = MdoExprLinear()
for food_name in food.keys():
expr += req_value[req_name, food_name] * var[food_name]
cons[req_name] = model.add_cons(req_data[0], req_data[1], expr, req_name)
# ----Step 3. Solve the problem and populate the result.
model.solve_prob()
model.display_results()
time.sleep(1) #for print
status_code, status_msg = model.get_status()
if status_msg == "OPTIMAL":
print("----\n")
print("The solver terminated with an OPTIMAL status (code {0}).".format(status_code))
print("目标函数总收益是: {0}".format(model.get_real_attr("PrimalObjVal")))
print("原始解是:")
for var_name,var_val in var.items():
primal_soln = var_val.get_real_attr("PrimalSoln")
print("{0:>20} : {1}".format(var_name,primal_soln))
else:
print("Optimizer terminated with a(n) {0} status (code {1}).".format(status_msg, status_code))
except MdoError as e:
print("Received Mindopt exception.")
print(" - Code : {}".format(e.code))
print(" - Reason : {}".format(e.message))
except Exception as e:
print("Received exception.")
print(" - Reason : {}".format(e))
finally:
# Step 4. Free the model.
model.free_mdl()
点击运行后,得到的结果如下:
Start license validation (current time : 17-JAN-2023 23:54:14).
License validation terminated. Time : 0.003s
Model summary.
- Num. variables : 9
- Num. constraints : 8
- Num. nonzeros : 67
- Bound range : [5.5e+01,2.0e+03]
- Objective range : [6.0e-01,2.3e+00]
- Matrix range : [1.0e+00,5.1e+02]
Presolver started.
Presolver terminated. Time : 0.000s
Simplex method started.
Model fingerprint: =IGYvFmb5d2dgF2dud3b
Iteration Objective Dual Inf. Primal Inf. Time
0 0.00000e+00 0.0000e+00 4.0937e+01 0.00s
3 1.48557e+01 0.0000e+00 0.0000e+00 0.00s
Postsolver started.
Simplex method terminated. Time : 0.001s
Optimizer summary.
- Optimizer used : Simplex method
- Optimizer status : OPTIMAL
- Total time : 0.002s
Solution summary. Primal solution
- Objective : 1.4855737705e+01
----
The solver terminated with an OPTIMAL status (code 1).
目标函数总收益是: 14.855737704918033
原始解是:
Cheeseburger : 4.385245901639344
HamSandwich : 0.0
Hamburger : 0.0
FishSandwich : 0.0
ChickenSandwich : 0.0
Fries : 6.147540983606558
SausageBiscuit : 0.0
LowfatMilk : 3.422131147540985
OrangeJuice : 0.0
方法2:命令行直接运行.py文件
上面是直接在cell中运行所有的脚本,我们也可以建立个新文档,将Python代码存在src/python_src
文件夹的LP_2_diet.py文件。然后在Launcher中打开Terminal,执行python xx.py
文件来运行。
您也可以下载本.py文件,在自己的电脑上安装MindOpt求解器,然后在自己电脑的环境运行。
Luancher可以点击左上角的+打方块打开,Terminal在最下方,如截图示意。打开的窗口可以拖动调整位置。
然后在Terminal命令行里运行如下指令:
cd src/python_src
python LP_2_diet.py
运行得到的结果同方法1:
方法3:复制案例广场的例子
这个例子已经在MindOpt的案例广场,可以直接复制:营养调配
直接点击复制项目,会将配置的文件全部复制过来,可以直接运行。(如上图所示)
求解结果
运行结果里面打印有:
目标函数总收益是: 14.855737704918033
原始解是:
Cheeseburger : 4.385245901639344
HamSandwich : 0.0
Hamburger : 0.0
FishSandwich : 0.0
ChickenSandwich : 0.0
Fries : 6.147540983606558
SausageBiscuit : 0.0
LowfatMilk : 3.422131147540985
OrangeJuice : 0.0
代表求解结果的目标函数(每日饮食餐费)最低解为:$14.86元,包含了4.39份起司汉堡,6.15份薯条,以及3.42份低脂牛乳。
联系我们
钉钉:damodi
邮箱地址:solver.damo@list.alibaba-inc.com