2.2.1. 目的関数・変数・制約

次のような生産計画問題を考えます.


2つの油田 X, Y が存在し,それぞれ一日あたり重油・ガスを次の量だけ生産する.

表 2.1 生産量/日

重油

ガス

X

6t

4t

Y

1t

6t

また,重油・ガスの週あたりの生産ノルマが,次のように定められている.

ノルマ/週

重油

12t

ガス

24t

油田 X, Y の日あたりの運転コストは,次のとおりである.

運転コスト/日

X

180

Y

160

油田 X, Y ともに,最大で週 5 日まで運転可能である. ノルマを満たしながら運転コストを最小化するためには,それぞれの油田を週あたり何日運転すれば良いだろうか?


この問題を定式化すると,以下のようになります.


\[\begin{split}\begin{array}{ll} \bf{変数} \\ \hline x & 油田 X の運転日数/週 \\ \hline y & 油田 Y の運転日数/週 \\ \hline \\ \bf{目的関数(最小化)} \\ \hline 180x + 160y & 運転コスト/週 \\ \hline \\ \bf{制約条件} \\ \hline 6x + y \ge 12 & 重油ノルマ/週 \\ \hline 4x + 6y \ge 24 & ガスノルマ/週 \\ \hline 0 \le x \le 5 & 油田Xの週あたりの運転日数制約 \\ \hline 0 \le y \le 5 & 油田Yの週あたりの運転日数制約 \\ \hline \end{array}\end{split}\]

それでは,この問題を PySIMPLE で記述した例を見てみましょう.:

from pysimple import Problem, Variable

problem = Problem(name='油田問題1')

# 油田X,Yの運転日数/週(変数)
x = Variable(name='油田Xの運転日数')
y = Variable(name='油田Yの運転日数')

# 運転コスト(目的関数)
problem += 180*x + 160*y, '全運転コスト'

# 製品ノルマ
problem += 6*x +   y >= 12, '重油ノルマ/週'
problem += 4*x + 6*y >= 24, 'ガスノルマ/週'

# 各油田の日数制約
problem += 0 <= x, '油田Xの週あたりの運転日数制約(下限)'
problem += x <= 5, '油田Xの週あたりの運転日数制約(上限)'
problem += 0 <= y, '油田Yの週あたりの運転日数制約(下限)'
problem += y <= 5, '油田Yの週あたりの運転日数制約(上限)'

# 求解
print(problem)
problem.solve()

# 結果出力
print(x.val)
print(y.val)
print(problem.objective.val)

この PySIMPLE による記述を上から順に見ていきましょう.:

from pysimple import Problem, Variable

この部分はモデリングに必要なクラスや関数を使える状態にしています. PySIMPLE でモデリングを行う前には使用するオブジェクトを使える状態にしておく必要があります. インポートできるオブジェクト一覧やインポート方法はさまざまですが,ここでは一旦先に進むことにします.:

problem = Problem(name='油田問題1')

この部分は問題の宣言です.name='..' には問題の名前を指定します. name='..' は省略可能ですが,出力などで使用されますので,なるべく記述した方が良いでしょう.

また,ここでは記述がありませんが,type=.. で目的関数の最小化・最大化を指定することができます. 指定する場合は type=mintype=max のように記述します.省略した場合は最小化となります.:

# 油田X,Yの運転日数/週(変数)
x = Variable(name='油田Xの運転日数')
y = Variable(name='油田Yの運転日数')

この部分は変数(油田の運転日数)の宣言です. モデル中で使用する変数は,使用する前に宣言する必要があります. name='..' には変数の名前を指定しますが省略可能です.

# から行の終わりまではコメントです.

# 運転コスト(目的関数)
problem += 180*x + 160*y, '全運転コスト'

この部分は目的関数(運転コスト)の内容を問題に設定しています. += の左辺に問題を,右辺に目的関数とその名前を記述します.目的関数の名前は省略可能です.

* は積,+ は和を表す演算子です. PySIMPLE では四則演算や数学関数(Sum(), Exp(), ..)などを式の記述に用いることができます.

# 製品ノルマ
problem += 6*x +   y >= 12, '重油ノルマ/週'
problem += 4*x + 6*y >= 24, 'ガスノルマ/週'

この部分では制約式(生産ノルマ)を問題に設定しています. += の左辺に問題を,右辺に制約式とその名前を記述します.制約式の名前は省略可能です.

関係演算子 >= の左辺,右辺には,任意の式を記述できます. 目的関数の内容定義の際と同様に,任意の式の中に演算子や数学関数を記述できます. 左辺と右辺の関係を表す関係演算子には,>=, <=, == を指定できます.:

# 各油田の日数制約
problem += 0 <= x, '油田Xの週あたりの運転日数制約(下限)'
problem += x <= 5, '油田Xの週あたりの運転日数制約(上限)'
problem += 0 <= y, '油田Yの週あたりの運転日数制約(下限)'
problem += y <= 5, '油田Yの週あたりの運転日数制約(上限)'

この部分は制約式(運転日数の上下限)を設定しています. problem += 0 <= x <= 5 のように一度に記述することはできません. 変数の上下限は宣言時に下限を lb, 上限を ub で記述することもできます. 上記の制約式を記述する代わりに変数を宣言するときに次のように記述します.:

x = Variable(lb=0, ub=5, name='油田Xの運転日数')
y = Variable(lb=0, ub=5, name='油田Yの運転日数')

以上で,問題の定義の記述は完了です.

次に,これまでに定義した問題の最適解を求め,結果を出力する部分を記述します.:

# 求解
print(problem)
problem.solve()

print(問題) は問題に設定された情報を出力します. 問題.solve() は定義した問題について最適解の計算を行う関数です. 問題.solve() は,必ずモデル記述の後に記述する必要があります.

# 結果出力
print(x.val)
print(y.val)
print(problem.objective.val)

この部分は,最適化計算結果の出力を指定しています. 変数や式に .val を付けることでその現在値を取り出すことができます. 問題の目的関数は 問題.objective で参照できます. 最適化計算後の値を出力するためには,最適化計算 solve の後に記述する必要があります.

以上でこのモデルについての PySIMPLE の記述は終了です.

次にこのモデルを実行してみます(実行方法については 数理計画問題を解く を参照してください). すると,数理計画モデルを解く経過が,以下のように出力されます.

Problem(name='油田問題1', type=min):
[constraints]
重油ノルマ/:
6*油田Xの運転日数+油田Yの運転日数>=12
ガスノルマ/:
4*油田Xの運転日数+6*油田Yの運転日数>=24

[objective]
全運転コスト:
180*油田Xの運転日数+160*油田Yの運転日数


[About Nuorium Optimizer]
Nuorium Optimizer 26.1.0 build:f2d78da8
         <with META-HEURISTICS engine "wcsp"/"rcpsp">
         <with Netlib BLAS>
, Copyright (C) 1991 NTT DATA Mathematical Systems Inc.

[Problem and Algorithm]
PROBLEM_NAME                                        油田問題1
NUMBER_OF_VARIABLES                                         2
NUMBER_OF_FUNCTIONS                                         3
PROBLEM_TYPE                                     MINIMIZATION
METHOD                                           HIGHER_ORDER

[Progress]
<preprocess begin>.........<preprocess end>
<iteration begin>
    res=1.2e+02 .... 4.1e-06  2.1e-08
<iteration end>

[Result]
STATUS                                                OPTIMAL
VALUE_OF_OBJECTIVE                                        750
ITERATION_COUNT                                             6
FUNC_EVAL_COUNT                                             9
FACTORIZATION_COUNT                                         7
RESIDUAL                                      2.072696939e-08
ELAPSED_TIME(sec.)                                       0.02
油田Xの運転日数.val=1.500000000256067
油田Yの運転日数.val=2.9999999999710107
全運転コスト.val=750.0000000414537

最後に結果出力に対応する結果が以下のように出力されます.:

油田Xの運転日数.val=1.500000000256067
油田Yの運転日数.val=2.9999999999710107
全運転コスト.val=750.0000000414537

+= の左辺は指定した変数と目的関数の名前で,name='..' に記述したものが出力されます. 右辺には変数と目的関数の値が出力されています.

また,このモデルは PySIMPLE のサンプルとして同梱されています. このサンプルを実行するには次のようにします.:

$ python -m pysimple.sample.tutorial oil1