はじめに
Pythonの最適化モデリングツールPyomoで、パラメータや変数、制約式のインデックスを作成できるSet
クラスとRangeSet
クラスについて詳しくまとめました。
インデックスの作成方法、作成したインデックス要素へのアクセス方法、およびインデックス同士の演算方法について記載しています。
検証環境は以下の通りです。
|
バージョン |
Windows 10 Home |
22H2 |
Python |
3.11.6 |
Pyomo |
6.7.3 |
Setクラス
変数作成の基本
Set
クラスの変数を作成する基本的な方法を解説します。
以下に例を示します。
1
2
3
4
5
6
|
import pyomo.environ as pyo
model = pyo.ConcreteModel()
model.A = pyo.Set(initialize=[1, 2, 3]) # list
model.B = pyo.Set(initialize=(1, 2, 3)) # tuple
|
まず、Pyomoの最適化モデルであるConcreteModel
オブジェクトmodel
を作成しています。このmodel
に対してSet
変数を定義します。
Set
のインデックスは、initialize
引数にPythonのlist
やtuple
を与えることで指定できます。
また、以下のように文字列で与えることもできます。
1
2
3
4
|
model = pyo.ConcreteModel()
model.FOOD = pyo.Set(initialize=["spam", "ham", "egg"])
model.PERSON = pyo.Set(initialize=("Alice", "Bob"))
|
なお、Pythonのset
({1, 2, 3}
のように定義します) でもSet
を定義することは可能です。しかし、set
オブジェクトは要素の順序を持たないため、実行の度に結果が変わることがあり、使用は推奨されません。
また、Set
インスタンスは定数であるため、公式リファレンスでは大文字で定義されています。本記事でもこの慣習に従います。
関数を使用した変数作成
Set
のinitialize
引数に関数を渡して変数を定義することも可能です。以下は変数IDX
を[0, 2, 4, 6, 8]
と設定する例です。
1
2
3
4
5
|
def initialize_idx(m):
return [2*i for i in range(5)]
# [0, 2, 4, 6, 8]
model.IDX = pyo.Set(initialize=initialize_idx)
|
なお、initialize_idx()
の引数m
はPyomoのモデルmodel
自身です。以下のようにmodel
に定義されている変数を使用することも可能です。
1
2
3
4
5
|
def initialize_idx2(m):
return [3*i for i in m.IDX]
# [0, 6, 12, 18, 24]
model.IDX2 = pyo.Set(initialize=initialize_idx2)
|
また、上記のコードはPythonのラムダ式を用いて以下のように簡略化できます。
1
2
3
4
5
|
# [0, 2, 4, 6, 8]
model.IDX = pyo.Set(initialize=lambda m: [2*i for i in range(5)])
# [0, 6, 12, 18, 24]
model.IDX2 = pyo.Set(initialize=lambda m: [3*i for i in m.IDX])
|
Set変数へのアクセス
この節では、Set
変数の要素にアクセスする方法を解説します。
Set
変数は反復可能なオブジェクト (iterable object) であるため、for
文を使って要素にアクセスできます。
1
2
3
4
5
|
model = pyo.ConcreteModel()
model.FOOD = pyo.Set(initialize=["spam", "ham", "egg"])
for food in model.FOOD:
print(food)
|
実行結果
要素の数を取得する場合、len()
関数を使用できます。
1
2
|
>>> len(model.FOOD)
3
|
個別の要素にアクセスする場合、at()
メソッドを使用します。at()
メソッドにアクセスしたい要素の番号を1から始まる整数で与えます。
1
2
3
4
5
6
|
>>> model.FOOD.at(1)
'spam'
>>> model.FOOD.at(2)
'ham'
>>> model.FOOD.at(3)
'egg'
|
Set
変数の情報をまとめて表示したい場合、pprint()
メソッドが使用できます。
1
2
3
4
|
>>> model.FOOD.pprint()
FOOD : Size=1, Index=None, Ordered=Insertion
Key : Dimen : Domain : Size : Members
None : 1 : Any : 3 : {'spam', 'ham', 'egg'}
|
また、最適化モデルからSet
変数をまとめて取得したい場合、最適化モデルのcomponent_objects()
メソッドを使用します。このメソッドのctype
引数に取得したいオブジェクトの種類(ここではSet
)を指定します。
以下のように最適化の変数 pyo.Var
をmodel
に定義した場合でも、Set
変数のみ取得できていることが分かります。
1
2
3
4
5
6
7
8
|
model = pyo.ConcreteModel()
model.FOOD = pyo.Set(initialize=["spam", "ham", "egg"])
model.PERSON = pyo.Set(initialize=("Alice", "Bob"))
model.x1 = pyo.Var(model.FOOD)
model.x2 = pyo.Var(model.PERSON)
for c in model.component_objects(ctype=pyo.Set):
c.pprint()
|
実行結果
1
2
3
4
5
6
|
FOOD : Size=1, Index=None, Ordered=Insertion
Key : Dimen : Domain : Size : Members
None : 1 : Any : 3 : {'spam', 'ham', 'egg'}
PERSON : Size=1, Index=None, Ordered=Insertion
Key : Dimen : Domain : Size : Members
None : 1 : Any : 2 : {'Alice', 'Bob'}
|
RangeSetクラス
変数作成
Set
クラスでは文字列などの任意のインデックスを設定できました。一方、RangeSet
クラスでは一定間隔の数値を設定するだけで十分な場合に使用します。
以下にRangeSet
クラスの使用例を示します。RangeSet()
の引数に整数を指定すると、1から指定値までの整数が設定されます。
1
2
3
4
5
|
model = pyo.ConcreteModel()
model.RS = pyo.RangeSet(5)
for v in model.RS:
print(v)
|
実行結果
RangeSet
では開始値や、数値のステップ幅も以下のように指定できます。
- 引数が1つ: 終了値を与える。開始値は0, ステップ幅は1.
- 引数が2つ: 開始値、終了値の順に与える。ステップ幅は1.
- 引数が3つ: 開始値、終了値、ステップ幅の順に与える。
例
1
2
3
4
5
6
7
8
|
# [3, 4, 5] を指定
model.RS1 = pyo.RangeSet(3, 5)
# [5, 7, 9, 11] を指定
model.RS2 = pyo.RangeSet(5, 11, 2)
# [1, 1.5, 2.0, 2.5, 3.0] を指定
model.RS3 = pyo.RangeSet(1, 3, 0.5)
|
RangeSet変数へのアクセス
RangeSet
変数へアクセスする方法は、基本的にはRange
変数と同じです。
ただし、最適化モデルからRangeSet
変数をまとめて取得したい場合、最適化モデルのcomponent_objects()
メソッドのctype
引数にはSet
ではなくRangeSet
を指定します。
1
2
|
for c in model.component_objects(ctype=pyo.RangeSet):
c.pprint()
|
実行結果の例
1
2
3
4
5
6
7
8
9
|
RS : Dimen=1, Size=5, Bounds=(1, 5)
Key : Finite : Members
None : True : [1:5]
RS1 : Dimen=1, Size=3, Bounds=(3, 5)
Key : Finite : Members
None : True : [3:5]
RS2 : Dimen=1, Size=4, Bounds=(5, 11)
Key : Finite : Members
None : True : [5:11:2]
|
Set, RangeSetの演算
PyomoはSet
とRangeSet
の演算もサポートしています。
以下に例として、Set
オブジェクトIDX1
, IDX2
に対する和集合|
、積集合&
、差集合-
、排他的論理和^
を示します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
model = pyo.ConcreteModel()
model.IDX1 = pyo.Set(initialize=("A", "B", "C"))
model.IDX2 = pyo.Set(initialize=("B", "C", "D"))
# 和集合 (union) ['A', 'B', 'C', 'D']
model.UNION = model.IDX1 | model.IDX2
# 共通部品 (積集合, intersection) ['B', 'C']
model.IS = model.IDX1 & model.IDX2
# 差集合 (set difference) ['A']
model.DIFF = model.IDX1 - model.IDX2
# 排他的論理和 (exclusive or, XOR) ['A', 'D']
model.XOR = model.IDX1 ^ model.IDX2
|
また、2つの集合から要素1つずつ取り出した組合せである直積集合 (product) を作ることもできます。
直積集合は2次元の配列となります。これを用いることで、変数や制約式を2次元配列として定義できます。
1
2
3
4
5
6
|
model = pyo.ConcreteModel()
model.FOOD = pyo.Set(initialize=["spam", "ham"])
model.PERSON = pyo.Set(initialize=("Alice", "Bob"))
# [('spam', 'Alice'), ('spam', 'Bob'), ('ham', 'Alice'), ('ham', 'Bob')]
model.PRODUCT = model.FOOD * model.PERSON
|
参考