= Parameters()
params = 3
params.TimeLimit print(params)
TimeLimit = 3
OutputFlag = False
RandomSeed = 1
Taeget = 0
Initial = False
SCOP(Solver forCOnstraint Programing:スコープ)は, 大規模な制約最適化問題を高速に解くためのソルバーである.
ここで,制約最適化(constraint optimization)とは, 数理最適化を補完する最適化理論の体系であり, 組合せ最適化問題に特化した求解原理-メタヒューリスティクス(metaheuristics)-を用いるため, 数理最適化ソルバーでは求解が困難な大規模な問題に対しても,効率的に良好な解を探索することができる.
SCOPのトライアルバージョンは, http://logopt.com/scop2/ からダウンロードもしくはgithub https://github.com/scmopt/scop からクローンできる. また,テクニカルドキュメントは, https://scmopt.github.io/manual/14scop.html にある.
ここでは,SCOPで対象とする重み付き制約充足問題について解説する.
一般に制約充足問題(constraint satisfaction problem)は,以下の3つの要素から構成される.
変数(variable): 分からないもの,最適化によって決めるもの. 制約充足問題では,変数は,与えられた集合(以下で述べる「領域」)から1つの要素を選択することによって決められる.
領域(domain): 変数ごとに決められた変数の取り得る値の集合
制約(constraint): 幾つかの変数が同時にとることのできる値に制限を付加するための条件. SCOPでは線形制約(線形式の等式,不等式),2次制約(一般の2次式の等式,不等式), 相異制約(集合に含まれる変数がすべて異なることを表す制約)が定義できる.
制約充足問題は,制約をできるだけ満たすように, 変数に領域の中の1つの値を割り当てることを目的とした問題である.
SCOPでは,重み付き制約充足問題(weighted constraint satisfaction problem) を対象とする.
ここで「制約の重み」とは,制約の重要度を表す数値であり, SCOPでは正数値もしくは無限大を表す文字列 ’inf’を入力する. ’inf’を入力した場合には,制約は絶対制約(hard constraint)とよばれ, その逸脱量は優先して最小化される. 重みに正数値を入力した場合には,制約は考慮制約(soft constraint)とよばれ, 制約を逸脱した量に重みを乗じたものの和の合計を最小化する.
すべての変数に領域内の値を割り当てたものを解(solution)とよぶ. SCOPでは,単に制約を満たす解を求めるだけでなく, 制約からの逸脱量の重み付き和(ペナルティ)を最小にする解を探索する.
SCOPモジュール (scop.py) は,以下のクラスから構成されている.
モデルクラス Model
変数クラス Variable
制約クラス Constraint (これは,以下のクラスのスーパークラスである.)
SCOPでは数(制約)名や値を文字列で区別するため重複した名前を付けることはできない. なお,使用できる文字列は, 英文字 (a–z, A–Z), 数字 (0–9), 大括弧 ([ ]), アンダーバー (_), および @ に限定される.
それ以外の文字列はすべてアンダーバー (_)に置き換えられる.
Parametersクラスで設定可能なパラメータは,以下の通り.
TimeLimit は制限時間を表す.制限時間は正数値を設定する必要があり,その既定値は 600秒である.
OutputFlag は出力フラグを表し,最適化の過程を出力する際の詳細さを制御するためのパラメータである. 真(True もしくは正の値)に設定すると詳細な情報を出力し, 偽(False もしくは 0)に設定すると最小限の情報を出力する. 既定値は偽(0)である.
RandomSeed は乱数の種である.SCOPでは探索にランダム性を加味しているので,乱数の種を変えると,得られる解が変わる可能性がある. 乱数の種の既定値は 1である.
Target は制約の逸脱量が目標値以下になったら自動終了させるためのパラメータである. 既定値は 0 である.
Initial は,前回最適化の探索を行った際の最良解を初期値とした探索を行うとき True ,それ以外のとき False を表すパラメータである. 既定値は False である.最良解の情報は,「変数:値」を1行としたテキストとしてファイル名 scop_best_data.txt に保管されている. このファイルを書き換えることによって,異なる初期解から探索を行うことも可能である.
Parameters (TimeLimit:int=600, OutputFlag:bool=False, RandomSeed:int=1, Target:int=0, Initial:bool=False)
SCOP parameter class to control the operation of SCOP.
変数クラス Variable のインスタンスは,モデルインスタンスの addVariable もしくは addVariables メソッドを 用いて生成される.
引数の name は変数名を表す文字列であり, domain は領域を表すリストである.
引数の names は変数名を要素としたリストであり, domain は領域を表すリストである.
変数クラスは,以下の属性をもつ.
また,変数インスタンスは,変数の情報を文字列として返すことができる.
Variable (name='', domain=None)
SCOP variable class. Variables are associated with a particular model. You can create a variable object by adding a variable to a model (using Model.addVariable or Model.addVariables) instead of by using a Variable constructor.
variable X[1]:['1', '2', '3'] = None
variable __x0:['1', '2', '3'] = None
variable __x1:['4', '5', '6'] = None
制約クラスは,以下で定義する線形制約クラス,2次制約クラス,相異制約クラスの基底クラスである.
Constraint (name='', weight=1)
Constraint base class
PythonからSCOPをよび出して使うときに,最初にすべきことはモデルクラス Model のインスタンスを生成することである. たとえば, ‘test’ と名付けたモデルインスタンス model を生成したいときには,以下のように記述する.
インスタンスの引数はモデル名であり,省略すると無名のモデルが生成される.
Model クラスは,以下のメソッドをもつ.
領域を定義するためのリスト domain の要素は,文字列でも数値でもかまわない. (ただし内部表現は文字列であるので,両者は区別されない.)
以下に例を示す.
x = model.addVarriable('var') # domain is set to []
x = model.addVariable(name='var',domain=[1,2,3]) # arguments by name
x = model.addVariable('var',['A','B','C']) # arguments by position
1行目の例では,変数名を ‘var’ と設定した空の領域をもつ変数を追加している. 2行目の例では,名前付き引数で変数名と領域を設定している.領域は 1,2,3 の数値である. 3行目の例では,領域を文字列として変数を追加している.
addVariables(names,domain) はモデルに,同一の領域をもつ複数の変数を同時に追加する. 引数の names は変数名を要素としたリストであり,domain は領域を表すリストである. 領域を定義するためのリストの要素は,文字列でも数値でもかまわない.
addConstriant(con) は制約インスタンス con をモデルに追加する. 制約インスタンスは,制約クラスを用いて生成されたインスタンスである.制約インスタンスの生成法については, 以下で解説する.
optimize はモデルの求解(最適化)を行うメソッドである. 最適化のためのパラメータは,パラメータ属性 Params で設定する. 返値は,最適解の情報を保管した辞書と,破った制約の情報を保管した辞書のタプルである.
たとえば,以下のプログラムでは最適解を辞書 sol に,破った制約を辞書 violated に保管する.
最適解や破った制約は,変数や制約の名前をキーとし,解の値や制約逸脱量を値とした辞書であるので, 最適解の値と逸脱量を出力するには,以下のように記述すれば良い.
引数:
- cloud: 複数人が同時実行する可能性があるときTrue(既定値はFalse); Trueのとき,ソルバー呼び出し時に生成されるファイルにタイムスタンプを追加し,計算終了後にファイルを消去する.
モデルインスタンスは,以下の属性をもつ.
最適化の状態を表す整数と意味
状態の定数 | 説明 |
---|---|
0 | 最適化成功 |
1 | 求解中にユーザが Ctrl-C を入力したことによって強制終了した. |
2 | 入力データファイルの読み込みに失敗した. |
3 | 初期解ファイルの読み込みに失敗した. |
4 | ログファイルの書き込みに失敗した. |
5 | 入力データの書式にエラーがある. |
6 | メモリの確保に失敗した. |
7 | 実行ファイル scop.exe のよび出しに失敗した. |
10 | モデルの入力は完了しているが,まだ最適化されていない. |
負の値 | その他のエラー |
また,モデルインスタンスは,モデルの情報を文字列として返すことができる.
Model (name:Optional[str]='', constraints:Optional[List[__main__.Constraint]]=[], variables:Optional[List[__main__.Variable]]=[], Params:Optional[__main__.Parameters]=Parameters(TimeLimit=600, OutputFlag=False, RandomSeed=1, Target=0, Initial=False), varDict:Optional[Dict[str,List]]={}, Status:Optional[int]=10)
SCOP model class.
Attbibutes: - constraints: Set of constraint objects in the model. - variables: Set of variable objects in the model. - Params: Object including all the parameters of the model. - varDict: Dictionary that maps variable names to the variable object.
model = Model(name='test')
model.addVariable(name="x[1]",domain=[0,1,2])
print(model)
model.Params.TimeLimit= 1
sol, violated = model.optimize()
print("solution=", sol)
print("violated constraints=", violated)
model.Status
Model:test
number of variables = 1
number of constraints= 0
variable x[1]:['0', '1', '2'] = None
================ Now solving the problem ================
solution= {'x[1]': '0'}
violated constraints= {}
0
最も基本的な制約は,線形制約である. 線形制約は \[ 線形項1 + 線形項2 + \cdots 制約の方向 (\leq, \geq, =) 右辺定数 \] の形で与えられる.線形項は,「変数」が「値」をとったとき \(1\), それ以外のとき \(0\) を表す値変数(value variable) \(x[変数,値]\) を用いて, \[ 係数 \times x[変数,値] \] で与えられる.
ここで,係数ならびに右辺定数は整数とし,制約の方向は 以下(\(\leq\)),以上(\(\geq\)),等しい(\(=\))から1つを選ぶ必要がある.
線形制約クラス Linear のインスタンスは,以下のように生成する.
引数の意味は以下の通り.
name は制約の名前を表す.これは制約を区別するための名称であり,固有の名前を文字列で入力する必要がある.
(名前が重複した場合には,前に定義した制約が無視される.) 名前を省略した場合には,自動的に __CON[ 通し番号 ] という名前が付けられる. これは,以下の2次制約や相異制約でも同じである.
weight は制約の重みを表す.重みは,制約の重要性を表す正数もしくは文字列 ‘inf’ である. ここで ‘inf’ は無限大を表し,絶対制約を定義するときに用いられる. 重みは省略することができ,その場合の既定値は 1である.
rhs は制約の右辺定数(right hand side)を表す. 右辺定数は,制約の右辺を表す定数(整数値)である. 右辺定数は省略することができ,その場合の既定値は 0 である.
direction は制約の向きを表す. 制約の向きは, ‘<=’ , ‘>=’ , ‘=’ のいずれかの文字列とする.既定値は ‘<=’ である.
上の引数はすべて Linear クラスの属性となる.最適化を行った後では,制約の左辺の評価値が属性として参照可能になる.
Linear クラスは,以下のメソッドをもつ.
addTerms メソッドの引数の意味は以下の通り. - coeffs は追加する項の係数もしくは係数リスト.係数もしくはリストの要素は整数. - vars は追加する項の変数インスタンスもしくはの変数インスタンスのリスト.リストの場合には,リストcoeffsと同じ長さをもつ必要がある. - values は追加する項の値もしは値のリスト.リストの場合には,リストcoeffsと同じ長さをもつ必要がある.
addTerms メソッドは,1つの項を追加するか,複数の項を一度に追加する.1つの項を追加する場合には,引数の係数は整数値,変数は変数インスタンスで与え,値は変数の領域の要素とする.複数の項を一度に追加する場合には,同じ長さをもつ,係数,変数インスタンス,値のリストで与える.
たとえば,項をもたない線形制約インスタンス L に対して,
L.addTerms(1, y, 'A')
と1つの項を追加すると,制約の左辺は
1 x[y, 'A']
となる.ここで x は値変数( y が ‘A’ になるとき 1,それ以外のとき 0 の仮想の変数)を表す.
同様に,項をもたない線形制約インスタンス L に対して,
L.addTerms([2, 3, 1], [y, y, z], ['C', 'D', 'C'])
と3つの項を同時に追加すると,制約の左辺は以下のようになる.
2 x[y,'C'] + 3 x[y,'D'] + 1 x[z,'C']
setRhs(rhs) は線形制約の右辺定数を rhs に設定する.引数は整数値であり,既定値は 0 である.
setDirection(dir) は制約の向きを設定する.引数 dir は ‘<=’ , ‘>=’ , ‘=’ のいずれかの文字列とする.既定値は ‘<=’ である.
setWeight(weight) は制約の重みを weight に設定する.引数は正数値もしくは文字列 ‘inf’ である. ここで ‘inf’ は無限大を表し,絶対制約を定義するときに用いられる.
また,線形制約クラス Linear は,制約の情報を文字列として返すことができる.
Linear (name='', weight=1, rhs=0, direction='<=')
Linear ( name, weight=1, rhs=0, direction=“<=” ) Linear constraint constructor.
Arguments: - name: Name of linear constraint. - weight (optiona): Positive integer representing importance of constraint. - rhs: Right-hand-side constant of linear constraint. - direction: Rirection (or sense) of linear constraint; “<=” (default) or “>=” or “=”.
Attributes: - name: Name of linear constraint. - weight (optional): Positive integer representing importance of constraint. - rhs: Right-hand-side constant of linear constraint. - lhs: Left-hand-side constant of linear constraint. - direction: Direction (or sense) of linear constraint; “<=” (default) or “>=” or “=”. - terms: List of terms in left-hand-side of constraint. Each term is a tuple of coeffcient,variable and its value.
SCOPでは(非凸の)2次関数を左辺にもつ制約(2次制約)も扱うことができる.
2次制約は, \[ 2次項1 + 2次項2 + \cdots 制約の方向 (\leq, \geq, =) 右辺定数 \] の形で与えられる.ここで2次項は, \[ 係数 \times x[変数1,値1] \times x[変数2,値2] \] で与えられる.
2次制約クラス Quadratic のインスタンスは,以下のように生成する.
2次制約クラスの引数と属性は,線形制約クラス Linear と同じである.
Quadratic クラスは,以下のメソッドをもつ.
2次制約に対する addTerms メソッドの引数は以下の通り.
addTerms メソッドは,1つの項を追加するか,複数の項を一度に追加する. 1つの項を追加する場合には, 引数の係数は整数値,変数は変数インスタンスで与え,値は変数の領域の要素とする. 複数の項を一度に追加する場合には,同じ長さをもつ,係数,変数インスタンス,値のリストで与える.
たとえば,項をもたない2次制約インスタンス Q に対して,
と1つの項を追加すると,制約の左辺は
となる.
同様に,項をもたない2次制約インスタンス Q に対して,
と3つの項を同時に追加すると,制約の左辺は以下のようになる.
setRhs(rhs) は2次制約の右辺定数を rhs に設定する.引数は整数値であり,既定値は 0 である.
setDirection(dir) は制約の向きを設定する.引数 dir は ‘<=’ , ‘>=’ , ‘=’ のいずれかの文字列とする.既定値は ‘<=’ である.
setWeight(weight) は制約の重みを設定する.引数は正数値もしくは文字列 ‘inf’ である. ‘inf’ は無限大を表し,絶対制約を定義するときに用いられる.
また,2次制約クラス Quadratic は,制約の情報を文字列として返すことができる.
Quadratic (name='', weight=1, rhs=0, direction='<=')
Quadratic ( name, weight=1, rhs=0, direction=“<=” ) Quadratic constraint constructor.
Arguments: - name: Name of quadratic constraint. - weight (optional): Positive integer representing importance of constraint. - rhs: Right-hand-side constant of linear constraint. - direction: Direction (or sense) of linear constraint; “<=” (default) or “>=” or “=”.
Attributes: - name: Name of quadratic constraint. - weight (optiona): Positive integer representing importance of constraint. - rhs: Right-hand-side constant of linear constraint. - lhs: Left-hand-side constant of linear constraint. - direction: Direction (or sense) of linear constraint; “<=” (default) or “>=” or “=”. - terms: List of terms in left-hand-side of constraint. Each term is a tuple of coeffcient, variable1, value1, variable2 and value2.
Q = Quadratic(name = "a quadratic constraint", rhs = 10, direction = "<=" )
x = Variable(name="x",domain=["A","B","C"] )
y = Variable(name="y",domain=["A","B","C"] )
Q.addTerms([3,9], [x,x], ["A","B"], [y,y], ["B","C"])
print(Q)
a_quadratic_constraint: weight=1 type=quadratic 3(x,A)(y,B) 9(x,B)(y,C) <=0
相異制約は,変数の集合に対し, 集合に含まれる変数すべてが異なる値を とらなくてはならないことを規定する. これは組合せ的な構造に対する制約であり,制約最適化の特徴的な制約である.
SCOPにおいては,値が同一であるかどうかは,値の名称ではなく, 変数のとりえる値の集合(領域)を表したリストにおける順番(インデックス)によって決定される. たとえば, 変数 var1 および var2 の領域がそれぞれ [‘A’,‘B’] ならびに [‘B’,‘A’] であったとき, 変数 var1 の値 ‘A’, ‘B’ の順番はそれぞれ 0 と 1, 変数 var2 の値 ‘A’, ‘B’ の順番はそれぞれ 1 と 0 となる. したがって,相異制約を用いる際には変数に同じ領域を与えることが(混乱を避けるという意味で)推奨される.
相異制約クラス Alldiff のインスタンスは,以下のように生成する.
引数の名前と既定値は以下の通り.
name は制約名を与える.
varlist は相異制約に含まれる変数インスタンスのリストを与える. これは,値の順番が異なることを要求される変数のリストであり,省略も可能である. その場合の既定値は,空のリストとなる. ここで追加する変数は,モデルクラスに追加された変数である必要がある.
weight は制約の重みを与える.
相異制約の制約名と重みについては,線形制約クラス Linear と同じように設定する. 上の引数は Alldiff クラスの属性でもある.その他の属性として最適化した後で得られる式の評価値がある.
Alldiff クラスは,以下のメソッドをもつ.
addVariable(var) は相異制約に1つの変数インスタンス var を追加する.
addVariables(varlist) は相異制約の変数インスタンスを複数同時に(リスト varlist として)追加する.
setWeight(weight) は制約の重みを設定する.引数は正数値もしくは文字列 ‘inf’ である. ‘inf’ は無限大を表し,絶対制約を定義するときに用いられる.
また,相異制約クラス Alldiff は,制約の情報を文字列として返すことができる.
Alldiff (name='', varlist=None, weight=1)
Alldiff ( name=None,varlist=None,weight=1 ) Alldiff type constraint constructor.
Arguments: - name: Name of all-different type constraint. - varlist (optional): List of variables that must have differennt value indices. - weight (optional): Positive integer representing importance of constraint.
Attributes: - name: Name of all-different type constraint. - varlist (optional): List of variables that must have differennt value indices. - lhs: Left-hand-side constant of linear constraint.
SCOPはメタヒューリスティクスによって解の探索を行う. 一般には,解の良さと計算時間はトレードオフ関係がある.つまり,計算時間をかければかけるほど良い解を得られる可能性が高まる. どの程度の計算時間をかければ良いかは,最適化したい問題例(問題に数値を入れたもの)による. plot_scopは,横軸に計算時間,縦軸に目的関数値をプロットする関数であり,最適化を行ったあとに呼び出す. 得られるPlotlyの図は,どのくらいの計算時間をかければ良いかをユーザーが決めるための目安を与える.
たとえば以下の例の図から,500秒程度の計算時間で良好な解を得ることができるが,念入りにさらに良い解を探索したい場合には2000秒以上の計算時間が必要なことが分かる.
plot_scop (file_name:str='scop_out.txt')