Google OR-Tools(二) 线性优化Linear Optimization

本文参考Google OR-Tools官网文档介绍OR-Tools的使用方法。

1 线性规划问题

线性规划是优化问题里最简单的一种形式,需要极大化或极小化的目标函数是线性的,而约束条件由一组线性等式或不等式组成。很多复杂的非线性规划问题都会需要将其装换成线性规划问题来求解。求解线性规划问题最常用的算法是单纯形法(包括了单纯形表、修正单纯形法、对偶单纯形法等),除此之外还有内点法、灵敏度分析等算法。
线性规划问题的一般形式:
\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}
其中约束条件的个数为m=l+r+qc_ja_{ij}为常系数,b_i也是常数,需要调整为非负数;x_j为决策变量。如果线性规划是凸规划,则局部极大值就是全局极大值。

2 典型案例

营养搭配问题(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

根据上面表格的信息,我们可以建立一个线性规划模型。令x_1,x_2,x_3,x_4,x_5表示五种食物的采购量(g),则有:
\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}
在斯蒂格勒提出这个问题的年代,线性规划算法还尚未出现,因此尽管在1944年他本人计算出了这个问题的可行答案,但他使用的是比较原始的方法计算的。直到1947年,Jack Laderman提出了单纯形算法,才有有效的方法来计算这个问题,不过由于当时还没计算机,需要9个工作人员花了120个工作日才能算出结果。幸运的是我们现在可以利用计算机强大的运算能力和OR-Tools很方便地处理这种问题。

3 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<Variable> variables = new List<Variable>();
            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<Variable> variables = new List<Variable>();
            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());
            }
        }
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容

  • 本章涉及知识点1、线性规划的定义2、可行区域、目标函数、可行解和最优解3、转线性规划为标准型4、转线性规划为松弛型...
    PrivateEye_zzy阅读 38,482评论 2 36
  • 这个题目和陈述句有什么区别呢?陈述句已经确定了,只是告知,没有悬念。而疑问句则是留给我们悬念,让读者好奇。例如我读...
    237b4a592a88阅读 473评论 0 0
  • 有幸听了一堂一位有知青经历的女作家曾老师的演讲。她抽取了一些她作品中的片断,与大家分享。 年代、成分、知青、真诚、...
    国庆007阅读 182评论 0 0
  • 太上曰:祸福无门,唯人自召。善恶之报,如影随形。是以天地有司过之神依人所犯轻重,以夺人算。算减则贫耗,多逢忧患,人...
    清霞亮阅读 440评论 0 0
  • 年初三上午坐车到云和县城,转车坐上去云和的小巴、一路环山而上,刚到半山腰便是云雾缭绕,大雾弥漫,已经...
    牧风汉子阅读 723评论 0 1