数理最適化セミナーのご案内

2.3 集合・添字

 実は,ここまでのモデルでは,次のように各油田について同じ日数制約を定義しているので,冗長な記述になっていると言えます.

$0 \leq x \leq 5$ 油田Xの週あたりの運転日数制約
$0 \leq y \leq 5$ 油田Yの週あたりの運転日数制約

 

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

集合
$OilField = \{0,1\}$ 油田集合
 
変数
$x_i$, $i \in OilField$ 油田iの運転日数/週
 
定数
${\rm costX}$ 油田0の運転コスト/日
${\rm costY}$ 油田1の運転コスト/日
 
目的関数(最小化)
${\rm costX} \cdot x_0 + {\rm costY} \cdot x_1$ 運転コスト/週
 
制約条件
$6x_0 + x_1 \geq 12$ 重油ノルマ/週
$4x_0 + 6x_1 \geq 24$ ガスノルマ/週
$0 \leq x_i \leq 5$, $\forall i \in OilField$ 油田iの週あたりの運転日数制約

 運転日数の制約を一行で書き表すことができました.

 対応するC++SIMPLEの記述は,次のようになります.

// 油田集合と添字の定義
Set OilField(name = "油田集合");
OilField = "0 1";
Element i(set = OilField);

// 油田 i の運転日数/週
Variable x(name = "油田の運転日数", index = i);

// 油田運転コスト/日
Parameter costX(name = "油田Xの運転コスト");
Parameter costY(name = "油田Yの運転コスト");

// 運転コスト/週(目的関数)
Objective cost(name = "全運転コスト", type = minimize);
cost = costX * x[0] + costY * x[1];

// 製品ノルマ
6 * x[0] + x[1] >= 12;     // 重油ノルマ
4 * x[0] + 6 * x[1] >= 24; // ガスノルマ

// 油田 i の週あたりの日数制約
0 <= x[i] <= 5;

// 求解
solve();

// 結果出力
x[i].val.print();
cost.val.print();

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

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

Set OilField(name = "油田集合");

 ここでは集合(油田の集合)を宣言しています.C++SIMPLEで添字を使用する場合は,まず添字の属する集合を宣言する必要があります.変数,目的関数,定数と同様に,name = "..."の部分には集合名を指定します.name = "..."は省略可能ですが,内容を出力する際などで使用されますので,記述したほうが良いでしょう.

OilField = "0 1";

 ここでは油田集合の内容を定義しています.先の定式化の添字範囲が{0, 1}なので,0, 1を集合の要素とします.

Element i(set = OilField);

 ここでは集合OilFieldの要素を表す添字iを宣言しています.set = ...で添字が属する集合を定義します.

Variable x(name = "油田の運転日数", index = i);

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

cost = costX * x[0] + costY * x[1];

 ここでは運転コストの内容定義をしています(制約式と内容定義は異なる点に注意してください).添字付けは,x[添字]と記述します.

// 製品ノルマ
6 * x[0] + x[1] >= 12;
4 * x[0] + 6 * x[1] >= 24;

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

// 日数制約
0 <= x[i] <= 5;

 ここでは日数制約を記述します.添字にiと指定することで,全ての${\rm i} \in OilField$に関する日数制約を,かけたことになります.

// 結果出力
x[i].val.print();

 結果出力も上記日数制約と同様に,添字にiと指定することで,全ての${\rm i} \in OilField$についてx[i]の値が出力されます.

 

 次に実行してみます(実行方法については「3.1利用方法」を参照してください).データファイルは「2.2定数」で使用した変更前のデータを使用してください.最適化経過が出力されたあと,x[i].val.print()に対応した,以下の出力が得られます.

油田の運転日数[0]=1.5
油田の運転日数[1]=3

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

 

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

定数
${\rm costX}_{\rm i}$, $i \in OilField$ 油田iの運転コスト/日

 ${\rm costX}_0$, ${\rm costX}_1$はそれぞれ以前の${\rm costX}$, ${\rm costY}$に対応する定数です.C++SIMPLEでも同様に定数の添字付けを用いて,以下のように修正します.

Parameter costX(name = "油田Xの運転コスト");
Parameter costY(name = "油田Yの運転コスト");
cost = costX * x[0] + costY * x[1];

      ↓

Parameter costX(name = "油田運転コスト", index = i);
cost = costX[0] * x[0] + costX[1] * x[1];

 定数の添字付けは,変数の添字付けと同様にindex = iと指定します.上記変更に合わせて,データファイルの内容を以下のように修正します.

"油田運転コスト" = [0] 180 [1] 160;

 添字付きの定数値を指定する右辺は,

[添字] 値 [添字] 値 ...

と記述します.

 

 では,実行してみましょう(実行方法については「3.1利用方法」を参照ください).最適化経過が出力された後,以下のように以前と同様の結果が得られます.

油田の運転日数[0]=1.5
油田の運転日数[1]=3
全運転コスト=750

 ここで,油田集合とその要素について考えます.上記のデータファイル中には,運転コストの添字として0, 1が記述されています.そしてC++SIMPLEの記述中で,運転コストの添字は油田集合の要素であると明示しています.以上より,C++SIMPLEはこのような油田集合の要素は0, 1からなると推定することができますので,実は,以下の油田集合の具体的な要素を与える記述は省略することができます.

OilField = "0 1";

 この記述を削除して実行してみますと,前回と同様の結果が得られるのが確認できます.

 このようにC++SIMPLEでは,添字と集合の関係から集合の内容を自動的に推定する機能があります.この機能を利用すれば集合の要素をC++SIMPLEで陽に記述する必要がなくなります.これにより,汎用的なモデル記述が可能となりますので,是非御活用ください.

 

 次に,重油とガスの生産ノルマの値を外部から与えることを考えます.定式化において製品集合を導入して製品ノルマを以下のように記述します.

集合
$Product = \{重油,ガス\}$ 製品集合
 
定数
$norma_j$, $j \in Product$ 製品jのノルマ/週

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

6 * x[0] + x[1] >= 12;
4 * x[0] + 6 * x[1] >= 24;

      ↓

Set Product(name = "製品集合");
Element j(set = Product);
Parameter norma(name = "製品ノルマ", index = j);
6 * x[0] + x[1] >= norma["重油"];
4 * x[0] + 6 * x[1] >= norma["ガス"];

 新たに製品集合の宣言を追加し,ノルマを製品を表す添字j付きの定数にします.上記のように文字列を添字に使用する場合は,文字列を"..."の中に記述する必要があります.次に,データファイルにノルマを与えるデータを追加しましょう.データファイルは以下のようになります.

"油田運転コスト" = [0] 180 [1] 160;
"製品ノルマ" = ["重油"] 12 ["ガス"] 24;

 C++SIMPLEの記述中で,製品ノルマの添字は製品集合の要素であると明示しています.このことから,C++SIMPLEは製品集合の要素は"重油","ガス"であると推定することができます.ゆえに,C++SIMPLEの記述中に製品集合の要素を書く必要はありません.このことは,油田集合の要素の推定と同様です.実行させると以前と同様の結果が得られます.

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

集合
$OilField = \{ 0,1\} $ 油田集合
$Product = \{重油,ガス\}$ 製品集合
 
定数
${\rm costX}_{\rm i}$, $i \in OilField$ 油田iの運転コスト/日
$norma_j$, $j \in Product$ 製品jのノルマ/週
 
変数
$x_i$, $i \in OilField$ 油田iの運転日数/週
 
目的関数(最小化)
${\rm costX}_0 \cdot x_0 + {\rm costX}_1 \cdot x_1$ 運転コスト/週
 
制約条件
$6x_0 + x_1 \geq norma_{重油}$ 重油ノルマ/週
$4x_0 + 6x_1 \geq norma_{ガス}$ ガスノルマ/週
$0 \leq x_i \leq 5$, $\forall i \in OilField$ 油田iの週あたりの運転日数制約
// 油田集合
Set OilField(name = "油田集合");
Element i(set = OilField);

// 製品集合
Set Product(name = "製品集合");
Element j(set = Product);

// 油田 i の運転コスト/日
Parameter costX(name = "油田運転コスト", index = i);

// 製品 j のノルマ/週
Parameter norma(name = "製品ノルマ", index = j);

// 油田 i の運転日数/週(変数)
Variable x(name = "油田の運転日数", index = i);

// 運転コスト/週(目的関数)
Objective cost(name = "全運転コスト", type = minimize);
cost = costX[0] * x[0] + costX[1] * x[1];

// 製品ノルマ
6 * x[0] + x[1] >= norma["重油"];     // 重油ノルマ/週
4 * x[0] + 6 * x[1] >= norma["ガス"]; // ガスノルマ/週

// 油田 i の週当りの運転日数制約
0 <= x[i] <= 5;

// 求解
solve();
// 結果出力
x[i].val.print();
cost.val.print();

 

 

上に戻る