JavaScriptを有効にしてください

Pyomoのインデックスを作成するSetクラスとRangeSetクラス

 ·   7 min read

※記事内に商品プロモーションを含むことがあります。

はじめに

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のlisttupleを与えることで指定できます。

また、以下のように文字列で与えることもできます。

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インスタンスは定数であるため、公式リファレンスでは大文字で定義されています。本記事でもこの慣習に従います。

関数を使用した変数作成

Setinitialize引数に関数を渡して変数を定義することも可能です。以下は変数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)

実行結果

1
2
3
spam
ham
egg

要素の数を取得する場合、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.Varmodelに定義した場合でも、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)

実行結果

1
2
3
4
5
1
2
3
4
5

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はSetRangeSetの演算もサポートしています。
以下に例として、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

参考

シェアする

Helve
WRITTEN BY
Helve
関西在住、電機メーカ勤務のエンジニア。X(旧Twitter)で新着記事を配信中です

サイト内検索