docplexで解の決定変数は浮動小数点型で得られます。これは整数解やバイナリ解を求めた場合も同様です。
ただ、アプリケーションでは結果を整数として扱いことも多いと思います。通常整数化にはint関数を使いますが、int関数は少数点以下を切り捨てます。
しかしながら、docplexはtorelanceの設定に従い、整数の近似値を返すことがありますので、0.9999999などの値が戻ることがあります。この場合int関数で整数化すると「0」になってしまいます。ですので、roundをつかって四捨五入して整数化してください。つまり「round(value)」のように変換してください。
これはバイナリ型やIntegerの値が小さい場合には特に問題になります。
- テスト環境
- docplex 2.25.236
- cplex 22.1.1
- python 3.10.9
バイナリ解の求解
具体例を見てみます。10個のアイテムのいずれかを選び、値の合計を最大サイズの範囲内で最大化するという問題です。いずれかを選ぶので「0か1」のバイナリ解を求めています。
item_count = 10
bin_size = 5
item_size = [3.2895421940121903, 2.747936312766254, 1.6530471297151517, 5.253427253264955, 7.28929165215484, 4.739553657547207, 1.8839131940694132, 4.161982467170119, 0.695705496598702, 3.7371204363975687]
item_value = [0.27927757476783455, 0.417034060023064, 0.14499849480269422, 0.6859586722309748, 0.9500408034846701, 0.46717681781350606, 0.7520048049254987, 0.7849634886831153, 0.10733709626379628, 0.7421045232784768]
model = Model(name='item_pack')
item_choice = model.binary_var_list(item_count, name="item")
model.add_constraint(model.sum(item_size[i] * item_choice[i] for i in range(item_count)) <= bin_size)
model.maximize(model.sum(item_value[i] * item_choice[i] for i in range(item_count)))
solution = model.solve()
item_choiced = [item_choice[i].solution_value for i in range(item_count)]
print(f"item_choiced{item_choiced}")
以下のように2番目と7番目のアイテムが選ばれました。binary_var_listを使っているので「0」か「1.0」が返っています。
item_choiced[0, 1.0, 0, 0, 0, 0, 1.0, 0, 0, 0]
キッチリ整数解が戻らない例
ではitem_sizeを一つだけ変更してみます。
item_size[9]=2.7371204363975687
model = Model(name='item_pack')
item_choice = model.binary_var_list(item_count, name="item")
model.add_constraint(model.sum(item_size[i] * item_choice[i] for i in range(item_count)) <= bin_size)
model.maximize(model.sum(item_value[i] * item_choice[i] for i in range(item_count)))
solution = model.solve()
item_choiced = [item_choice[i].solution_value for i in range(item_count)]
print(f"item_choiced{item_choiced}")
すると以下のように7番目と10番目のアイテムが選ばれましたが、10番目は「1.0」ではなく「0.9999999999999999」として結果が戻っています。
item_choiced[0, 0, 0, 0, 0, 0, 1.0, 0, 0, 0.9999999999999999]
そのためint関数で整数化すると「0」になってしまいます。これを避けるためにはroundで整数化して"round(value)"とする必要があります。
value=item_choiced[9]
#0.9999999999999999
print(f"value={value}")
#0になってしまう。
print(f"int(value)={int(value)}")
#roundが必要
print(f"round(value)={round(value)}")
value=0.9999999999999999
int(value)=0
round(value)=1
このように稀にキッチリ「0」か「1.0」が戻らないことがあるので、整数化する際には、かならず"round(value)"とする必要があります。
round_solution設定をつかった回避
round_solutionというオプションをモデルに設定するという方法もあります。
model = Model(name='item_pack')
#round_solutionで整数解を得る
model.round_solution = True
item_choice = model.binary_var_list(item_count, name="item")
model.add_constraint(model.sum(item_size[i] * item_choice[i] for i in range(item_count)) <= bin_size)
model.maximize(model.sum(item_value[i] * item_choice[i] for i in range(item_count)))
solution = model.solve()
item_choiced = [item_choice[i].solution_value for i in range(item_count)]
print(f"item_choiced{item_choiced}")
解がround処理をされて戻りましたので、整数になっています。
item_choiced[0, 0, 0, 0, 0, 0, 1.0, 0, 0, 1]
doclpexのバージョン2.20.204より前のバージョンからバージョンアップした場合は特に注意が必要
特にdoclpexのバージョン2.20.204より前のバージョンでは自動的にroundされていましたので、この問題は発生しませんでした。しか、このバージョン以降に上げた場合にはroundしていないと、アプリケーションは全く変更していなくても、特にバイナリでは、エラーも発生せずに全く違う結果になってしまいますので注意が必要です。
Changed in 2.20.204 (2021.02):
In docplex.mp:
Add support for exporting solutions and solution pools to SOL format.
Add fast methods for changing batches of variable bounds:
Model.change_var_lower_bounds, Model.change_var_upper_bounds
Reset random seed value for cplex 12.10 , was different from COS release value.
Improved performance of variable creation
Removed a warning about accessing a deprecated “solve_status” in solve.
docplex.mp.AdvModel now has checker enabled by default to avoid Python errors.
Is is up to the user to disable type-checking to get maximum performance.
Fixed a bug about not printing updated variable bounds in MPS and SAV
Changed the default rounding behavior: solution values are not rounded by default.
サンプルプログラム