Lingo是學習成本最低的,求解各種規模規劃問題的神器。本文用一個例子讓你快速上手Lingo。
優化問題
現實生活中,遇到的很多問題可以轉換到某個優化問題,并給出某種優化模型。優化問題是指求滿足一定約束條件下的決策變量,使得目標函數最大或最小的問題。
小紅想買若干個茄子、黃瓜和香蕉,每樣最多買5斤,總共至少買10斤。茄子10元,黃瓜20元,香蕉30元。問怎樣買最省錢。
這個優化問題的各個元素如下
- 決策變量,茄子、黃瓜和香蕉每一種買的斤數。
- 約束條件,每樣最多買5斤,至少買10斤水果。
- 目標函數,購買每種用具所花的費用之和。
優化問題的可行解指的是滿足約束條件的決策變量,比如每樣來4斤。最優解指的是使得目標函數取得最大或最小的可行解。
按照決策變量和約束條件的性質,可以把優化問題分為很多種。比如整數規劃、線性規劃等等。你只需要知道Lingo都可以解決即可。
Lingo
簡介
Lingo由美國芝加哥(Chicago)大學的Linus Schrage教授于1980年前后開發,按照求解的規模,分為演示版、學生版、高級版、工業版等等不同的版本。
本文使用的軟件,來自于網絡上流傳已久的版本, LINGO_11.0
使用這個版本是為了方便學習和演示,若要用于商業,還請購買最新的軟件。
開始
直接下載并解壓,找到解壓后的文件夾中的lingo11.exe
即可運行。
軟件界面是英文,而且據筆者了解暫時沒有漢化。默認打開后就是一個標題為LINGO MODEL - LINGO 1
的文本輸入頁面,這是模型頁面。我們所有的工作就從這個頁面開始。
第一個模型
在模型頁面中,復制下列代碼。
由于編碼的問題,直接復制之后的所有中文字符會無法識別。但lingo支持中文,所以將?刪去,改為該有的中文即可。
!模型1:小紅買水果;
title: 買用具;
!目標函數;
min = 10*x1+20*x2+30*x3;
!約束條件;
x1<=5;
x2<=5;
x3<=5;
x1+x2+x3>=10;
依次點擊菜單欄LINGO->solve
,彈出了求解狀態窗口(solver status}和求解報告窗口(solution report).
回顧一下問題。小紅想買若干個茄子、黃瓜和香蕉,每樣最多買5斤,總共至少買10斤。茄子10元,黃瓜20元,香蕉30元。問怎樣買最省錢。
在求解報告窗口中,我們發現找到了全局最優解,目標函數最少為150,也就是最少花150元。在報告中部,變量和值的部分中,看到茄子和香蕉各買5斤。
也許上面一串代碼只想讓你砸了電腦,放輕松,Lingo不是一門編程語言,只是將我們日常鼠標的操作轉化成了命令而已。
基本元素
語句
Lingo的模型文件由一行行語句組成,Lingo在開始求解模型后,會先分析每行語句,并知道自己該干什么。
每行語句必須以英文分號結尾,事實上所有的符號都必須在英文狀態下輸入。這是因為lingo一般會忽略空格和換行符,所以在Lingo看來
x1
<
=
5;
和x1<=5;
沒什么區別。
值得一提的是,若語法錯誤,比如輸入了中文的分號,Lingo會提示
Incalid input: A syntax error has occurred
并且指出錯誤的符號。
Lingo中允許注釋,并且注釋中可以添加中文,就像第一個實例中的一樣。注釋語句可以出現在任何地方,以!
起始,末尾;
結束,Lingo會忽略掉這兩個符號間的所有內容。就像這樣
!ingnoreAnything;
我們推薦模型的每一行都給出注釋,因為也許幾天后你自己也不知道當初的想法。
變量
Lingo不區分大小寫字母, 忽略掉這一點會導致令人沮喪的行為,就像Lingo的腦子突然短路了一樣。
變量不必預先定義,直接在語句中出現即可。決策變量也沒有特殊的標記,沒有賦予初值的變量都作為可變得決策變量。所以不要給決策變量賦初值。
變量名必須用字母開頭,其后可以是其他字符、數字或下劃線。變量名不能超過32個字符。
就像我們在第一個模型中,語句
min = 10*x1+20*x2+30*x3;
創造了x1、x2和x3三個變量,代表茄子、黃瓜和香蕉的購買量。
運算符
Lingo支持所有的數學運算,以及常見的函數。所有支持的函數以及調用的命令,讀者可以去查閱Lingo的幫助文檔。當然也可以看文末的參考文獻。
直接在模型頁面輸入這個例子,求解試一試
x1+1=1;
x2*1=1;
x3/2=1;
x4-2=1;
x5^2=1;
(x6+x7)^2=1;
有一類特殊的運算符被用在條件判斷中,被稱為條件和邏輯運算符。在提到集合以及集合函數后,我們會繼續介紹。
集合
基本集合
集合是Lingo非常強大的功能。因為我們一般涉及到的數據不會只有兩三鐘,而是成百上千的數據,以及他們之間彼此構成的百萬計的矩陣。
還是從第一個模型開始。
小紅看到水果生意這么好做,決定前去批發水果倒賣。假設她要買編號從1到10共10種水果,我們設x(j)
為編號為j
的水果的購買量,那么j
的取值范圍就是1到10,這一百個整數。
于是我們建立了一個擁有10個元素的集合。在Lingo中,用集合域來定義集合。
將下面的代碼復制到空白的模型文件中,并求解。
!下面sets:和endsets之間的部分被稱為集合域;
sets:
!fruit表示一個索引范圍,從1到10。x為水果購買量;
fruit/1..10/:x;
!集合域的結束;
endsets
集合使用前,必須在集合域定義。集合域可以出現在模型文件的任意部分,但不能嵌套在其他域中。
一個集合的定義語句包括三個部分:指標集名稱、指標范圍、集合名稱。
fruit/1..10/:x;
其中fruit
為指標集名稱,/1..10/
表示指標范圍,x
表示擁有指標fruit
的集合。
在一個集合定義語句中,可以一次定義多個擁有相同指標的集合,之間用,
隔開。也可以把集合名留空,表示給一個指標集名稱指定了一個指標范圍。
fruit/1..100/:x,y;!定義了兩個有10個元素的集合;
要想引用集合中的變量,使用
x(1);
這種集合名后附一個括號表示元素的坐標。
派生集合
我們更常用的是派生集合,即有雙下標的集合,就是我們日常所說的矩陣。
小紅可以從80個供應商手中拿貨,將供應商按1到8編號。第i
號供應商可以提供的第j
號水果的量設為c(ij)
。則c(ij)
構成的集合共有80個元素,并用兩個指標i,j
索引。
在Lingo中,使用派生集合來定義這種兩個指標集生成的矩陣。
sets:
fruit/1..10/:x;
supplier/1..8/;
supply(supplier,fruit):c;
endsets
上述語句中,先定義了兩個指標集fruit
和supplier
,范圍分別是1到10和1到8. 隨后用新的語法定義了前面提到的供應量c(ij)
。
supply(supplier,fruit):c;
這行語句表示定義行坐標為supplier
,列坐標為fruit
的新指標集supply
,并且說明變量c
擁有指標集supply
. 換句話說,集合c
是一個行坐標為supplier
,列坐標為fruit
的矩陣。
引用派生集合中元素的語法為
c(1,1)
這句引用了第一行第一列的元素。
也可以從三個或多個指標集中派生集合。
數據域
定義集合之后,若集合中的元素本身不是決策變量,我們需要對集合中的元素進行賦值。
小紅需要知道10種水果的詳細價錢,以及8個供應商提供的80個供應量數據。Lingo同樣也要知道這些。
sets:
!增加了price集合;
fruit/1..10/:x,price;
supplier/1..8/;
supply(fruit,supplier):c;
endsets
data:
price=10,20,30,40,50,60,20,10,5,10;
c=
1 2 1 3 1 3 1 4 1 4
1 2 1 3 1 4 1 3 1 4
1 2 1 3 1 3 1 4 1 4
1 2 1 3 1 3 1 4 1 4
1 2 1 3 1 3 1 4 1 4
1 2 1 3 1 3 1 4 1 4
1 2 1 3 1 3 1 4 1 4;
enddata
對非決策變量進行賦值時,賦值語句必須位于數據域中。數據域以data:
開始,enddata
結束。
賦值時,只需要按照數學中的寫法將元素排列上去即可。一般派生集合我們采用示例代碼中的寫法,讓人一目了然。但是要注意,c
中的換行并不是必須的,只是為了方便人去理解。
集合函數
定義集合的一大優勢在于,可以用非常方便的方法遍歷集合。
總共有四個集合函數,包括遍歷函數@for
,求和函數@sum
, 最小值函數@min
和最大值函數@max
。
你可以像這樣使用集合函數
z=@sum(fruit(j)|j #GE#2
:price(j));
上面的語句表示對標號大于等于2的水果的價錢求和。注意其中的換行并不是必須的。
一般語法為
@function(setname(set_index)|conditional_qualifier:
? expression);
其中function
為四種集合函數,setname
為指標集名稱,set_index
為指標變量,|conditional_qualifier
是條件判斷,用于界定指標變量的范圍, expression
為所要求的表達式。
邏輯運算符
邏輯運算符只能用于條件判斷中,使用條件判斷的場合包括上文提到的集合函數,以及@if
語句中。
常用的邏輯運算符有
運算符 | 意義 |
---|---|
#eq# | 相等 |
#ne# | 不相等 |
#gt# | 大于 |
#ge# | 大于等于 |
#lt# | 小于 |
#le# | 小于等于 |
#and# | 與 |
#or# | 或 |
#not# | 非 |
@if
是條件函數,一般用在分段函數中。語法為
@if(條件表達式,真值,假值);
如果條件表達式的結果為真,則這個函數返回真值,表達式結果為假,則返回假值。
變量限制
我們在實際應用中,往往要對決策變量做某些限制。比如小紅買水果,若商家只愿意整斤賣,則水果購買量必須為整數。
Lingo默認的變量取值范圍是大于零的實數,若要指出某個決策變量的限制,需要使用變量限制語句。
變量限制的語法如下
@bin(x);
這行語句表示x只能取0或1.
所有的變量限制語句如下
語句 | 意義 |
---|---|
@bnd(下限,x,上限) | x取下限到上限之間的值 |
@free(x) | x取所有實數 |
@gin(x) | x取整數 |
@bin(x) | x取0,1 |
變量限制語句一般用在@for
中。
最終的模型
小紅想從8個供貨商購進10種水果。從第i個供貨商購買第j種水果的購買量為x(ij), 第i個供貨商對第j種水果的供應量為c(j), 每種水果統一市場定價為price(j)。若總共進50斤水果,最多從一個購買手中購進10斤水果。水果必須論整斤賣。問怎樣購買最省錢。
下面是模型文件的內容。
sets:
!price(j)是每種水果的價錢;
fruit/1..10/:price;
supplier/1..8/;
!c(ij)為供貨量,x(ij)為購買量;
supply(supplier,fruit):c,x;
endsets
data:
price=10,20,30,40,50,60,20,10,5,10;
c=
1 2 1 3 1 3 1 4 1 4
1 2 1 3 1 4 1 3 1 4
1 2 1 3 1 3 1 4 1 4
1 2 1 3 1 3 1 4 1 4
1 2 1 3 1 3 1 4 1 4
1 2 1 3 1 3 1 4 1 4
1 2 1 3 1 3 1 4 1 4
1 2 1 3 1 3 1 4 1 4;
enddata
min = @sum(fruit(J):
@sum(supplier(I): price(J)*x(I,J)));
!每種水果購買量不超過供應量;
@for(supply(I,J):
x(I,J) <= c(I,J));
!至少購買50斤水果;
@sum(supply(I,J):
x(I,J))>=50;
!每種供應商至多購買十斤;
@for(supplier(I):
@sum(fruit(J):x(I,J))<=10);
!購買量必須為整數;
@for(supply(I,J):
@gin(x));
Have fun!