2.2.3. 集合・添字

油田運転日数を一般的に記述することを考えてみましょう. まず油田運転日数 \(x\), \(y\) をそれぞれ \(x_0\), \(x_1\) と変更し,定式化を次のように変更します.


\[\begin{split}\begin{array}{ll} \bf{集合,添字} \\ \hline i \in OilField = \{ 0, 1 \} & 油田集合,添字 \\ \hline \\ \bf{変数} \\ \hline x_i, \quad i \in OilField & 油田iの運転日数/週 \\ \hline \\ \bf{定数} \\ \hline costX & 油田0の運転コスト/日 \\ \hline costY & 油田1の運転コスト/日 \\ \hline \\ \bf{目的関数} \\ \hline costX \cdot x_0 + costY \cdot x_1 & 運転コスト/週 \\ \hline \\ \bf{制約条件} \\ \hline 6x_0 + x_1 \ge 12 & 重油ノルマ/週 \\ \hline 4x_0 + 6x_1 \ge 24 & ガスノルマ/週 \\ \hline 0 \le x_i \le 5, \quad \forall i \in OilField & 油田iの週あたりの運転日数制約 \\ \hline \end{array}\end{split}\]

対応する PySIMPLE の記述は,次のようになります.:

from pysimple import Problem, Element, Parameter, Variable

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

# 油田を表す添字
i = Element(value=[0, 1], name='油田')

# 油田 i の運転コスト/日
costX = Parameter(value=180, name='油田Xの運転コスト')
costY = Parameter(value=160, name='油田Yの運転コスト')

# 油田 i の運転日数(変数)
x = Variable(lb=0, ub=5, index=i, name='油田の運転日数')

# 運転コスト/週(目的関数)
problem += costX*x[0] + costY*x[1], '全運転コスト'

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

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

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

定式化と同様,日数制約を一度に書き表しています.

それでは,PySIMPLE の記述の変更・追加点について,上から順に見ていきます.:

# 油田を表す添字
i = Element(value=[0, 1], name='油田')

ここでは添字(油田の添字)を宣言しています. value=.. には添字がとる要素の列を指定します. ここでは先の定式化の添字範囲が {0, 1} なので,0, 1 を添字の範囲とします. name='..' には添字の名前を指定しますが省略可能です.

C++SIMPLE のように集合を作成した後に,添字を作成することもできます.:

# 油田集合
OilField = Set(value=[0, 1], name='油田集合')
i = Element(set=OilField)

Set を用いる場合には Element と同様にインポートが必要になります.:

x = Variable(lb=0, ub=5, index=i, name='油田の運転日数')

ここでは油田の運転日数を,添字付き変数として宣言しています.index=.. で添字を指定します.

# 運転コスト/週(目的関数)
problem += costX*x[0] + costY*x[1], '全運転コスト'

ここでは運転コストの内容定義をしています.添字付けは,x[添字] と記述します.:

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

ここでは製品ノルマの制約を記述しています. 以前に x, y と書いた変数部分を x[0], x[1] と置き換えただけです.

# 結果出力
print(x.val)

結果出力も上記日数制約と同様に,添字に i と指定することで, 全ての \(i \in OilField\) について x[i] の値が出力されます. ここでは print(x.val) としていますが,print(x[i].val) でも同じ効果があります.

次に実行してみます(実行方法については 数理計画問題を解く を参照してください). 最適化経過が出力されたあと,print(x) に対応した,以下の出力が得られます.

油田の運転日数[0].val=1.500000000256067
油田の運転日数[1].val=2.9999999999710107

変数名が添字つきで出力されているのが確認できます.

ここまでの記述の変更で,添字 i を導入し,各油田の運転日数を x[i] と簡略化することができました. 次に,油田運転コスト costX, costY も添字 i を用いて簡略化してみます. 運転コストを添字付けし,以下のように表すことにします.


\[\begin{split}\begin{array}{ll} \bf{定数} \\ \hline costX_i, i \in OilField & 油田iの運転コスト/日 \\ \hline \end{array}\end{split}\]

\(costX_0\), \(costX_1\) はそれぞれ以前の \(costX\), \(costY\) に対応する定数です. PySIMPLE でも同様に定数の添字付けを用いて,以下のように修正します.:

# 油田 i の運転コスト/日
costX = Parameter(value=180, name='油田Xの運転コスト')
costY = Parameter(value=160, name='油田Yの運転コスト')

# 運転コスト/週(目的関数)
problem += costX*x[0] + costY*x[1], '全運転コスト'

# 油田 i の運転コスト/日
costX = Parameter(index=i, value={0: 180, 1: 160}, name='油田運転コスト')

# 運転コスト/週(目的関数)
problem += costX[0]*x[0] + costX[1]*x[1], '全運転コスト'

定数の添字付けは,変数の添字付けと同様に index=i と指定します. 定数の値を要素ごとに指定する場合は Python の辞書で指定します.

では,実行してみましょう(実行方法については 数理計画問題を解く を参照してください). 最適化経過が出力された後,以下のように以前と同様の結果が得られます.:

油田の運転日数[0].val=1.500000000256067
油田の運転日数[1].val=2.9999999999710107

次に,重油とガスの製品についても一般に記述してみましょう. 定式化において製品集合を導入して製品ノルマを以下のように記述します.


\[\begin{split}\begin{array}{ll} \bf{集合} \\ \hline Product = \{ 重油, ガス \} & 製品集合 \\ \hline \\ \bf{定数} \\ \hline norma_j, \quad j \in Product & 製品jのノルマ/週 \\ \hline \end{array}\end{split}\]

PySIMPLE の記述においても同様に定数の添字付けを用いて表現し,ノルマに関する制約式を以下のように変更します.:

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

# 製品を表す添字
j = Element(value=['重油', 'ガス'], name='製品')

# 製品 j のノルマ/週
norma = Parameter(index=j, value={'重油': 12, 'ガス': 24}, name='製品ノルマ')

# 製品ノルマ
problem += 6*x[0] +   x[1] >= norma['重油'], '重油ノルマ/週'
problem += 4*x[0] + 6*x[1] >= norma['ガス'], 'ガスノルマ/週'

新たに製品を表す添字 j の宣言を追加し,製品ノルマを添字 j 付きの定数にします. 上記のように文字列を添字に使用する場合は,文字列を '..' の中に記述する必要があります.

ここまでの変更をまとめて,集合,変数,定数,制約条件,目的関数を分類し整理すると, 定式化と PySIMPLE の記述は次のようになります.


\[\begin{split}\begin{array}{ll} \bf{集合,添字} \\ \hline i \in OilField = \{ 0, 1 \} & 油田集合,添字 \\ \hline j \in Product = \{ 重油, ガス \} & 製品集合,添字 \\ \hline \\ \bf{定数} \\ \hline costX_i, \quad i \in OilField & 油田iの運転コスト/日 \\ \hline norma_j, \quad j \in Product & 製品jのノルマ/週 \\ \hline \\ \bf{変数} \\ \hline x_i, \quad i \in OilField & 油田iの運転日数/週 \\ \hline \\ \bf{目的関数(最小化)} \\ \hline costX_0 \cdot x_0 + costX_1 \cdot x_1 & 運転コスト/週 \\ \hline \\ \bf{制約条件} \\ \hline 6x_0 + x_1 \ge norma_{重油} & 重油ノルマ/週 \\ \hline 4x_0 + 6x_1 \ge norma_{ガス} & ガスノルマ/週 \\ \hline 0 \le x_i \le 5, \quad \forall i \in OilField & 油田iの週あたりの運転日数制約 \\ \hline \end{array}\end{split}\]

from pysimple import Problem, Element, Parameter, Variable

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

# 油田を表す添字
i = Element(value=[0, 1], name='油田')
## 油田集合
#OilField = Set(value=[0, 1], name='油田集合')
#i = Element(set=OilField)

# 製品を表す添字
j = Element(value=['重油', 'ガス'], name='製品')
## 製品集合
#Product = Set(value=['重油', 'ガス'], name='製品集合')
#j = Element(set=Product)

# 油田 i の運転コスト/日
costX = Parameter(index=i, value={0: 180, 1: 160}, name='油田運転コスト')

# 製品 j のノルマ/週
norma = Parameter(index=j, value={'重油': 12, 'ガス': 24}, name='製品ノルマ')

# 油田 i の運転日数(変数)
x = Variable(lb=0, ub=5, index=i, name='油田の運転日数')

print(costX)
print(norma)

# 運転コスト/週(目的関数)
problem += costX[0]*x[0] + costX[1]*x[1], '全運転コスト'

# 製品ノルマ
problem += 6*x[0] +   x[1] >= norma['重油'], '重油ノルマ/週'
problem += 4*x[0] + 6*x[1] >= norma['ガス'], 'ガスノルマ/週'

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

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

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

$ python -m pysimple.sample.tutorial oil3