解决经典的饮食模型
diet
#解决经典饮食模型,展示如何为现有模型添加约束
import gurobipy as gp
from gurobipy import GRB
import dietmodel
categories , minNutrition , maxNutrition = gp.multidict ({
'calories ': [1800 , 2200] ,
'protein ': [91 , GRB.INFINITY ] ,#INFINITY为无穷
'fat ': [0 , 65] ,
'sodium ': [0 , 1779]})
foods , cost = gp.multidict ({
'hamburger ': 2.49 ,
'chicken ': 2.89 ,
'hot dog ': 1.50 ,
'fries ': 1.89 ,
'macaroni ': 2.09 ,
'pizza ': 1.99 ,
'salad ': 2.49 ,
'milk ': 0.89 ,
'ice cream ': 1.59})
#Nutrition values for the foods
nutritionValues = {
('hamburger ', 'calories '): 410 ,
('hamburger ', 'protein '): 24 ,
('hamburger ', 'fat '): 26 ,
('hamburger ', 'sodium '): 730 ,
('chicken ', 'calories '): 420 ,
('chicken ', 'protein '): 32 ,
('chicken ', 'fat '): 10 ,
('chicken ', 'sodium '): 1190 ,
('hot dog ', 'calories '): 560 ,
('hot dog ', 'protein '): 20 ,
('hot dog ', 'fat '): 32 ,
('hot dog ', 'sodium '): 1800 ,
('fries ', 'calories '): 380 ,
('fries ', 'protein '): 4 ,
('fries ', 'fat '): 19 ,
('fries ', 'sodium '): 270 ,
('macaroni ', 'calories '): 320 ,
('macaroni ', 'protein '): 12 ,
('macaroni ', 'fat '): 10 ,
('macaroni ', 'sodium '): 930 ,
('pizza ', 'calories '): 320 ,
('pizza ', 'protein '): 15 ,
('pizza ', 'fat '): 12 ,
('pizza ', 'sodium '): 820 ,
('salad ', 'calories '): 320 ,
('salad ', 'protein '): 31 ,
('salad ', 'fat '): 12 ,
('salad ', 'sodium '): 1230 ,
('milk ', 'calories '): 100 ,
('milk ', 'protein '): 8 ,
('milk ', 'fat '): 2.5 ,
('milk ', 'sodium '): 125 ,
('ice cream ', 'calories '): 330 ,
('ice cream ', 'protein '): 8 ,
('ice cream ', 'fat '): 10 ,
('ice cream ', 'sodium '): 180}
# Model
m = gp . Model (" diet ")
# Create decision variables for the foods to buy 为购买的食物创建决策变量
buy = m . addVars ( foods , name ="buy")
# You could use Python looping constructs and m. addVar () to create these decision variables instead . The following would be equivalent(等价的)
# buy = {}
# for f in foods :
# buy[f] = m.addVar ( name =f)
# The objective is to minimize the costs prod函数计算乘积
m.setObjective ( buy.prod ( cost ) , GRB.MINIMIZE )
# Using looping constructs , the preceding statement would be:使用循环结构,上面的语句将会是:
# m. setObjective (sum(buy[f]* cost [f] for f in foods ) , GRB. MINIMIZE )
# Nutrition constraints营养约束
m . addConstrs (( gp.quicksum ( nutritionValues [f , c ] * buy [ f ] for f in foods )
== [ minNutrition [ c ] , maxNutrition [ c ]]
for c in categories ) , "_")
# Using looping constructs , the preceding statement would be:
# for c in categories :
# m. addRange (sum( nutritionValues [f, c] * buy[f] for f in foods ) ,
# minNutrition [c] , maxNutrition [c] , c)
def printSolution ():
if m.status == GRB.OPTIMAL :
print ('\ nCost : %g' % m.ObjVal )#获取目标函数值
print ('\ nBuy:')
for f in foods:
if buy [ f ].X > 0.0001:
print ('%s %g' % (f , buy [ f ].X ))
else:
print ('No solution ')
# Solve
m.optimize ()
printSolution ()
print ('\ nAdding constraint : at most 6 servings of dairy ')
m.addConstr ( buy.sum ([ 'milk ', 'ice cream ']) <= 6 , " limit_dairy ")
# Solve 前面又增加了一个约束条件,所以要再次有下面的代码
m.optimize ()
printSolution ()
diet思路
问题一
内部函数不能直接调用,只可直接调用外部函数,dietmodel.solve ( categories , minNutrition , maxNutrition ,foods , cost , nutritionValues )可以在diet2调用,而dietmodel.printSolution()不可以
问题二
m.optimize导致的
问题三
优化完成后,可查询属性值,可查询X变量属性,以获得每个变量的解决方案值,0.0001是为了控制精度
diet2
import dietmodel
import gurobipy as gp
from gurobipy import GRB
categories , minNutrition , maxNutrition = gp.multidict ({
'calories ': [1800 , 2200] ,
'protein ': [91 , GRB.INFINITY ] ,
'fat ': [0 , 65] ,
'sodium ': [0 , 1779]})
foods , cost = gp.multidict ({
'hamburger ': 2.49 ,
'chicken ': 2.89 ,
'hot dog ': 1.50 ,
'fries ': 1.89 ,
'macaroni ': 2.09 ,
'pizza ': 1.99 ,
'salad ': 2.49 ,
'milk ': 0.89 ,
'ice cream ': 1.59})
# Nutrition values for the foods
nutritionValues = {
('hamburger ', 'calories '): 410 ,
('hamburger ', 'protein '): 24 ,
('hamburger ', 'fat '): 26 ,
('hamburger ', 'sodium '): 730 ,
('chicken ', 'calories '): 420 ,
('chicken ', 'protein '): 32 ,
('chicken ', 'fat '): 10 ,
('chicken ', 'sodium '): 1190 ,
('hot dog ', 'calories '): 560 ,
('hot dog ', 'protein '): 20 ,
('hot dog ', 'fat '): 32 ,
('hot dog ', 'sodium '): 1800 ,
('fries ', 'calories '): 380 ,
('fries ', 'protein '): 4 ,
('fries ', 'fat '): 19 ,
('fries ', 'sodium '): 270 ,
('macaroni ', 'calories '): 320 ,
('macaroni ', 'protein '): 12 ,
('macaroni ', 'fat '): 10 ,
('macaroni ', 'sodium '): 930 ,
('pizza ', 'calories '): 320 ,
('pizza ', 'protein '): 15 ,
('pizza ', 'fat '): 12 ,
('pizza ', 'sodium '): 820 ,
('salad ', 'calories '): 320 ,
('salad ', 'protein '): 31 ,
('salad ', 'fat '): 12 ,
('salad ', 'sodium '): 1230 ,
('milk ', 'calories '): 100 ,
('milk ', 'protein '): 8 ,
('milk ', 'fat '): 2.5 ,
('milk ', 'sodium '): 125 ,
('ice cream ', 'calories '): 330 ,
('ice cream ', 'protein '): 8 ,
('ice cream ', 'fat '): 10 ,
('ice cream ', 'sodium '): 180}
dietmodel.solve ( categories , minNutrition , maxNutrition ,
foods , cost , nutritionValues )
diet2思路
dietmodel
一个制定和求解模型的函数,但它不包括模型数据,数据由调用程序调用。
# Solve the classic diet model. This file implements a function that formulates and solves the model,
# but it contains no model data. The data is
# passed in by the calling program. Run example 'diet2.py',
# 'diet3.py', or 'diet4.py' to invoke this function.
#解决经典饮食模式,这个文件实现了一个制定和求解模型的函数,但它不包含模型数据,数据由调用程序传入。运行示例'diet2.py'、'diet3.py'、'diet4.py'来调用这个函数
import gurobipy as gp
from gurobipy import GRB
def solve(categories, minNutrition, maxNutrition, foods, cost,
nutritionValues):
# Model
m = gp.Model("diet")
# Create decision variables for the foods to buy
buy = m.addVars(foods, name="buy")
# The objective is to minimize the costs
m.setObjective(buy.prod(cost), GRB.MINIMIZE)
# Nutrition constraints
m.addConstrs((gp.quicksum(nutritionValues[f, c] * buy[f] for f in foods) ==
[minNutrition[c], maxNutrition[c]] for c in categories), "_")
def printSolution():
if m.status == GRB.OPTIMAL:
print('\nCost: %g' % m.ObjVal)
print('\nBuy:')
for f in foods:
if buy[f].X > 0.0001:
print('%s %g' % (f, buy[f].X))
else:
print('No solution')
# Solve
m.optimize()
printSolution()
print('\nAdding constraint: at most 6 servings of dairy')
m.addConstr(buy.sum(['milk', 'ice cream']) <= 6, "limit_dairy")
# Solve
m.optimize()
printSolution()
dietmodel思路:
做到花最少的钱去买食物使营养约束在指定范围内
一些知识储备:
multidict函数
允许在一条语句中初始化一个或多个字典,该函数以字典为参数,其中与每个键相关联的值是一个长度为n的列表。该函数将这些列表拆分成单个条目,创建n个独立的字典。函数返回一个列表。该列表第一项是共享键值列表,然后是n个单独的字典。
import gurobipy as gp
from gurobipy import GRB
names, lower, upper = gp.multidict({ 'x': [0, 1], 'y': [1, 2], 'z': [0, 3] })
print(names)
['x', 'y', 'z']
print(lower)
{'x':0, 'y':1, 'z':0}
print(upper)
{'x':1, 'y':2, 'z':3}
列表理解和生成器表达式
是重要的Python特性,让以简洁的方式进行隐式枚举。
列表理解
建立一个包含1到5的数学平方的列表
gurobi> [x*x for x in [1, 2, 3, 4, 5]]
[1, 4, 9, 16, 25]
可先用列表理解来构建列表,然后将列表传递给sum。不过生成器表达式更简单、更高效。
生成器表达式
gurobi> sum(x*x for x in [1, 2, 3, 4, 5])
只要方法接收Interable参数(可以被遍历的东西),就可以使用生成器表达式。例如,大多数接受list参数(最常见的Iterable类型)的Python方法也会接受生成器表达式。
生成器表达式在我们的Python示例中被广泛使用,主要是在使用addConstrs方法一次创建多个约束时。
注意:有一个Python例程可以创建连续的整数列表:range。上面的代码通常可写成:
gurobi> sum(x*x for x in range(1,6))
列表理解和生成器表达式都可以包含多个for子句和一个或多个if子句
[(x,y) for x in range(4) for y in range(4) if x < y]
[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
构建了一个包含所有x,y对的元组列表,其中x和y均小于4,且x小于y。
元组列表类(tupledict)
tuplelist对象上的select方法来检索与特定字段中的一个或多个指定值相匹配的所有图元
‘*’表示可以接受任何可以取到的值
gurobi> print(l.select(1, '*'))
2个元组(前一项为1的只有2个),每个元组2个值(列)
(1,2)
(1,3)
gurobi> print(l.select('*', 3))
2个元组(第二项为3的只有2个),每个元组2个值(两列)
( 1 , 3 )
( 2 , 3 )
gurobi> print(l.select('*', [2, 4]))
2个元组(第二项为2或4的只有两项),每个元组2个值
( 1 , 2 )
( 2 , 4 )
gurobi> print(l.select('*', '*'))
4个元组()第一二项都可以接受任何可以取到的值,每个元组2个值
( 1 , 2 )
( 1 , 3 )
( 2 , 3 )
( 2 , 4 )
使用列表理解也可以获得类似的结构。例如:
gurobi> print(l.select(1, '*'))
2个元组(前一项为1的只有2个),每个元组2个值(列)
(1,2)
(1,3)
gurobi>l=tuplelist([(1,2),(1,3),(2,3),(2,4)])
print([(x,y) for x,y in l if x==1])
问题在于,后一条语句会考虑列表l中的每个成员,这对大型列表来说效率很低。select方法建立了内部数据结构,使这些选择变得相当高效。
元组类(tupledict)
用tupledict对象上的sum和prod方法构建线性表达式
轻松简洁
gurobi构建线性表达式,变量的系数为1时使用sum方法,变量的系数不为1时使用prod方法(变量和系数相乘后累加)。
使用sum和prod方法的代码如下:
import gurobipy as grb
model = grb.Model()
# 定义变量的下标
tl = [(1, 1), (1, 2), (1, 3),
(2, 1), (2, 2), (2, 3),
(3, 1), (3, 2), (3, 3)]
vars = model.addVars(tl, name = "x")
model.update()
# 创建一个系数矩阵,用tuplelist格式存储,键(key)和vars一样
c1 = [(1, 1), (1, 2), (1, 3)]
coeff = grb.tupledict(c1)
coeff[(1, 1)] = 1
coeff[(1, 2)] = 0.3
coeff[(1, 3)] = 0.4
# 变量系数是1的时候用sum方法
print(vars.sum(1, '*'))
# 输出
# <gurobi.LinExpr: x[1,1] + x[1,2] + x[1,3]>
# prod方法:变量系数不是1,变量和系数相乘后累加
print(vars.prod(coeff, 1, '*'))
# 输出
# <gurobi.LinExpr: x[1,1] + 0.3 x[1,2] + 0.4 x[1,3]>
addVars方法为输入参数中的每个元组添加一个Gurobi决策变量到模型中,并以tupledict的形式返回结果,将创建变量d(1,2)、d(1,3)、d(2,3)、d(2,4)。
sum方法
import gurobipy as gp
from gurobipy import GRB
l = list([(1, 2), (1, 3), (2, 3), (2, 4)])
model=gp.Model()
d = model.addVars(l, name="d")
model.update()
#构建线性表达式
print((sum(d.select(1,'*'))))
#或上式可为print(d.sum('*',3))代替
#d[1,2] + d[1,3]
#Python sum语句会创建一个线性表达式来捕捉这些变量的总和。得到的表达式为d(1,2)+d(1,3)
prod方法
系数通过dict参数提供,它们使用与tupledict相同的元组进行索引。
import gurobipy as grb
model = grb.Model()
# 定义变量的下标
tl = [(1, 1), (1, 2), (1, 3),
(2, 1), (2, 2), (2, 3),
(3, 1), (3, 2), (3, 3)]
vars = model.addVars(tl, name = "x")
model.update()
# 创建一个系数矩阵,用tuplelist格式存储,键(key)和vars一样
c1 = [(1, 1), (1, 2), (1, 3)]
coeff = grb.tupledict(c1)
coeff[(1, 1)] = 1
coeff[(1, 2)] = 0.3
coeff[(1, 3)] = 0.4
# prod方法:变量系数不是1,变量和系数相乘后累加
print(vars.prod(coeff, 1, '*'))
# 输出
# <gurobi.LinExpr: x[1,1] + 0.3 x[1,2] + 0.4 x[1,3]>