#27 最適化計算の実行

前回の投稿(#26)で、最適化計算のライブラリPyomoで、変数をベクトル化してみたことを報告しました。

https://note.com/toshiyamiyazaki/n/nd8dbf61b1b02

今回はその続き。
実際に最適化計算を行いました。
まずは、コード全体を掲載します。

import pyomo.environ as pyo
from pyomo.opt import SolverFactory

# ConcreteModelクラスを用いて問題のオブジェクトを作成
model = pyo.ConcreteModel()
# リストで指定した内容をキーワードとして保持
df_index = df_price_per_ton.index
NUM = len(df_index)
model.ITEMS = list(df_index)
uu = {df_index[i]:df_price_per_ton.iat[i, 0] for i in range(NUM)}
def initializeU(model, i):
   '''変数の初期化'''
   return uu[i]
model.u = pyo.Var(model.ITEMS, domain=pyo.NonNegativeReals, initialize=initializeU)

for x in range(len(df_index)):
   # 投入量を計算
   input = 0 # 投入分
   output = 0 # 産出分
   fuel = 0 # 燃料使用分
   waste = 0 # 廃棄物発生量
   for i in range(187):
       for k in model.ITEMS:
           if k == '0131': # 0131(農業サービス)からの投入は考慮しない
               continue
           if k == df_iotable.index[i]:
               input += df_iotable.iat[i, x] * 1000000 / model.u[k]
               # 燃料使用分を計算
               if k == '2121': # 石炭製品の燃料使用分
                   fuel += df_iotable.iat[i, x] * 1000000 / model.u[k]
               # 石油化学系基礎製品への石油製品の投入ではない場合
               if k == '2111':
                   if df_iotable.columns[x] != '2031':
                       fuel += df_iotable.iat[i, x] * 1000000 / model.u[k] # 石油製品の燃料使用分

   # 産出量を計算
   for i in range(187):
       if i == x:
           continue
       if model.ITEMS[x] == '0131': # 0131(農業サービス)の産出は考慮しない
           output = 0
       else:
           output += df_iotable.iat[x, i] * 1000000 / model.u[k]

   # 廃棄物発生量を計算
   waste = input - output - fuel
   # 計算結果をdf_estimateに格納
   df_estimate.iat[x, 0] = waste
   df_estimate.iat[x, 1] = input
   df_estimate.iat[x, 2] = output
   df_estimate.iat[x, 3] = fuel

# 目的関数を定義してruleに渡す
def ObjRule(model):
   z = 0
   slist = ['農業', '林業', '鉱業', '印刷', '皮革', '非鉄金属', '業務用機器', '情報通信機器','輸送用機器']
   for sector, group in df_estimate.groupby('sector'):
       if sector in slist:
           continue
       else:
           z += (group['Wx'].sum() - df_statistics.loc[sector, 'Sy']) ** 2
   return z

model.OBJ = pyo.Objective(rule = ObjRule,
                         sense = pyo.minimize)

# 制約条件を定義
#model.CONST = pyo.Constraint(expr = model.u['0131'] == 0 )

model.CONST = pyo.ConstraintList()
for i in model.ITEMS:
   if i == '0131':
       model.CONST.add(model.u[i] == 0)
   else:
       model.CONST.add(model.u[i] >= 100)

# ソルバを指定して最適化
opt = pyo.SolverFactory('ipopt')
opt.options['linear_solver'] = 'ma27'
# 最適化計算のログ出力の詳細度合い (print_level) を設定
opt.options["print_level"] = 4

res = opt.solve(model, tee=True) # 最適化計算を実行

print(f"評価関数:{model.OBJ()}")
for i in model.ITEMS:
   print(f"u{i}: {model.u[i].value:.0f}")  # 重量単価を1つずつ表示
print(res)

問題のオブジェクを作成

まず、ConcreteModelクラスを用いて問題のオブジェクトを作成します。

# ConcreteModelクラスを用いて問題のオブジェクトを作成
model = pyo.ConcreteModel()

変数の定義

Varクラスを用いて変数を定義します。
このとき、model.ITEMSというリストを引数に与えることで、ベクトル化された変数を定義できます。

model.ITEMSには、df_price_per_ton(重量単価初期値一覧表)のインデックス(code)がリスト形式で格納されています。

次に、uuに、キー(key)がcode、値(value)が重量単価初期値のペアを辞書形式で格納していきます。

続いて、関数InitializeUを定義し、変数の初期値を重量単価初期値で与えていきます。

# リストで指定した内容をキーワードとして保持
df_index = df_price_per_ton.index
NUM = len(df_index)
model.ITEMS = list(df_index)
uu = {df_index[i]:df_price_per_ton.iat[i, 0] for i in range(NUM)}
def initializeU(model, i):
   '''変数の初期化'''
   return uu[i]
model.u = pyo.Var(model.ITEMS, domain=pyo.NonNegativeReals, initialize=initializeU)

廃棄物発生量の計算

各産業の廃棄物発生量を、先ほど定義した変数を用いて計算していきます。

for x in range(len(df_index)):
   # 投入量を計算
   input = 0 # 投入分
   output = 0 # 産出分
   fuel = 0 # 燃料使用分
   waste = 0 # 廃棄物発生量
   for i in range(187):
       for k in model.ITEMS:
           if k == '0131': # 0131(農業サービス)からの投入は考慮しない
               continue
           if k == df_iotable.index[i]:
               input += df_iotable.iat[i, x] * 1000000 / model.u[k]
               # 燃料使用分を計算
               if k == '2121': # 石炭製品の燃料使用分
                   fuel += df_iotable.iat[i, x] * 1000000 / model.u[k]
               # 石油化学系基礎製品への石油製品の投入ではない場合
               if k == '2111':
                   if df_iotable.columns[x] != '2031':
                       fuel += df_iotable.iat[i, x] * 1000000 / model.u[k] # 石油製品の燃料使用分

   # 産出量を計算
   for i in range(187):
       if i == x:
           continue
       if model.ITEMS[x] == '0131': # 0131(農業サービス)の産出は考慮しない
           output = 0
       else:
           output += df_iotable.iat[x, i] * 1000000 / model.u[k]

   # 廃棄物発生量を計算
   waste = input - output - fuel
   # 計算結果をdf_estimateに格納
   df_estimate.iat[x, 0] = waste
   df_estimate.iat[x, 1] = input
   df_estimate.iat[x, 2] = output
   df_estimate.iat[x, 3] = fuel

目的関数の定義

Objectiveクラスを用いて目的関数を定義します。
ObjRule関数で目的関数を記述し、Objectiveクラスのruleに渡しています。

slistには、目的関数から除外するsector(部門分類)として、農業、林業、 鉱業、印刷、皮革、非鉄金属、業務用機器、情報通信機器、輸送用機器を指定します。

その理由として、

・農業部門では、動物の糞尿のみが統計値に計上されていること
・農業以外の部門では、廃棄物発生量が0となっていること

が挙げられます。

# 目的関数を定義してruleに渡す
def ObjRule(model):
   z = 0
   slist = ['農業', '林業', '鉱業', '印刷', '皮革', '非鉄金属', '業務用機器', '情報通信機器','輸送用機器']
   for sector, group in df_estimate.groupby('sector'):
       if sector in slist:
           continue
       else:
           z += (group['Wx'].sum() - df_statistics.loc[sector, 'Sy']) ** 2
   return z

model.OBJ = pyo.Objective(rule = ObjRule,
                         sense = pyo.minimize)

制約条件の定義

Constraintクラスで制約条件を設定します。

# 制約条件を定義
model.CONST = pyo.ConstraintList()
for i in model.ITEMS:
   if i == '0131':
       model.CONST.add(model.u[i] == 0)
   else:
       model.CONST.add(model.u[i] >= 100)

ソルバを指定

SolverFactoryでソルバを指定します。ここではIPOPTを用います。
オプションで、linearsolverをHSLのma27に指定します。

最初、linearsolverがデフォルトのMUMPS (MUltifrontal Massively Parallel sparse direct Solver)で最適化計算を実行したのですが、最適化計算がうまくいきませんでした。

そこで、オプションで、lineasolverをHSL MA27に指定しました。

# ソルバを指定して最適化
opt = pyo.SolverFactory('ipopt')
opt.options['linear_solver'] = 'ma27'

HSLのインストールに四苦八苦しましたが、以下のサイトを参考にして、何とかできました^^;

Installation — do-mpc 4.0.0 documentation

1.Obtain the HSL shared library. Choose the personal licence.
2.Unpack the archive and copy its content to a destination of your choice. (e.g. /home/username/Documents/coinhsl/)
3.Rename libcoinhsl.so to libhsl.so. CasADi is searching for the shared libraries under a depreciated name.
4.Locate your .bashrc file on your home directory (e.g. /home/username/.bashrc)
5.Add the previously created directory to your LD_LIBRARY_PATH, by adding the following line to your .bashrc

export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/home/ffiedler/Documents/coinhsl/lib"

6.Install libgfortran with Anaconda:
conda install -c anaconda libgfortran

最適化計算のログ出力の詳細度合いを指定

最適化計算のログ出力の詳細の度合いを、print_levelで設定できます。
今回は4に設定。0~12の範囲で設定でき、数字が大きいほど詳細になります。

# 最適化計算のログ出力の詳細度合い (print_level) を設定
opt.options["print_level"] = 4

最適化

SolverFactoryオブジェクトのsolveメソッドにmodelを指定して実行すると、最適化が行わます。resには最適化の結果が格納されます。
また、solveでtee=Trueとすると、ソルバの出力が表示されます。

res = opt.solve(model, tee=True) # 最適化計算を実行

結果の表示

目的関数と重量単価【最適化後】を表示させます。

print(f"評価関数:{model.OBJ()}")
for i in model.ITEMS:
   print(f"u{i}: {model.u[i].value:.0f}")  # 重量単価を1つずつ表示

画像1

また、opt.solve()の戻り値をprintで表示させると、以下のような出力を得ます。

print(res)

画像2

Follow me!

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です