學(xué)建模實(shí)戰(zhàn):用LINGO求解運(yùn)輸與投資優(yōu)化問題)
1. 項(xiàng)目概述當(dāng)數(shù)學(xué)建模遇上LINGO最近在帶學(xué)生做數(shù)學(xué)建模競賽的培訓(xùn)發(fā)現(xiàn)很多同學(xué)一看到“運(yùn)輸方案優(yōu)化”或者“連續(xù)投資決策”這類題目就有點(diǎn)發(fā)怵。題目描述往往是一大堆數(shù)據(jù)表格要求你給出成本最低的運(yùn)輸計劃或者未來幾年收益最大的投資策略。思路大家其實(shí)都有列方程、設(shè)變量嘛但真到了求解那一步尤其是變量一多、約束一復(fù)雜手工計算幾乎不可能用通用編程語言比如Python從頭寫算法又容易在調(diào)試上耗費(fèi)大量時間。這時候一個專業(yè)的工具就能讓你事半功倍——它就是LINGO。簡單來說LINGO是一款專門用于求解線性規(guī)劃、非線性規(guī)劃、整數(shù)規(guī)劃等優(yōu)化問題的軟件。你可以把它理解為一個“數(shù)學(xué)規(guī)劃模型的翻譯器和求解器”。你不需要去深究背后的單純形法、分支定界法是什么原理你只需要用接近數(shù)學(xué)語言的格式把目標(biāo)函數(shù)、決策變量和約束條件“告訴”它它就能快速給你算出最優(yōu)解。對于“運(yùn)輸方案”和“連續(xù)投資”這類典型的運(yùn)籌學(xué)問題LINGO幾乎是量身定做的解決方案。本文我就結(jié)合這兩個經(jīng)典場景帶你從零開始掌握用LINGO將數(shù)學(xué)模型轉(zhuǎn)化為實(shí)際解決方案的全過程并分享一些我多年輔導(dǎo)中總結(jié)的、在官方教程里未必會寫的實(shí)戰(zhàn)技巧和避坑指南。2. 核心思路拆解從問題到LINGO模型在打開LINGO軟件之前最關(guān)鍵的一步是把現(xiàn)實(shí)問題抽象成一個清晰的數(shù)學(xué)模型。很多新手會直接跳進(jìn)軟件里敲代碼結(jié)果發(fā)現(xiàn)模型本身有邏輯漏洞求解自然失敗。我們分別看看這兩個問題的建模核心。2.1 運(yùn)輸問題成本最小化的網(wǎng)絡(luò)流運(yùn)輸問題的背景非常直觀你有若干個工廠產(chǎn)地每個工廠有固定的產(chǎn)量供給量你也有若干個銷售點(diǎn)銷地每個銷售點(diǎn)有固定的需求量。從每個工廠到每個銷售點(diǎn)都有單位運(yùn)輸成本。目標(biāo)是如何安排運(yùn)輸量在滿足所有供給和需求的前提下讓總運(yùn)輸成本最低。這本質(zhì)上是一個線性規(guī)劃問題。它的核心結(jié)構(gòu)非常規(guī)整決策變量通常設(shè)為X_ij表示從工廠i運(yùn)到銷售點(diǎn)j的貨物量。目標(biāo)函數(shù)總成本最小化。即對所有可能的i, j求單位成本_ij * X_ij的總和。約束條件供給約束對于每個工廠i運(yùn)往所有銷售點(diǎn)的貨物總量不能超過其產(chǎn)量。需求約束對于每個銷售點(diǎn)j從所有工廠運(yùn)來的貨物總量必須恰好滿足其需求量平衡型運(yùn)輸問題。非負(fù)約束運(yùn)輸量不能為負(fù)數(shù)。這個模型的妙處在于它雖然變量多工廠數(shù)×銷售點(diǎn)數(shù)但約束和目標(biāo)函數(shù)都是線性的結(jié)構(gòu)清晰是LINGO最擅長處理的類型之一。在LINGO中我們可以利用集合SETS功能來優(yōu)雅地定義這種多維變量和約束避免為每一個變量和約束都寫一行代碼。2.2 連續(xù)投資問題動態(tài)決策的跨期優(yōu)化連續(xù)投資問題也叫多階段投資問題比靜態(tài)的運(yùn)輸問題多了一個時間維度。典型描述是你有一筆初始資金需要在未來若干年比如5年內(nèi)進(jìn)行投資。市場上有多種投資項(xiàng)目每種項(xiàng)目每年有特定的投資期限和收益率。每年你會有新的資金流入如項(xiàng)目到期回收的本息然后需要重新分配投資。目標(biāo)是在規(guī)劃期結(jié)束時讓總資金額最大。這本質(zhì)上是一個帶有時序關(guān)系的線性或非線性規(guī)劃問題。其核心在于“動態(tài)”和“決策鏈”決策變量通常設(shè)為X_it表示在第t年投入到項(xiàng)目i的資金量。目標(biāo)函數(shù)最終時刻如第5年末的總資金最大化。約束條件這是關(guān)鍵和難點(diǎn)資金流平衡約束最核心每年可用于投資的資金總額等于上年末回收的資金加上本年可能的外來資金。這建立起了年份之間的連接。例如第2年的投資總額等于第1年那些為期1年的項(xiàng)目回收的本息和。項(xiàng)目投資限額約束每個項(xiàng)目每年可能有投資上限。非負(fù)約束投資額不能為負(fù)數(shù)。邏輯約束如果項(xiàng)目期限大于1年那么一旦投資在到期前資金是被鎖定的中間年份不能再對其追加或減少投資。這通常需要通過定義變量或附加約束來實(shí)現(xiàn)。注意連續(xù)投資問題的模型建立比運(yùn)輸問題更考驗(yàn)邏輯嚴(yán)密性。一個常見的錯誤是忽略了資金的時間鎖定效應(yīng)或者寫錯了資金平衡方程的順序?qū)е履P颓蠼饨Y(jié)果違背常識比如第一年就把所有錢投到一個長期項(xiàng)目后面幾年無錢可投。在LINGO中實(shí)現(xiàn)時需要仔細(xì)定義集合尤其是時間集并利用FOR循環(huán)來精確表達(dá)這些跨期約束。3. LINGO實(shí)戰(zhàn)運(yùn)輸問題建模與求解我們用一個具體例子來上手。假設(shè)有3個工廠A1, A2, A3產(chǎn)量分別是7噸、4噸、9噸4個銷售點(diǎn)B1, B2, B3, B4需求量分別是3噸、6噸、5噸、6噸。單位運(yùn)價表元/噸如下從\到B1B2B3B4A1311310A21928A3741053.1 LINGO模型代碼詳解打開LINGO在模型窗口輸入以下代碼。我會逐段解釋MODEL: ! 1. 定義集合; SETS: factory /A1, A2, A3/: capacity; ! 工廠集合屬性為產(chǎn)能capacity; market /B1, B2, B3, B4/: demand; ! 市場集合屬性為需求demand; link(factory, market): cost, volume; ! 派生集合表示工廠到市場的路線屬性為單位成本cost和運(yùn)量volume; ENDSETS ! 2. 輸入數(shù)據(jù); DATA: capacity 7 4 9; ! 工廠產(chǎn)能; demand 3 6 5 6; ! 市場需求; cost 3 11 3 10 ! 成本矩陣按行排列: A1-B1,B2,B3,B4; 1 9 2 8 ! A2-B1,B2,B3,B4; 7 4 10 5; ! A3-B1,B2,B3,B4; ENDDATA ! 3. 定義目標(biāo)函數(shù)最小化總運(yùn)輸成本; MIN SUM(link(i, j): cost(i, j) * volume(i, j)); ! 4. 定義約束條件; ! 供給約束每個工廠運(yùn)出的總量不超過其產(chǎn)能; FOR(factory(i): SUM(market(j): volume(i, j)) capacity(i) ); ! 需求約束每個市場運(yùn)入的總量等于其需求; FOR(market(j): SUM(factory(i): volume(i, j)) demand(j) ); ! 5. 變量范圍約束非負(fù)已在LINGO中默認(rèn)如需特殊限制可在此添加; END關(guān)鍵點(diǎn)解析集合定義這是LINGO建模的精髓。link(factory, market)自動生成了所有工廠和市場的組合3x412條路線我們無需手動定義12個變量。cost和volume是附著在這些路線上的屬性。數(shù)據(jù)輸入DATA部分用于賦值。注意cost矩陣的輸入是按行排列的順序必須與link集合中元素的生成順序一致通常是先遍歷第一個集合的所有元素再遍歷第二個。這是新手最容易出錯的地方之一。目標(biāo)與約束SUM和FOR是LINGO的循環(huán)求和與循環(huán)定義函數(shù)。FOR(factory(i): ...)意味著為factory集合中的每一個成員i即A1, A2, A3都生成一個后面的約束不等式。這樣三行供給約束就通過一行代碼生成了。3.2 求解與結(jié)果分析輸入代碼后點(diǎn)擊工具欄的“求解”按鈕靶心圖標(biāo)。LINGO會調(diào)用內(nèi)置求解器進(jìn)行計算。求解狀態(tài)報告求解完成后會彈出對話框。你應(yīng)該看到“Global optimal solution found.”找到全局最優(yōu)解這表明求解成功。同時會顯示目標(biāo)函數(shù)值最小總成本和迭代次數(shù)。查看結(jié)果關(guān)閉報告窗口回到模型界面。選擇菜單LINGO - Solution或直接查看狀態(tài)窗口下方的“Solution Report”標(biāo)簽頁。這里會列出所有變量的最優(yōu)值。 對于本例最優(yōu)解volume變量大致如下A1-B3: 5噸 A1-B4: 2噸A2-B1: 3噸 A2-B3: 0噸 A2-B4: 1噸A3-B2: 6噸 A3-B4: 3噸 其他路線運(yùn)量為0 最小總成本為86元。靈敏度分析可選但重要對于線性規(guī)劃LINGO還可以提供靈敏度分析報告LINGO - Range。這份報告會告訴你每個約束條件的“影子價格”和每個變量目標(biāo)函數(shù)系數(shù)的“允許變化范圍”。例如某個工廠產(chǎn)能約束的影子價格是5意味著該工廠產(chǎn)能每增加1單位總成本最多能減少5元。這在商業(yè)決策中極具價值。實(shí)操心得第一次運(yùn)行時如果遇到“No feasible solution found”無可行解大概率是約束條件互相矛盾。比如總產(chǎn)能74920小于總需求365620本例是平衡的。如果不平衡產(chǎn)能需求供給約束應(yīng)改為如果產(chǎn)能需求則問題無解或需求約束需改為并修改目標(biāo)。務(wù)必先手算驗(yàn)證一下總量關(guān)系。4. LINGO進(jìn)階連續(xù)投資問題建模我們看一個簡化版的5年投資問題。假設(shè)有3個項(xiàng)目可供投資項(xiàng)目A每年初投資年末回收本利年利率10%。項(xiàng)目B每年初投資需要兩年跨兩個年度到期后回收本利年利率40%。項(xiàng)目C每年初投資需要三年跨三個年度到期后回收本利年利率80%。 期初有10萬元資金每年末的項(xiàng)目回收款可以用于下一年初再投資。如何分配每年的投資使第5年末的本利總額最大建模思路我們需要定義5個時間點(diǎn)年初。決策變量Xij表示在第i年年初投入到項(xiàng)目j的資金量j1,2,3對應(yīng)A,B,C。關(guān)鍵在于理清每年年初的可投資資金流。4.1 LINGO模型代碼MODEL: SETS: year /1..5/:; ! 定義5個年份; project /A, B, C/:; ! 定義3個項(xiàng)目; invest(year, project): X; ! 決策變量X(i,j)表示第i年初投給項(xiàng)目j的錢; ENDSETS DATA: ! 利率參數(shù); rate_A 0.10; rate_B 0.40; rate_C 0.80; ! 項(xiàng)目期限年; term_A 1; term_B 2; term_C 3; ENDDATA ! 初始資金; INIT: capital 100000; ENDINIT ! 目標(biāo)函數(shù)最大化第五年末的總資金; MAX F; ! 定義每年的資金流平衡; ! 第1年初可投資資金就是初始資金; SUM(project(j): X(1, j)) capital; ! 第2年初可投資資金 第1年項(xiàng)目A到期回收的本利; SUM(project(j): X(2, j)) (1rate_A) * X(1, A); ! 第3年初可投資資金 第2年項(xiàng)目A到期回收的本利 第1年項(xiàng)目B到期回收的本利; SUM(project(j): X(3, j)) (1rate_A) * X(2, A) (1rate_B) * X(1, B); ! 第4年初可投資資金 第3年項(xiàng)目A到期回收的本利 第2年項(xiàng)目B到期回收的本利 第1年項(xiàng)目C到期回收的本利; SUM(project(j): X(4, j)) (1rate_A) * X(3, A) (1rate_B) * X(2, B) (1rate_C) * X(1, C); ! 第5年初可投資資金 第4年項(xiàng)目A到期回收的本利 第3年項(xiàng)目B到期回收的本利 第2年項(xiàng)目C到期回收的本利; SUM(project(j): X(5, j)) (1rate_A) * X(4, A) (1rate_B) * X(3, B) (1rate_C) * X(2, C); ! 第5年末的總資金F等于第5年所有投資到期回收的本利和; F (1rate_A) * X(5, A) (1rate_B) * X(4, B) (1rate_C) * X(3, C); ! 非負(fù)約束; FOR(invest(i, j): X(i, j) 0); END4.2 模型難點(diǎn)與技巧這個模型比運(yùn)輸問題復(fù)雜體現(xiàn)在資金流方程的構(gòu)建每年初的投資總額必須等于上年末回收的資金總額?;厥盏馁Y金來自之前年份投資的、且剛好在本年年初到期的項(xiàng)目。必須仔細(xì)追蹤每個項(xiàng)目的投資年份和到期年份。我上面用注釋詳細(xì)列出了每筆錢的來源建議在紙上畫一個時間線圖來輔助理解。目標(biāo)函數(shù)的定義最終財富F不是簡單地加總所有投資而是第五年末時所有已到期投資的本利和。即第五年初的投資中只有項(xiàng)目A能在當(dāng)年末收回第四年初投的B項(xiàng)目、第三年初投的C項(xiàng)目也會在第五年末到期。使用INIT部分對于初始資金這種在模型開始前就確定的常量可以用INIT部分來初始化使模型更清晰。求解結(jié)果分析求解此模型你會得到每年在各個項(xiàng)目上的最優(yōu)投資分配X(i,j)以及最終的最大本利和F。你會發(fā)現(xiàn)盡管項(xiàng)目C的利率最高但因?yàn)樗i定期長3年所以并不會把所有錢都投給它。最優(yōu)策略是在前期利用C和B的高收益進(jìn)行積累后期則更注重資金的流動性多投A以確保最后一年有足夠的到期資金來貢獻(xiàn)給最終收益F。這個動態(tài)權(quán)衡的過程正是連續(xù)投資問題的精髓。踩坑提醒在寫資金平衡方程時極易出現(xiàn)“時間錯配”。例如把X(1,B)的回收算到第2年初實(shí)際上它需要2年應(yīng)在第3年初回收。一個有效的檢查方法是對于任意X(i, j)其回收年份一定是i term_j。在LINGO中你可以嘗試用更復(fù)雜的集合和SUM函數(shù)來生成這些約束以增強(qiáng)模型的通用性比如年份數(shù)變化時無需重寫方程但這需要更熟練的集合操作。初學(xué)時像上面這樣“笨拙”地明確寫出每個方程反而更不容易出錯也便于調(diào)試。5. 常見問題、調(diào)試技巧與模型優(yōu)化即使模型邏輯正確在LINGO中也可能遇到各種問題。以下是一些常見情況及處理辦法。5.1 常見錯誤與排查錯誤類型可能原因排查與解決方法No feasible solution found1. 約束條件相互矛盾。2. 變量上下限設(shè)置過緊無解空間。3. 初始值設(shè)置不當(dāng)導(dǎo)致求解器誤判。1.檢查總量平衡如運(yùn)輸問題中總供給與總需求關(guān)系。2.簡化模型先注釋掉部分約束看是否能得到可行解逐步添加以定位矛盾點(diǎn)。3. 檢查BND或變量賦值是否合理。Unbounded solution目標(biāo)函數(shù)可以在不違反約束的情況下無限優(yōu)化如利潤無限大。這通常是模型構(gòu)建錯誤。檢查是否遺漏了關(guān)鍵的資源限制約束。例如在投資問題中是否忘記了每年投資總額不能超過可用資金這一根本約束。求解時間過長或內(nèi)存不足1. 問題規(guī)模太大變量/約束過多。2. 模型是非線性或整數(shù)規(guī)劃本身復(fù)雜。1. 嘗試使用LINGO - Options中的求解器選項(xiàng)如調(diào)整迭代次數(shù)、收斂精度。2. 對于整數(shù)規(guī)劃可以設(shè)置啟發(fā)式搜索或調(diào)整分支策略。3. 考慮是否能用線性規(guī)劃近似或者分解問題。結(jié)果與預(yù)期不符1. 目標(biāo)函數(shù)系數(shù)如成本、利率輸入錯誤。2. 約束條件符號,,用錯。3. 單位不統(tǒng)一。1.仔細(xì)核對DATA部分特別是矩陣數(shù)據(jù)的排列順序。2.逐行檢查約束理解其經(jīng)濟(jì)/物理意義。3. 用LINGO - Picture功能可視化模型結(jié)構(gòu)檢查行、列對應(yīng)關(guān)系。5.2 LINGO編程與調(diào)試技巧多用注釋使用!添加詳細(xì)注釋說明每個集合、變量、約束的含義。這對于復(fù)雜的、幾天后再回顧的模型至關(guān)重要。模塊化建模對于大型模型可以分塊編寫。先定義集合和數(shù)據(jù)再寫目標(biāo)函數(shù)然后分模塊寫約束如供給約束塊、需求約束塊、資金平衡塊。每寫完一個模塊可以暫時注釋掉其他部分單獨(dú)測試其語法是否正確。利用FOR和SUM簡化代碼這是LINGO的核心優(yōu)勢。對于規(guī)律性強(qiáng)的約束一定要用循環(huán)語句避免手動羅列。例如如果我們的投資問題有10年、10個項(xiàng)目手動寫資金平衡方程將是災(zāi)難。此時應(yīng)該定義“項(xiàng)目期限”屬性然后利用SUM函數(shù)和條件判斷來動態(tài)生成回收項(xiàng)。! 假設(shè)years是年份集合projects是項(xiàng)目集合term(p)是項(xiàng)目p的期限; FOR(years(t) | t #GT# 1: ! 從第2年開始; SUM(projects(p): X(t, p)) SUM(projects(p): (1rate(p)) * X(t - term(p), p)) ! 注意這里需要確保 t-term(p) 1否則會引用不存在的變量需用IF函數(shù)處理; );查看模型概況求解前點(diǎn)擊LINGO - Generate - Display model可以查看LINGO將你的集合語言展開后的完整線性規(guī)劃模型。這是驗(yàn)證模型是否按你設(shè)想的方式構(gòu)建的終極手段。從簡單案例開始不要一開始就構(gòu)建復(fù)雜的模型。先用2個工廠、2個市場或者2年、2個項(xiàng)目的小數(shù)據(jù)測試模型。用手算能驗(yàn)證結(jié)果確認(rèn)模型邏輯無誤后再替換成真實(shí)的大規(guī)模數(shù)據(jù)。5.3 模型擴(kuò)展與優(yōu)化思路掌握了基礎(chǔ)模型后你可以應(yīng)對更復(fù)雜的情況運(yùn)輸問題的變種產(chǎn)銷不平衡修改供給或需求約束的等號為不等號。帶有中轉(zhuǎn)站引入新的節(jié)點(diǎn)集合中轉(zhuǎn)站變量變?yōu)閄_ik(工廠到中轉(zhuǎn)站) 和Y_kj(中轉(zhuǎn)站到市場)并增加中轉(zhuǎn)站的流量平衡約束。多商品運(yùn)輸為每種商品增加一個維度使用三維數(shù)組X_ijk(工廠i, 市場j, 商品k)。投資問題的變種風(fēng)險約束引入風(fēng)險系數(shù)增加組合風(fēng)險不超過某閾值的約束。借貸可能引入“借款”變量利率不同在資金平衡方程中增加借款流入和還款流出項(xiàng)。非線性收益如果收益率與投資額有關(guān)如規(guī)模效應(yīng)則目標(biāo)函數(shù)變?yōu)榉蔷€性需要在LINGO中選用非線性求解器LINGO - Options - Solver中選擇Global或Multistart。最后我個人在數(shù)學(xué)建模競賽培訓(xùn)和實(shí)際項(xiàng)目中最深的體會是LINGO這類工具的強(qiáng)大永遠(yuǎn)建立在你對問題本質(zhì)的深刻理解之上。軟件只是幫你做計算而建模才是真正的思考?;ㄔ谇逦x變量、梳理約束關(guān)系上的時間遠(yuǎn)比調(diào)試錯誤代碼的時間更有價值。當(dāng)你拿到一個復(fù)雜問題不妨先問自己決策是什么目標(biāo)是什么限制條件有哪些它們之間如何用數(shù)學(xué)語言關(guān)聯(lián)把這些想明白了翻譯成LINGO代碼就是水到渠成的事。開始的時候可能會覺得語法生疏但就像學(xué)任何語言一樣多寫幾個完整的模型你就能越來越熟練地用它來表達(dá)你的優(yōu)化思想了。