※記事内に商品プロモーションを含むことがあります。
はじめに
PyomoはPythonで書かれた最適化モデリングツールである。Pyomoの基本的な使い方と、線形計画問題の解き方については以下の記事を参照。
Pyomoで線形計画問題を解く
本記事では、Pyomoを使って非線形計画問題を解く方法を示す。Pyomoには最適化ソルバが含まれていないため、IPOPT (Interior Point OPTimizer)という無償ソルバーを使う。IPOPTは主双対内点法を利用したソルバであり、大規模な非線形問題を高速に解くことができる。問題は連続である必要がある。また、大域的最適解を求めるには問題が凸である必要がある。
環境は以下の通り。
ソフトウェア |
バージョン |
Python |
3.7.4 |
Pyomo |
5.6.9 |
IPOPT |
3.11.1 |
リンク
ソフトウェアのインストール
PyomoとIPOPTをインストールする。conda環境の場合は以下を実行する。
conda install -c conda-forge pyomo
conda install -c conda-forge ipopt
pip環境の場合は以下を実行する。
pip install pyomo
pip install ipopt
対象とする線形計画問題
以下の制約付き非線形最小化問題を考える。
$$ \begin{array}{ll} \text{minimize} \ & f(x_1, x_2) = x_1^2 + x_2^2 \\ \text{subject to} \ & x_1 x_2 \ge 1 \\ & x_1 \ge 0, x_2 \ge 0 \end{array} $$
図示すると以下のようになり、$(x_1, x_2)=(1, 1)$で最小値$ 2$をとる。青色で図示した領域は実行可能領域を示す。
Pyomoのソースコード
上記の問題をPyomoを使って記述し、最適化を実行したコードは以下のようになる。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
import pyomo.environ as pyo
model = pyo.ConcreteModel(name="NLP sample",
doc="2 variables, 1 constraints")
model.x = pyo.Var([1,2], domain=pyo.NonNegativeReals) # 変数を定義
model.OBJ = pyo.Objective(expr = model.x[1]**2 + model.x[2]**2,
sense = pyo.minimize) # 目的関数を定義
model.Constraint = pyo.Constraint(expr = model.x[1] * model.x[2] >= 1)
# 制約条件を定義
opt = pyo.SolverFactory('ipopt') # 最適化ソルバを設定
res = opt.solve(model) # 最適化計算を実行
print(f"評価関数:{model.OBJ()}")
print(f"x1: {model.x[1]()}")
print(f"x2: {model.x[2]()}")
|
実行結果は以下のようになり、最適値を得られている。
1
2
3
|
評価関数:1.9999999825046202
x1: 0.999999995626155
x2: 0.999999995626155
|
ソースコードの解説
以下、ソースコードの詳細を述べる。
問題のオブジェクトの作成
まず、ConcreteModel
クラスを用いて問題のオブジェクトを作成する。
1
2
|
model = pyo.ConcreteModel(name="NLP sample",
doc="2 variables, 1 constraints")
|
pyomoでは問題を定義するためのクラスには、次の2種類があるが、ここではConcreteModel
を用いた。
ConcreteModel
: 具体的なパラメータを定義しながら作成する
AbstractModel
: パラメータは後で設定する
また、name
, doc
という引数では、それぞれモデルの名前と説明を文字列 (str) で設定している。これらの値を取得するには、オブジェクトのname
, doc
メンバ変数を呼ぶ。
例:
1
2
|
>>> model.name
'NLP sample'
|
勿論、ConcreteModel
クラスは引数を与える必要はなく、単に以下のようにしても最適化の結果には影響しない。
1
|
model = pyo.ConcreteModel()
|
変数の定義
次に、Var
クラスを用いて変数を定義する。
1
|
model.x = pyo.Var([1,2], domain=pyo.NonNegativeReals) # 変数を定義
|
model
にx
といった適当なメンバ変数を作り、Var
クラスのオブジェクトを定義する。
Var
クラスの第1引数に数値のリストを割り当てることで、x
はそのリストと同じ長さのベクトルになる。
また、Var
クラスの引数domain
は変数の種類を表す。Pyomoで定義可能なdomain
の一部を以下に示す。
Reals
PositiveReals
NonPositiveReals
NegativeReals
NonNegativeReals
Integers
PositiveIntegers
NonPositiveIntegers
また、Var
クラスの引数bounds
とinitialize
でそれぞれ変数の範囲と初期値を定義できる。
例:
1
2
|
pyo.Var([1,2], domain=pyo.NonNegativeReals,
bounds=(3, 6), initialize=5)
|
目的関数の定義
次に、Objective
クラスを用いて、目的関数を定義する。
1
2
|
model.OBJ = pyo.Objective(expr = model.x[1]**2 + model.x[2]**2,
sense = pyo.minimize) # 目的関数を定義
|
先ほど定義した変数x
を用いて、expr
に目的関数を記述する。
また、sense
で最小化問題(minimize
)か最大化問題(maximize
)かを定義する(初期値はminimize
)。
目的関数を定義する方法として、以下のように関数を定義して、rule
に渡す方法もある。
1
2
3
4
5
|
def ObjRule(model):
return model.x[1]**2 + model.x[2]**2
model.OBJ = pyo.Objective(rule = ObjRule,
sense = pyo.minimize)
|
なお、ConcreteModel
ではexpr
を使う方法とrule
を使う方法のどちらも使えるが、AbstractModel
ではrule
を使う方法しか使えない。
制約条件の定義
Constraint
クラスで制約条件を設定する。
1
2
|
model.Constraint = pyo.Constraint(expr = model.x[1] * model.x[2] >= 1)
# 制約条件を定義
|
等式制約は==
で、不等式制約は<=
, >=
でそれぞれ設定する。
Objective
クラスの場合と同様に、expr
の代わりに、rule
と関数で制約条件を定義することができる。
例:
1
2
3
4
|
def ConstRule(model):
return model.x[1] * model.x[2] >= 1
model.Constraint1 = pyo.Constraint(rule = ConstRule)
|
ソルバの指定と最適化
最後に、ソルバを指定して最適化を行う。
1
2
|
opt = pyo.SolverFactory('ipopt')
res = opt.solve(model)
|
SolverFactory
でソルバを指定する。ここではIPOPTを用いる。次に、SolverFactory
オブジェクトのsolve
メソッドにmodel
を指定して実行すると、最適化が行われる。res
には最適化の結果が格納される。
また、solve
でtee=True
とすると、ソルバの出力が表示される。
1
|
res = opt.solve(model, tee=True)
|
最適化ソルバのオプション設定
最適化ソルバ(ここではIPOPT)にオプションを設定するためには、最適化計算の前に、SolverFactory
オブジェクトのoptions
変数に辞書形式で渡す。
例:
1
2
3
4
5
|
opt = pyo.SolverFactory('ipopt')
opt.options = {"print_level" : 4,
"tol" : 1E-3}
res = opt.solve(model, tee=True)
|
もしくは、以下のようにしてもよい。
1
2
3
4
5
|
opt = pyo.SolverFactory('ipopt')
opt.options["print_level"] = 4
opt.options["tol"] = 1E-3
res = opt.solve(model, tee=True)
|
上記の例では、
- 最適化計算のログ出力の詳細度合い (
print_level
) を4(0~12の範囲。数字が大きいほど詳細になる)
- 最適解の許容誤差 (
tol
) を1E-3
と設定している。
IPOPTで設定可能なオプションについては、以下のページを参照。
Ipopt: Ipopt Options
一覧の形式で確認したい場合には、以下のページを参照。
https://www.coin-or.org/Bonmin/option_pages/options_list_ipopt.html
最適化結果の評価
最適化後の変数や目的関数の値を取得するには、model
の変数に直接アクセスする。
1
2
3
4
5
6
|
>>> model.OBJ()
1.9999999825046202
>>> model.x[1]()
0.999999995626155
>>> model.x[2]()
0.999999995626155
|
最適化問題の概要を表示するには、model.display()
, model.pprint()
などを実行する。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
>>> print(model.display())
Model NLP sample
Variables:
x : Size=2, Index=x_index
Key : Lower : Value : Upper : Fixed : Stale : Domain
1 : 0 : 0.999999995626155 : None : False : False : NonNegativeReals
2 : 0 : 0.999999995626155 : None : False : False : NonNegativeReals
Objectives:
OBJ : Size=1, Index=None, Active=True
Key : Active : Value
None : True : 1.9999999825046202
Constraints:
Constraint1 : Size=1
Key : Lower : Body : Upper
None : 1.0 : 0.9999999912523101 : None
None
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
>>> print(model.pprint())
2 variables, 1 constraints
1 Set Declarations
x_index : Dim=0, Dimen=1, Size=2, Domain=None, Ordered=False, Bounds=(1, 2)
[1, 2]
1 Var Declarations
x : Size=2, Index=x_index
Key : Lower : Value : Upper : Fixed : Stale : Domain
1 : 0 : 0.999999995626155 : None : False : False : NonNegativeReals
2 : 0 : 0.999999995626155 : None : False : False : NonNegativeReals
1 Objective Declarations
OBJ : Size=1, Index=None, Active=True
Key : Active : Sense : Expression
None : True : minimize : x[1]**2 + x[2]**2
1 Constraint Declarations
Constraint1 : Size=1, Index=None, Active=True
Key : Lower : Body : Upper : Active
None : 1.0 : x[1]*x[2] : +Inf : True
4 Declarations: x_index x OBJ Constraint1
None
|
最適化の結果を表示するには、opt.solve()
の戻り値をprint
で表示する。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
>>> print(res)
Problem:
- Lower bound: -inf
Upper bound: inf
Number of objectives: 1
Number of constraints: 1
Number of variables: 2
Sense: unknown
Solver:
- Status: ok
Message: Ipopt 3.11.1\x3a Optimal Solution Found
Termination condition: optimal
Id: 0
Error rc: 0
Time: 0.35607051849365234
Solution:
- number of solutions: 0
number of solutions displayed: 0
|
参考
Pyomoで線形計画問題を解く
IPOPTに利用されている主双対内点法の概要。
非線形計画問題の主双対内点法
Pyomoの公式リファレンス
Pyomo Documentation 5.7.2 — Pyomo 5.7.2 documentation
IPOPTのドキュメント
Ipopt: Documentation
Pythonで使える最適化ツールの比較(Pyomo, PuLPなど)。
ソルバの導入についても紹介されている。
Python + Pyomoによる(非線形)数値最適化 - Easy to type