跳轉到內容

GLPK/第三方 API 包裝器

來自華夏公益教科書,開放書籍,共建開放世界

此頁面描述了與原生 GLPK API 集 介面的“包裝器”類,以提供額外的功能。此頁面主要針對開發大型應用程式的程式設計師。

GLPK API 在很多方面都是比較底層的,而且不太智慧(但它們正在改進)。尤其是 C++ 開發人員,有時會編寫包裝器類來在 GLPK 和他們的客戶端程式碼之間進行介面。他們的動機通常源於對以下一項或兩項功能的需求:

  • 提供更高層、更定製、更智慧的 客戶端呼叫集
  • 能夠在不影響客戶端程式碼的情況下 切換求解器 - 就像需要重新連結但 不需要 重新編譯一樣。

在這個背景下,智慧包括能夠在內部維護行和列計數、識別不完整模型、選擇特定求解器呼叫、優雅地失敗以及其他相關的自適應行為。

以上兩方面的實現都需要設計權衡,這意味著 API 包裝器在某種程度上往往是針對特定情況的。例如,為了保持共性,切換求解器的能力可能需要排除專門的求解器功能。


COIN-OR 開放求解器介面 (Osi)

[編輯 | 編輯原始碼]

求解器切換的關鍵舉措是 COIN-OR Osi 專案。

GLPK 透過派生的包裝器類獲得支援OsiGlpk. 測試環境包括(沒有提到 Mac OS X)

  • Windows 使用 Microsoft Visual C++ V6 和 V7
  • Windows 使用 Cygwin 工具鏈
  • Linux 使用 g++ 2.95.2 及更高版本。

鑑於 GLPK 正在進行的 更新 其 API 集的計劃,OsiGlpk程式碼目前依賴於已棄用的 API(截至 2010 年 12 月,一個尋求對齊的 2008 年票據仍然開放 [1])。對於 LP 問題這可能不是問題,但對於分支定界切割方法來說可能是個問題。建議開發人員在繼續之前確認(可能需要修改)此介面類。COIN-OR Osi 仍在持續開發中。

Osi 程式碼庫更改在 projects.coin-or.org/Osi/changeset 中進行跟蹤。

通用線性最佳化包 (Glop)

[編輯 | 編輯原始碼]

Glop,通用線性最佳化包,是一個庫包,它提供函式來在 C 或 C++ 程式中建立、操作和求解線性規劃 (LP)。Glop 主要是一個包裝器庫,允許使用者透過同一個 API 訪問不同的現有 LP 求解器。Glop 支援 GLPK,由 Francois Galea 編寫。

LEMON 圖形庫

[編輯 | 編輯原始碼]

LEMON 是用於網路中高效建模和最佳化的庫。[2] 這是一個 C++ 模板庫,提供了各種資料結構和演算法,用於“主要與圖形相關的組合最佳化任務”。GLPK 是 LEMON 支援的眾多求解器之一。

LEMON 專案由匈牙利布達佩斯羅蘭大學運籌學系埃格瓦里組合最佳化研究小組 (EGRES) 於 2003 年啟動。LEMON 是 COIN-OR 倡議的成員,該倡議是一組與運籌學相關的開源專案。LEMON 在 Boost 軟體許可證版本 1.0 下分發,這是一個允許商業和非商業用途的寬鬆許可證。Boost 許可證與 GPL 相容。該專案維護著一個包含使用者文件、原始碼瀏覽和錯誤跟蹤的網站。它還運營著幾個郵件列表。

IAJAAR.H 專案

[編輯 | 編輯原始碼]

IAJAAR.H 是一個小型專案,它為程式設計師提供了一個名為IAJAAR的專用三元組 (3 元組) 容器類。該專案在 本地 進行了更詳細的描述。

使用 C++ 向量

[編輯 | 編輯原始碼]

一些使用者可能更喜歡使用來自 C++ 標準庫 的向量,而不是 C 樣式陣列。

例如,標準 GLPK 教程問題 可以重新編碼如下

// system headers
...
#include <vector>             // STL sequence container

// declare variables
glp_prob *lp;
std::vector<int>    iav;
std::vector<int>    jav;
std::vector<double> arv;
iav.reserve(1001);            // was: int ia[1+1000]
jav.reserve(1001);            // was: int ja[1+1000]
arv.reserve(1001);            // was: double ar[1+1000]
double Z, x1, x2, x3;
int solver_ret;               // exit status of solver
// create problem
lp = glp_create_prob();
glp_set_prob_name(lp, "example using std::vector");
glp_set_obj_dir(lp, GLP_MAX);
// fill problem
glp_add_rows(lp, 3);
...
glp_load_matrix(lp, 9, &iav[0], &jav[0], &arv[0]);  // was: glp_load_matrix(lp, 9, ia, ja, ar)
solver_ret = glp_simplex(lp, NULL);
...

這樣做的原因是 C++ 標準要求 std::vector 儲存在連續的記憶體中。為此,Josuttis (1999 p155) 這樣寫道:[3]

"這個例子表明,無論出於何種原因(例如用於現有的 C 庫)需要型別為 T 的陣列,都可以使用 std::vector<T> 並將第一個元素的地址傳遞給它"

如果您嘗試讀取或寫入超出範圍的資料,這種習慣用法會像 C 樣式陣列一樣肯定會失敗 - 但它會丟擲一個帶有更易於理解的錯誤訊息的 C++ 異常。如果您願意,甚至可以捕獲異常,然後更優雅地退出。例如

glp_prob *lp;
try
  {
    // most of the above code in here
  }
catch(const std::out_of_range& e)
  {
    std::cout << "e.what()" << std::endl;
    // any mopping up
  }
glp_delete_prob(lp);
glp_free_env();

不要在以後嘗試在向量中預留更多容量 - 重新分配會自動使所有引用、指標和迭代器失效!

如果在 C++11 標準下程式設計,可以使用初始化列表(以儲存所有 std::vector::push_back 呼叫)

// load the structural matrix data using initialization lists
std::vector<int>    iat = {  1,   1,   2,   2};
std::vector<int>    jat = {  1,   2,   1,   2};
std::vector<double> art = {1.0, 2.0, 3.0, 1.0};

// now prepend the required position zero placeholder (any value will do but zero is safe)
// first create length one vectors using default member construction
std::vector<int>    iav(1);
std::vector<int>    jav(1);
std::vector<double> arv(1);

// then concatenate these with the original data vectors
iav.insert(iav.end(), iat.begin(), iat.end());
jav.insert(jav.end(), jat.begin(), jat.end());
arv.insert(arv.end(), art.begin(), art.end());

// then load the structural matrix data
glp_load_matrix(lp, 9, &iav[0], &jav[0], &arv[0]);  // was: glp_load_matrix(lp, 9, ia, ja, ar)

需要使用 std::vector::insert 呼叫,因為 GLPK 從一開始就進行索引,而 C++ 從零開始索引。還要注意,std::deque 容器 保證連續使用記憶體。

參考文獻

[編輯 | 編輯原始碼]
  1. "#71 使用已棄用的 GLPK 庫函式". 檢索於 2010 年 12 月 28 日.
  2. Jüttner, Alpár; Dezső, Balázs; Kovács, Péter (2010) (PDF). LEMON : 網路中高效建模和最佳化的庫. http://lemon.cs.elte.hu/pub/doc/lemon-intro-presentation.pdf. 
  3. Josuttis, Nicolai M (1999). C++ 標準庫 : 教程和參考. 美國波士頓:Addison-Wesley. ISBN 0-201-37926-0.
華夏公益教科書