问题描述
我是优化问题的新手,正在研究一个简单的最大化问题,我可以在 Excel 中非常简单地解决这个问题。但是,我需要在 Python 中扩展它并需要一些帮助。
宏 | 食物 | 卡路里 | 能源 |
---|---|---|---|
蛋白质 | 鱼 | 100 | 60 |
蛋白质 | 羊肉 | 200 | 40 |
蛋白质 | 鸡蛋 | 200 | 38 |
碳水化合物 | 香蕉 | 200 | 25 |
碳水化合物 | 土豆 | 200 | 30 |
碳水化合物 | 米饭 | 200 | 40 |
脂肪 | 鳄梨 | 450 | 50 |
脂肪 | 奶酪 | 400 | 60 |
脂肪 | 奶油 | 500 | 55 |
考虑到以下限制,我需要最大化能量(e):
- 每个 Macros(m) 只能消耗 1 个食品 (i)。所以我需要一个指示变量 (0/1) 来从 m - 蛋白质、脂肪和碳水化合物中的每一个中只选择 1 个。
- 总卡路里数 (c) 不应超过恒定值 假设每个项目有 1 个部分(对此没有限制)
问题表述:
变量: X (m,i) → 二元变量 = {1,如果宏 m 和项目 i 被选中,0 否则}
最大化 e(m,i) * X(m,i)
参数: 卡路里 (C) -> 每种卡路里(宏观、食物)
受约束: 对于每个 m,Σ X (m,i)
到目前为止,我认为这是一个具有非线性约束的混合整数问题。
- 我曾尝试使用 Pulp,但由于非线性约束而失败。如果我去除非线性,它就可以正常工作。
- 我尝试使用 Scipy Optimize,但 Scipy 不允许创建整数变量。
我如何使用 Python 解决这个问题?我是否误解了这里的问题?
更新:
上面缺少由于 mean
而添加的非线性组件。我将问题从 total
上的约束更新为 mean
上的约束。在非数学术语中,我取所有宏相乘后得到的数字的平均值,因为我希望我的平均卡路里小于常数 N。
从数学上来说, Σ c(m,i)/ X(i)
解决方法
正如您已经提到的,scipy.optimize.minimize
无法处理混合整数问题 (MIP)。最多可以做的是尝试通过惩罚方法来解决 MIP,即在目标上添加一个惩罚函数,如 1.0/eps * np.sum(x*(1 - x))
,其中 eps > 0
是给定的惩罚参数,x
np.ndarray
。
但是,使用 MIP 求解器解决问题要方便得多。由于您的问题具有众所周知的类似背包的结构,您甚至可以期待非商业 MIP 求解器(PuLp 默认使用 CBC)来利用您问题的底层结构。在这里,我推荐以下公式:
Binary variables:
x[i] = 1 if fooditem i is chosen,0 otherwise
Parameters:
a[i][m] = 1 if fooditem i covers macro m,0 otherwise
c[i] calories for fooditem i
e[i] energy for fooditem i
N total calories limit
Model:
max Σ (e[i] * a[i][m] * x[i],∀ i ∈ Fooditems,m ∈ Macros)
s.t. Σ (a[i][m] * x[i],∀ i ∈ Fooditems) <= 1 ∀ m ∈ Macros. (1)
Σ (c[i] * x[i],∀ i ∈ Fooditems) <= N (2)
可以像这样建模和求解:
import pulp
fooditems = {
'Fish': {'macro': 'Protein','calorie': 100,'energy': 60},'Lamb': {'macro': 'Protein','calorie': 200,'energy': 40},'Egg': {'macro': 'Protein','energy': 38},'Banana': {'macro': 'Carbs','energy': 25},'Potato': {'macro': 'Carbs','energy': 30},'Rice': {'macro': 'Carbs','Avocado': {'macro': 'Fat','calorie': 450,'energy': 50},'Cheese': {'macro': 'Fat','calorie': 400,'Cream': {'macro': 'Fat','calorie': 500,'energy': 55},}
# parameters
macros = list({fooditems[i]['macro'] for i in fooditems})
a = {item: {m: 1 if m == fooditems[item]['macro']
else 0 for m in macros} for item in fooditems}
c = {item: fooditems[item]['calorie'] for item in fooditems}
e = {item: fooditems[item]['energy'] for item in fooditems}
N = 1000
# pulp model
mdl = pulp.LpProblem("bla",pulp.LpMaximize)
# binary variables
x = pulp.LpVariable.dicts("x",fooditems,cat="Binary")
# objective
mdl += pulp.lpSum([e[i] * a[i][m] * x[i] for m in macros for i in fooditems])
# constraints (1)
for m in macros:
mdl += (pulp.lpSum([a[i][m]*x[i] for i in fooditems]) <= 1)
# constraints (2)
mdl += (pulp.lpSum([x[i]*c[i] for i in fooditems]) <= N)
# solve the problem
mdl.solve()
print(f"Status: {pulp.LpStatus[mdl.status]}")
for var in mdl.variables():
print(f"{var.name} = {var.varValue:.0f}")
print(f"energy: {mdl.objective.value()}")
这产生了
Status: Optimal
x_Avocado = 0.0
x_Banana = 0.0
x_Cheese = 1.0
x_Cream = 0.0
x_Egg = 0.0
x_Fish = 1.0
x_Lamb = 0.0
x_Potato = 0.0
x_Rice = 1.0
Energy: 160.0