1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CPLEXサンプル 配合 1を読み解く

Last updated at Posted at 2024-09-02

CPLEXのサンプルの配合 I 食品製造 1(CPLEX OPL CPLEXサンプル MIP)を読み解きます。

<サンプル導入ディレクトリ>opl\examples/opl/models/BlendingMultiPeriod
にあります。

  • 実行環境
    • CPLEX 22.1.1
    • Windows 11 64bit

問題

このモデルでは、「Model Building in Mathematical Programming」(H.P. Williams 著) の Food manufacture 1を題材としています。多期間に渡る配合の問題です。

6種類の粗油を毎月何トン仕入れ、何トン保管するか、その組合せでマーガリンを毎月何トン製造するかを決定します。最大の粗利が出るようにします。
6種類の粗油の組合せによってマーガリンの硬度が変わりますが、3-6の硬度で作る必要があります。
また、毎月各粗油の仕入れ値は変動します。また保管には固定費がかかります。
image.png

配合 1 の例の特性は次のとおりです。

業種: 製造
機能: OPL モデル
技法: 線形計画法
複雑さ: 基本
キーワード: 配合、製造、食品、多期間
プロジェクトの場所: examples/opl/models/BlendingMultiPeriod
モデル名: blending1.mod

  • サンプル

●コード

この記事でのサンプルは、オリジナルのサンプルとほぼ同じ内容ですが、日本語コメントを入れたり順序を入れ替えたり、添え字を入れ替えたりして読みやすくしています

●サンプルデータ

利用データ

  1. 各粗油の仕入値/月
1 2 3 4 5 6
Veg1 110 130 110 120 100 90
Veg2 120 130 140 110 120 100
Oil1 130 110 130 120 150 140
Oil2 110 90 100 120 110 80
Oil3 115 115 95 125 105 135

2.各粗油の硬度

粗油 硬度
Veg1 8.8
Veg2 6.1
Oil1 2
Oil2 4.2
Oil3 5

3. その他の情報

データ
製品利益 150
植物油の最大生産量/月 200
非植物油の最大生産量/月 250
保管コスト/月 5
粗油の初期保管量と最終月保管量 500
各原料油の最大保管量 1000

決定変数

  1. 各粗油の各月の購入量
  2. 各粗油の各月の保管量
  3. 各粗油の各月の利用量
  4. 各月の製品生産量

目的関数

全製品利益=製品生産量*製品利益
全製品コスト=各粗油の仕入値*購入量+保管量*保管コスト)

総利益=全製品利益-全製品コスト

制約

  1. 在庫量が最大保管量以下
  2. 植物油と非植物の月あたりの最大使用量以下
  3. 1トンあたりの硬度は3以上6以下
  4. 利用量と生産量が等しい
  5. 前月在庫量+購入量と利用量+当月在庫量が等しい
  6. 初期在庫量と最終在庫量は固定値

問題の種類

混合整数計画

先に解を紹介します。

目的関数

総利益の合計:107,840

決定変数

1. 各粗油の各月の購入量

1 2 3 4 5 6
Veg1 0 0 0 0 0 659
Veg2 0 0 0 0 0 541
Oil1 0 0 0 0 0 0
Oil2 0 750 0 0 0 750
Oil3 0 0 0 0 0 0

2. 各粗油の各月の保管量

初期在庫 1 2 3 4 5 6
Veg1 500 477 477 318 159 0 500
Veg2 500 323 123 82 41 0 500
Oil1 500 500 500 500 500 500 500
Oil2 500 250 750 500 250 0 500
Oil3 500 500 500 500 500 500 500

3.各粗油の各月の利用量

1 2 3 4 5 6
Veg1 23 0 159 159 159 159
Veg2 177 200 41 41 41 41
Oil1 0 0 0 0 0 0
Oil2 250 250 250 250 250 250
Oil3 0 0 0 0 0 0

4.各月の製品生産量

生産量
1 450
2 450
3 450
4 450
5 450
6 450

OPLコード解説

OPLコードの解説を行っていきます。

利用データ

まず製品名とプロセス名のデータの定義を行っています。
データの中身の定義はblending1hkwd.datファイルの中で行っています。

粗油名の定義(mod)
// 各粗油名
{string} Raw = ...;
粗油名の定義(dat)
// 各粗油名
Raw = {Veg1, Veg2, Oil1, Oil2, Oil3};

生産月数として1月-6月を定義しています。
Rは1月-6月に加えて、0月も定義しています。これは在庫テーブルの月の情報です。在庫に関しては制約を1月と2月以降で別のものを定義しなくてよいようにというテクニカルな理由と、1月以前に在庫があった場合にも対応できるようにするという意図があります。

生産月数(mod)
// 生産月数
int NbMonths = ...;
range Months = 1..NbMonths;
// 初期在庫+月。在庫の制約を最初の月とそれ以降で分けないため
range R = 0..NbMonths;
生産月数(dat)
// 生産月数
NbMonths = 6;

粗油と製品関係のデータを定義しています。

粗油と製品関係(mod)
// 各粗油の仕入値/月
float CostRaw[Raw][Months] = ...;
// 各粗油の硬度
float HardRaw[Raw] = ...;
// 製品利益
float ProfitProd = ...;
// 植物油かそれ以外かの識別フラグ
float IsVeg[Raw] = ...;
float IsOil[Raw] = ...;
// 植物油の最大生産量/月
float MaxVeg = ...;
// 非植物油の最大生産量/月
float MaxOil = ...;
粗油と製品関係(dat)
// 各粗油の仕入値/月
CostRaw = [[110 130 110 120 100  90]
           [120 130 140 110 120 100]
           [130 110 130 120 150 140]
           [110  90 100 120 110  80]
           [115 115  95 125 105 135]];
// 各粗油の硬度
HardRaw = [8.8 6.1 2 4.2 5];
// 植物油かそれ以外かの識別フラグ
IsVeg = [1 1 0 0 0];
IsOil = [0 0 1 1 1];
// 製品利益
ProfitProd = 150;
// 植物油の最大生産量/月
MaxVeg = 200;
// 非植物油の最大生産量/月
MaxOil = 250;

問題ブラウザで見ると以下のようになっています。
image.png
image.png
IsVegとIsOilは、植物油と非植物油を見分けるためのフラグになっています。
image.png
image.png

保管系データの定義です。

保管系データ(mod)
// 保管コスト/月
float CostStore = ...;
// 粗油の初期保管量と最終月保管量
float StartEndStore = ...;
// 各原料油の最大保管量
int MaxStore = ...;
保管系データ(dat)
// 保管コスト/月
CostStore = 5;
// 粗油の初期保管量と最終月保管量
StartEndStore = 500;
// 各原料油の最大保管量
MaxStore = 1000;

決定変数

次に決定変数の定義を行っています。
dvarが決定変数を意味します。

Storeのy軸はMonthsではなくRになっています。0月が初期在庫になります。
Storeは「制約1:在庫数が最大保管量以下」を一緒に定義しています。

決定変数
//// 決定変数
// 購入量[各粗油,月]
dvar int+ Buy[Raw][Months];
// 保管量[各粗油,月] 制約1:最大保管量以下
dvar int+ Store[Raw][R] in 0..MaxStore;
// 利用量[各粗油,月]
dvar int+ Use[Raw][Months];
// 製品生産量[月]
dvar int+ p[Months];

これらの決定変数は、求められた解では以下のようになりました。
image.png
image.png
image.png
image.png

目的関数

次に目的関数の定義を行っています。

関数の定義に先立って、決定式のコスト(Cost)、利益(Profit)、粗利益(GrossProfit)の決定関数を作ります。

sumキーワードは関連式の集合の合計値を計算するキーワードです。
各粗油の仕入値(CostRaw)は粗油毎月毎に異なります。
製品利益(ProfitProd)在庫コスト(CostStore)は粗油と毎月共通です。

決定式のコスト(Cost)、利益(Profit)、粗利益
// 利益=製品利益*製品生産量
dexpr float Profit = 
  sum (m in Months) ProfitProd * p[m];
// コスト=各粗油の仕入値*購入量+保管コスト*保管量
dexpr float Cost =
  sum(j in Raw, m in Months)
         (CostRaw[j][m] * Buy[j][m] + CostStore * Store[j][m]);
// 粗利=利益-コスト
dexpr float GrossProfit = Profit - Cost;

図にすると以下のようになります。

image.png
image.png

決定関数として定義した粗利益を最大化することが目的関数になります。

目的関数 粗利益の最大化
maximize GrossProfit;

制約

次に制約の定義を行っています。
subject toの中カッコで囲みます。

制約(mod)
subject to{

-------
}

ctXXX:で制約にラベルを付けています。

以下の制約は決定変数の定義の中で定義済みです。
制約1:在庫量が最大保管量以下

制約2: 植物油と非植物油は月あたりの最大使用量以下

forall(m in Months)で月数分の制約を作っています。
「植物油の識別フラグ(IsVeg)」 * 「使用量(Use)」で「植物油の使用量」を計算し、それがMaxVeg以下である条件になっています。
非植物油についても同様の条件をつくっています。

制約2:植物油と非植物油は月あたりの最大使用量以下
ctMaxUseVeg: sum(j in Raw) IsVeg[j] * Use[j][m] <= MaxVeg;
ctMaxUseOil: sum(j in Raw) IsOil[j] * Use[j][m] <= MaxOil;

問題ブラウザで見ると以下のようになります。スラックは0ですので、全月で植物油と非植物油の使用量限界まで使っていることがわかります。
image.png
image.png

図にすると以下のようなイメージになります。
image.png
image.png

制約3:1トンあたりの硬度は3以上6以下

forall(m in Months)で月数分の制約を作っています。

右辺では、「各粗油の硬度(HardRaw)」 * 「使用量(Use)」で製造したマーガリンの総硬度を出しています。

左辺は3 *「生産量(p)」以上、6 * 「生産量(p)」以下の硬度であることを条件にしています。

制約3:1トンあたりの硬度は3以上6以下
ctHard1: sum(j in Raw) HardRaw[j] * Use[j][m]   >= 3 * p[m];
ctHard2: sum(j in Raw) HardRaw[j] * Use[j][m]   <= 6 * p[m];

問題ブラウザで見ると以下のようになります。
image.png
image.png

図にすると以下のようなイメージになります。
image.png
image.png

これは本当は以下のように、左辺を「p[m]」で割ってあげた方1トン当たりの硬度になるので、人間の可読性は高まります。しかしながら、決定変数を分母に割り算をすると線形の問題にならなくなるので、「p[m]」は右辺で掛け算にしています。

制約3:1トンあたりの硬度は3以上6以下
ctHard1: sum(j in Raw) HardRaw[j] * Use[j][m]/p[m]  >= 3 * ;
ctHard2: sum(j in Raw) HardRaw[j] * Use[j][m]/p[m]   <= 6 * ;

参考:決定変数を分母にした割合で制約を作る

制約4:利用量と生産量が等しい

forall(m in Months)で月数分の制約を作っています。
すべての粗油の「使用量(Use)」==「生産量(p)」という条件です。

制約4:利用量と生産量が等しい
ctMatBal: sum(j in Raw) Use[j][m] == p[m];

問題ブラウザで見ると以下のようになります。
5月はスラックが0ではありませんが、e-14であり誤差です。
image.png

図にすると以下のようなイメージになります。
image.png

制約5:前月在庫量+購入量と利用量+当月在庫量が等しい

forall(j in Raw, m in Months)で粗油分*月数分の制約を作っています。
前月在庫量+購入量と利用量+当月在庫量が一致するという流量保存の制約です。

制約5:前月在庫量+購入量と利用量+当月在庫量が等しい
forall(j in Raw, m in Months)
  ctInvVal: Store[j][m-1] + Buy[j][m] == Use[j][m] + Store[j][m];

問題ブラウザで見ると以下のようになります。
image.png

図にすると以下のようなイメージになります。
image.png

制約6:初期在庫量と最終在庫量は固定値

forall(j in Raw) で粗油数分の制約を作っています。
初期在庫(Store[j][0])を500(StartEndStore)に固定している。
最終月在庫(Store[j][NbMonths])を500(StartEndStore)に固定している。

制約6:初期在庫量と最終在庫量は固定値
forall(j in Raw) {
  ctStartInv: Store[j][0] == StartEndStore;
  ctEndInv: Store[j][NbMonths] == StartEndStore;
}

問題ブラウザで見ると以下のようになります。
image.png
image.png

図にすると以下のようなイメージになります。
image.png

結果の表示

解の決定変数を表示します。
plan[m][j] は、各粗油 j を毎月 m にどれだけ購入、使用、保管するかを出力します。

結果の表示(mod)
execute DISPLAY {
   for (var m in Months)
      for (var j in Raw)
         writeln("plan[",m,"][",j,"] = <buy:",Buy[j][m],",use:",Use[j][m],",store:",Store[j][m],">");

スクリプト・ログのビューに出力されます。

image.png

完成OPL

製品サンプルとほぼ同じ内容ですが、日本語コメントを入れたり順序を入れ替えたり、添え字を入れ替えたりして読みやすくしています。

blending1hkwd.mod
// 食品製造 1

// 各粗油名
{string} Raw = ...;

// 生産月数
int NbMonths = ...;
range Months = 1..NbMonths;
// 初期在庫+月。在庫の制約を最初の月とそれ以降で分けないため
range R = 0..NbMonths;

// 各粗油の仕入値/月
float CostRaw[Raw][Months] = ...;
// 各粗油の硬度
float HardRaw[Raw] = ...;
// 製品利益
float ProfitProd = ...;
// 植物油かそれ以外かの識別フラグ
float IsVeg[Raw] = ...;
float IsOil[Raw] = ...;
// 植物油の最大生産量/月
float MaxVeg = ...;
// 非植物油の最大生産量/月
float MaxOil = ...;


// 保管コスト/月
float CostStore = ...;
// 粗油の初期保管量と最終月保管量
float StartEndStore = ...;
// 各原料油の最大保管量
int MaxStore = ...;


//// 決定変数
// 購入量[各粗油,月]
dvar int+ Buy[Raw][Months];
// 保管量[各粗油,月] 制約1:最大保管量以下
dvar int+ Store[Raw][R] in 0..MaxStore;
// 利用量[各粗油,月]
dvar int+ Use[Raw][Months];
// 製品生産量[月]
dvar int+ p[Months];


// 利益=製品利益*製品生産量
dexpr float Profit = 
  sum (m in Months) ProfitProd * p[m];
// コスト=各粗油の仕入値*購入量+保管コスト*保管量
dexpr float Cost =
  sum(j in Raw, m in Months)
         (CostRaw[j][m] * Buy[j][m] + CostStore * Store[j][m]);
// 粗利=利益-コスト
dexpr float GrossProfit = Profit - Cost;

// 目的関数 粗利益の最大化
maximize GrossProfit;


// 制約
subject to {

  forall(m in Months) {
      
    // Maximum usage per month   
    // 制約2:植物油と非植物油の月あたりの最大使用量以下
    ctMaxUseVeg: sum(j in Raw) IsVeg[j] * Use[j][m] <= MaxVeg;
    ctMaxUseOil: sum(j in Raw) IsOil[j] * Use[j][m] <= MaxOil;
     
    // Hardness constraints
    // 制約3:1トンあたりの硬度は3以上6以下
    //ctHard1: sum(j in Raw) HardRaw[j] * Use[j][m] - 6 * p[m] <= 0;
    //ctHard2: sum(j in Raw) HardRaw[j] * Use[j][m] - 3 * p[m] >= 0;
    ctHard1: sum(j in Raw) HardRaw[j] * Use[j][m]   >= 3 * p[m];
    ctHard2: sum(j in Raw) HardRaw[j] * Use[j][m]   <= 6 * p[m];
    //ctHard1: sum(j in Raw) HardRaw[j] * Use[j][m] / (p[m]*1.0) >= 3;
    //ctHard2: sum(j in Raw) HardRaw[j] * Use[j][m] / (p[m]*1.0) <= 6;

    // Material balance constraints
    // 制約4:利用量と生産量が等しい
    //ctMatBal: sum(j in Raw) Use[j][m] - p[m] == 0;
    ctMatBal: sum(j in Raw) Use[j][m] == p[m];
  }

  // Inventory balance
  // 制約5:前月在庫量+購入量と利用量+当月在庫量が等しい
  forall(j in Raw, m in Months)
    ctInvVal: Store[j][m-1] + Buy[j][m] == Use[j][m] + Store[j][m];

  // Starting and ending inventories are fixed
  // 制約6:初期在庫量と最終在庫量は固定値
  forall(j in Raw) {
    ctStartInv: Store[j][0] == StartEndStore;
    ctEndInv: Store[j][NbMonths] == StartEndStore;
  }
}


//Display the plan for each month and each raw material
//plan[m][j] = <Buy[j][m], Use[j][m], Store[j][m]>
execute DISPLAY {
   for (var m in Months)
      for (var j in Raw)
         writeln("plan[",m,"][",j,"] = <buy:",Buy[j][m],",use:",Use[j][m],",store:",Store[j][m],">");
}

データは以下です。

blending1hkwd.dat
// 各粗油名
Raw = {Veg1, Veg2, Oil1, Oil2, Oil3};
// 生産月数
NbMonths = 6;

// 各粗油の仕入値/月
CostRaw = [[110 130 110 120 100  90]
           [120 130 140 110 120 100]
           [130 110 130 120 150 140]
           [110  90 100 120 110  80]
           [115 115  95 125 105 135]];
// 各粗油の硬度
HardRaw = [8.8 6.1 2 4.2 5];
// 植物油かそれ以外かの識別フラグ
IsVeg = [1 1 0 0 0];
IsOil = [0 0 1 1 1];
// 製品利益
ProfitProd = 150;
// 植物油の最大生産量/月
MaxVeg = 200;
// 非植物油の最大生産量/月
MaxOil = 250;

// 保管コスト/月
CostStore = 5;
// 粗油の初期保管量と最終月保管量
StartEndStore = 500;
// 各原料油の最大保管量
MaxStore = 1000;

参考

製造:配合 I

CPLEXサンプルを読み解く記事一覧

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?