ExcelとLpSolve
目的
最適化問題の解法としてよく用いられる混合整数計画法(MIP)。MIPはソルバーでも解くことができますが、ソルバーには決定変数の数が200まで(200変数を超えるとエラーになる)という制約があるので、無償で利用できるLpSolveをExcelから利用できるようにしておくとなにかと便利です。
要件
ExcelからLpSolveを呼び出して混合整数計画を解く
lp_solve_5.5.2.11_excel.zipおよびlp_solve_5.5.2.11_dev_win32.zipまたはlp_solve_5.5.2.11_dev_win64.zipをダウンロードしてdemo.xlsおよびlpsolve55.dllを入手する
https://sourceforge.net/projects/lpsolve/files/lpsolve/5.5.2.11/
demo.xlsとlpsolve55.dllは同じフォルダに格納する
使い方はdemo.xlsのプログラムソースやAPIリファレンスを参照する
https://lpsolve.sourceforge.net/5.5/lp_solveAPIreference.htm
ユーザインタフェース
demo.xlsのサンプルを少し変更して次のような混合整数計画問題を考えます。
決定変数:x1 , x2 , x3 , x4 , x5(非負)
目的関数:2 x1 + 3 x2 - 2 x3 + 3 x4 + 3 x5 → 最大化
制約条件
c1:0.5 x1 + 4 x2 + x3 + 2 x4 + 3 x5 >= 3
c2:x1 + 2 x2 + x3 + x4 + 2 x5 = 8
c3:4 x1 + x2 - 2 x3 + 2 x4 - 2 x5 <= 5
x4は整数、x5はバイナリ
x5 <= 5.3 , x2 >= 2
この問題を記述できるユーザインタフェースとして次のようなシートを用意します。水色ハッチング部分は変数名および制約条件名ですがおそらく日本語に対応していないので半角英数で記入します。黄色ハッチング部分には混合整数計画問題を解いたあとに最適化結果である決定変数や目的関数の値を入力します。
決定変数の列や制約条件の行を挿入すればある程度問題の拡張にも対応可能です。
プログラムソース
demo.xlsのサンプルソースはプログラム内に問題をべた書きされているので、変数の数や制約条件の数、最大化か最小化か、変数の上下限、変数のタイプ(整数やバイナリ)は画面から読み取るように組み込みます。cは変数の数のカウンタ、c1は制約の数のカウンタです。制約条件の結果も取得していますが、画面に書き込んではいません。
Option Explicit
Sub 混合整数計画法を解く()
Dim lpsolve As lpsolve55
Const 最大行 = 1048576
Const 最大列 = 16384
Dim i As Long
Dim j As Long
Dim c As Long
Dim c1 As Long
Dim t As String
Dim t1 As String
Dim a() As Double
Dim lp As Long
Dim obj As Double
Dim st As Long
Dim row() As Double
Dim col() As Double
Dim 目的行 As Long
Dim 結果行 As Long
Dim ws As Worksheet
Set ws = Sheets("問題")
ws.Activate
'変数のカウント------------------------------------------------------------
c = 0
For i = 2 To 最大列
If ws.Cells(1, i).value = "符号" Then
Exit For
Else
c = c + 1
End If
Next i
'lpsolveの初期化-----------------------------------------------------------
Set lpsolve = New lpsolve55
lpsolve.Init Application.ActiveWorkbook.Path
lp = lpsolve.make_lp(0, c)
'画面設定の読込------------------------------------------------------------
c1 = 0
For i = 1 To 最大行
t = ws.Cells(i, 1).value
t1 = ws.Cells(i, c + 2).value
If t = "" Then
Exit For
'変数名の設定
ElseIf t = "変数" Then
For j = 1 To c
lpsolve.set_col_name lp, j, ws.Cells(i, j + 1).value
Next j
'目的関数の設定
ElseIf t = "目的関数" Then
目的行 = i
ReDim a(0 To c) As Double
For j = 1 To c
a(j) = Val(ws.Cells(i, j + 1).value)
Next j
lpsolve.set_obj_fn lp, a(0)
'最大化する場合
If t1 = "max" Then
lpsolve.set_maxim lp
End If
'制約条件の設定(制約条件名の設定)
ElseIf InStr(t1, "=") > 0 Then
c1 = c1 + 1
ReDim a(0 To c) As Double
For j = 1 To c
a(j) = Val(ws.Cells(i, j + 1).value)
Next j
If t1 = ">=" Then
lpsolve.add_constraint lp, a(0), GE, Val(ws.Cells(i, c + 3).value)
ElseIf t1 = "=" Then
lpsolve.add_constraint lp, a(0), EQ, Val(ws.Cells(i, c + 3).value)
ElseIf t1 = "<=" Then
lpsolve.add_constraint lp, a(0), LE, Val(ws.Cells(i, c + 3).value)
End If
lpsolve.set_row_name lp, c1, ws.Cells(i, 1).value
'タイプの設定
ElseIf t = "タイプ" Then
For j = 1 To c
If ws.Cells(i, j + 1).value = "int" Then
lpsolve.set_int lp, j, True
ElseIf ws.Cells(i, j + 1).value = "bin" Then
lpsolve.set_binary lp, j, True
End If
Next j
'変数の上限設定
ElseIf t = "上限" Then
For j = 1 To c
If Val(ws.Cells(i, j + 1).value) > 0 Then
lpsolve.set_upbo lp, j, Val(ws.Cells(i, j + 1).value)
End If
Next j
'変数の下限設定
ElseIf t = "下限" Then
For j = 1 To c
If Val(ws.Cells(i, j + 1).value) > 0 Then
lpsolve.set_lowbo lp, j, Val(ws.Cells(i, j + 1).value)
End If
Next j
'結果行の取得
ElseIf t = "結果" Then
結果行 = i
End If
Next i
'タイムアウト設定
lpsolve.set_timeout lp, 0
'デバッグモードON
lpsolve.set_debug lp, True
'トレースモードOFF
lpsolve.set_trace lp, False
'結果ファイルの設定
lpsolve.set_outputfile lp, Application.ActiveWorkbook.Path & "\result.txt"
'結果ファイルへの問題の書き込み
lpsolve.print_lp lp
'最適化実行
st = lpsolve.solve(lp)
'目的関数の結果取得
obj = lpsolve.get_objective(lp)
'変数の結果取得
ReDim col(1 To lpsolve.get_Ncolumns(lp))
lpsolve.get_variables lp, col(1)
'最適解が求まったら
If st = 0 Then
'変数の結果書き込み
ReDim a(1 To c) As Double
ws.Range(Cells(結果行, 2), Cells(結果行, c + 1)).value = col
'目的関数の結果書き込み
ws.Cells(目的行, c + 3).value = obj
Else
MsgBox ("最適解が求まりませんでした")
End If
'制約条件の結果取得
ReDim row(1 To lpsolve.get_Nrows(lp))
lpsolve.get_constraints lp, row(1)
'結果ファイルへの書き込み
lpsolve.print_objective lp
lpsolve.print_solution lp, 1
lpsolve.print_constraints lp, 1
'lpファイルとmpsファイルの出力
lpsolve.write_lp lp, Application.ActiveWorkbook.Path & "\lp.lp"
lpsolve.write_mps lp, Application.ActiveWorkbook.Path & "\lp.mps"
End Sub
lpファイルを出力しておけばデバッグに役立ちます。
最後に
プログラムソースのコピペすら面倒くさいというものぐさな友人のためにExcelファイル添付しておきます。コーヒー1杯分で。笑
ここから先は
¥ 100
この記事が気に入ったらチップで応援してみませんか?