本文参考Google OR-Tools官网文档介绍OR-Tools的使用方法。
1 线性规划问题线性规划是优化问题里最简单的一种形式,需要极大化或极小化的目标函数是线性的,而约束条件由一组线性等式或不等式组成。很多复杂的非线性规划问题都会需要将其转换成线性规划问题来求解。求解线性规划问题最常用的算法是单纯形法(包括了单纯形表、修正单纯形法、对偶单纯形法等),除此之外还有内点法、灵敏度分析等算法。
线性规划问题的一般形式:
maximizec1x1+c2x2+c3x3+...+cnxnsubject toai1x1+ai2x2+ai3x3+...+ainxn≤bi, i=1,...,laj1x1+aj2x2+aj3x3+...+ajnxn≥bj, j=l+1,...,l+rak1x1+ak2x2+ak3x3+...+aknxn=bk, k=l+r+1,...,l+r+qx1≥0,x2≥0,...,xn≥0
\begin{aligned}
maximize\quad& c_1x_1+c_2x_2+c_3x_3+...+c_n x_n\\
subject\ to\quad& a_{i1} x_1+a_{i2} x_2+a_{i3} x_3+...+a_{in} x_n\leq b_i, \ i=1,...,l\\
& a_{j1} x_1+a_{j2} x_2+a_{j3} x_3+...+a_{jn} x_n\geq b_j, \ j=l+1,...,l+r\\
& a_{k1} x_1+a_{k2} x_2+a_{k3} x_3+...+a_{kn} x_n= b_k, \ k=l+r+1,...,l+r+q\\
& x_1\geq0,x_2\geq0,...,x_n\geq0
\end{aligned}
maximizesubject toc1x1+c2x2+c3x3+...+cnxnai1x1+ai2x2+ai3x3+...+ainxn≤bi, i=1,...,laj1x1+aj2x2+aj3x3+...+ajnxn≥bj, j=l+1,...,l+rak1x1+ak2x2+ak3x3+...+aknxn=bk, k=l+r+1,...,l+r+qx1≥0,x2≥0,...,xn≥0
其中约束条件的个数为m=l+r+qm=l+r+qm=l+r+q,cjc_jcj和aija_{ij}aij为常系数,bib_ibi也是常数,需要调整为非负数;xjx_jxj为决策变量。如果线性规划是凸规划,则局部极大值就是全局极大值。
营养搭配问题(Stigler diet)是由诺贝尔经济学奖获得者乔治·斯蒂格勒(George Joseph Stigler)在上世纪三四十年代提出的一个数学问题,就是要以最小的成本采购食物,同时各个营养指标都要满足。
下面这个表给出了所有的食谱信息(当然这不是斯蒂格勒当时列出的食谱,原始的食谱内容太多,我们简化一下),最后一行是各个营养的每周需求量,最终指定的食谱必须要满足这个需求:
食物 | 蛋白质 | 脂肪 | 碳水化合物 | 价格($/100g) |
---|---|---|---|---|
1 面包 | 8% | 1% | 55% | 0.25 |
2 黄油 | - | 90% | - | 0.5 |
3 奶酪 | 25% | 36% | - | 1.2 |
4 谷物 | 12% | 3% | 75% | 0.6 |
5 巧克力 | 8% | - | 50% | 1.5 |
每周需求量(g) | 550 | 600 | 2000 |
根据上面表格的信息,我们可以建立一个线性规划模型。令x1,x2,x3,x4,x5x_1,x_2,x_3,x_4,x_5x1,x2,x3,x4,x5表示五种食物的采购量(g),则有:
minimize0.25x1+0.5x2+1.2x3+0.6x4+1.5x5subject to0.08x1+0.25x3+0.12x4+0.08x5≥5500.01x1+0.9x2+0.36x3+0.03x4≥6000.55x1+0.75x4+0.5x5≥2000x1≥0,x2≥0,x3≥0,x4≥0,x5≥0
\begin{aligned}
minimize\quad& 0.25x_1+0.5x_2+1.2x_3+0.6x_4+1.5x_5\\
subject\ to\quad& 0.08x_1+0.25x_3+0.12x_4+0.08x_5\geq 550 \\
& 0.01x_1+0.9x_2+0.36x_3+0.03x_4\geq 600 \\
& 0.55x_1+0.75x_4+0.5x_5\geq 2000 \\
& x_1\geq0,x_2\geq0,x_3\geq0,x_4\geq0, x_5\geq0
\end{aligned}
minimizesubject to0.25x1+0.5x2+1.2x3+0.6x4+1.5x50.08x1+0.25x3+0.12x4+0.08x5≥5500.01x1+0.9x2+0.36x3+0.03x4≥6000.55x1+0.75x4+0.5x5≥2000x1≥0,x2≥0,x3≥0,x4≥0,x5≥0
在斯蒂格勒提出这个问题的年代,线性规划算法还尚未出现,因此尽管在1944年他本人计算出了这个问题的可行答案,但他使用的是比较原始的方法计算的。直到1947年,Jack Laderman提出了单纯形算法,才有有效的方法来计算这个问题,不过由于当时还没计算机,需要9个工作人员花了120个工作日才能算出结果。幸运的是我们现在可以利用计算机强大的运算能力和OR-Tools很方便地处理这种问题。
我们写个.Net Core控制台应用来解算这个问题。首先把上边表格的食谱信息用DataTable对象存储
//已知量
DataTable foodTable = new DataTable();
foodTable.Columns.Add("FoodName", typeof(string));
foodTable.Columns.Add("Protein", typeof(double));
foodTable.Columns.Add("Fat", typeof(double));
foodTable.Columns.Add("Carbohydrate", typeof(double));
foodTable.Columns.Add("Price", typeof(double));
DataRow dr = foodTable.NewRow();
dr["FoodName"] = "Bread";
dr["Protein"] = 0.08;//蛋白质
dr["Fat"] = 0.01;//脂肪
dr["Carbohydrate"] = 0.55;//碳水化合物
dr["Price"] = 0.25;
foodTable.Rows.Add(dr);
dr = foodTable.NewRow();
dr["FoodName"] = "Butter";
dr["Protein"] = 0;
dr["Fat"] = 0.9;
dr["Carbohydrate"] = 0;
dr["Price"] = 0.5;
foodTable.Rows.Add(dr);
dr = foodTable.NewRow();
dr["FoodName"] = "Cheese";
dr["Protein"] = 0.25;
dr["Fat"] = 0.36;
dr["Carbohydrate"] = 0;
dr["Price"] = 1.2;
foodTable.Rows.Add(dr);
dr = foodTable.NewRow();
dr["FoodName"] = "Grain";
dr["Protein"] = 0.12;
dr["Fat"] = 0.03;
dr["Carbohydrate"] = 0.75;
dr["Price"] = 0.6;
foodTable.Rows.Add(dr);
dr = foodTable.NewRow();
dr["FoodName"] = "Chocolate Bar";
dr["Protein"] = 0.08;
dr["Fat"] = 0;
dr["Carbohydrate"] = 0.5;
dr["Price"] = 1.5;
foodTable.Rows.Add(dr);
double minPorten = 550;
double minFat = 600;
double minCarbohydrate = 2000;
新建一个Linear Solver对象:
//Create a Linear Solver object
var solver= Solver.CreateSolver("DietSolver", "GLOP_LINEAR_PROGRAMMING");
定义决策变量
//Create variables
List variables = new List();
foreach (DataRow row in foodTable.Rows)
{
variables.Add(solver.MakeNumVar(0, double.PositiveInfinity, row["FoodName"].ToString()));
}
定义三条线性约束
//Create linear constraints
Google.OrTools.LinearSolver.Constraint ct1 = solver.MakeConstraint(minPorten, double.PositiveInfinity, "minPorten");
for(int i=0;i<foodTable.Rows.Count;i++)
{
var variable = variables[i];
ct1.SetCoefficient(variable, (double)foodTable.Rows[i]["Protein"]);
}
Google.OrTools.LinearSolver.Constraint ct2 = solver.MakeConstraint(minFat, double.PositiveInfinity, "minFat");
for (int i = 0; i < foodTable.Rows.Count; i++)
{
var variable = variables[i];
ct2.SetCoefficient(variable, (double)foodTable.Rows[i]["Fat"]);
}
Google.OrTools.LinearSolver.Constraint ct3 = solver.MakeConstraint(minCarbohydrate, double.PositiveInfinity, "minCarbohydrate");
for (int i = 0; i < foodTable.Rows.Count; i++)
{
var variable = variables[i];
ct3.SetCoefficient(variable, (double)foodTable.Rows[i]["Carbohydrate"]);
}
定义目标
//Create objective
Objective objective = solver.Objective();
for (int i = 0; i < foodTable.Rows.Count; i++)
{
var variable = variables[i];
objective.SetCoefficient(variable, (double)foodTable.Rows[i]["Price"]);
}
objective.SetMinimization();
最后求解
//Solve
var status=solver.Solve();
Console.WriteLine("Solution:");
Console.WriteLine("Objective value = " + solver.Objective().Value()+" $");
for (int i = 0; i < foodTable.Rows.Count; i++)
{
var variable = variables[i];
Console.WriteLine(foodTable.Rows[i]["FoodName"].ToString() +" : "+ variable.SolutionValue());
}
完整的程序:
using System;
using Google.OrTools.LinearSolver;
using System.Data;
using System.Collections.Generic;
namespace Demo2
{
class Program
{
static void Main(string[] args)
{
//已知量
DataTable foodTable = new DataTable();
foodTable.Columns.Add("FoodName", typeof(string));
foodTable.Columns.Add("Protein", typeof(double));
foodTable.Columns.Add("Fat", typeof(double));
foodTable.Columns.Add("Carbohydrate", typeof(double));
foodTable.Columns.Add("Price", typeof(double));
DataRow dr = foodTable.NewRow();
dr["FoodName"] = "Bread";
dr["Protein"] = 0.08;//蛋白质
dr["Fat"] = 0.01;//脂肪
dr["Carbohydrate"] = 0.55;//碳水化合物
dr["Price"] = 0.25;
foodTable.Rows.Add(dr);
dr = foodTable.NewRow();
dr["FoodName"] = "Butter";
dr["Protein"] = 0;
dr["Fat"] = 0.9;
dr["Carbohydrate"] = 0;
dr["Price"] = 0.5;
foodTable.Rows.Add(dr);
dr = foodTable.NewRow();
dr["FoodName"] = "Cheese";
dr["Protein"] = 0.25;
dr["Fat"] = 0.36;
dr["Carbohydrate"] = 0;
dr["Price"] = 1.2;
foodTable.Rows.Add(dr);
dr = foodTable.NewRow();
dr["FoodName"] = "Grain";
dr["Protein"] = 0.12;
dr["Fat"] = 0.03;
dr["Carbohydrate"] = 0.75;
dr["Price"] = 0.6;
foodTable.Rows.Add(dr);
dr = foodTable.NewRow();
dr["FoodName"] = "Chocolate Bar";
dr["Protein"] = 0.08;
dr["Fat"] = 0;
dr["Carbohydrate"] = 0.5;
dr["Price"] = 1.5;
foodTable.Rows.Add(dr);
double minPorten = 550;
double minFat = 600;
double minCarbohydrate = 2000;
//Create a Linear Solver object
var solver= Solver.CreateSolver("DietSolver", "GLOP_LINEAR_PROGRAMMING");
//Create variables
List variables = new List();
foreach (DataRow row in foodTable.Rows)
{
variables.Add(solver.MakeNumVar(0, double.PositiveInfinity, row["FoodName"].ToString()));
}
//Create linear constraints
Google.OrTools.LinearSolver.Constraint ct1 = solver.MakeConstraint(minPorten, double.PositiveInfinity, "minPorten");
for(int i=0;i<foodTable.Rows.Count;i++)
{
var variable = variables[i];
ct1.SetCoefficient(variable, (double)foodTable.Rows[i]["Protein"]);
}
Google.OrTools.LinearSolver.Constraint ct2 = solver.MakeConstraint(minFat, double.PositiveInfinity, "minFat");
for (int i = 0; i < foodTable.Rows.Count; i++)
{
var variable = variables[i];
ct2.SetCoefficient(variable, (double)foodTable.Rows[i]["Fat"]);
}
Google.OrTools.LinearSolver.Constraint ct3 = solver.MakeConstraint(minCarbohydrate, double.PositiveInfinity, "minCarbohydrate");
for (int i = 0; i < foodTable.Rows.Count; i++)
{
var variable = variables[i];
ct3.SetCoefficient(variable, (double)foodTable.Rows[i]["Carbohydrate"]);
}
//Create objective
Objective objective = solver.Objective();
for (int i = 0; i < foodTable.Rows.Count; i++)
{
var variable = variables[i];
objective.SetCoefficient(variable, (double)foodTable.Rows[i]["Price"]);
}
objective.SetMinimization();
//Solve
var status=solver.Solve();
Console.WriteLine("Solution:");
Console.WriteLine("Objective value = " + solver.Objective().Value()+" $");
for (int i = 0; i < foodTable.Rows.Count; i++)
{
var variable = variables[i];
Console.WriteLine(foodTable.Rows[i]["FoodName"].ToString() +" : "+ variable.SolutionValue());
}
}
}
}