在做計算的時候,我們有時候會先進行一些模型試算,根據(jù)試算結果修改我們的目標函數(shù)和約束。DOCPLEX提供了一系列方法,讓我們修改已經建立好的模型中的目標函數(shù)以及約束,這里我們通過小例子看一下這些方法。
例子
這里我們以一個多重背包問題為例,假設我們有一些背包和一些重物,我們當前的目標是將重物盡可能裝入背包中,使得裝入物品的總質量最大,那么我們的模型可以以如下方式創(chuàng)建:
# 導入包
from docplex.mp.model import Model
import pandas as pd
import numpy as np
# 創(chuàng)建數(shù)據(jù)
np.random.seed(0)
n_knapsack = 5
n_item = 20
def create_data() -> dict:
data = dict()
data["KnapsackRange"] = [i for i in range(n_knapsack)]
data["ItemRange"] = [j for j in range(n_item)]
data["ItemWeight"] = np.random.randint(n_knapsack, n_item, len(data["ItemRange"]))
data["KnapsackCapacity"] = np.random.randint(20, 50, len(data["KnapsackRange"]))
data["Lambda1"] = 1.0
data["Lambda2"] = 0.0
return data
data = create_data()
# 定義約束
class MaxWeightObj(object):
def __init__(self):
pass
def add(self, model, data):
total_weight_item = model.sum(data["Var"][(i,j)] * data["ItemWeight"][j] for i in data["KnapsackRange"] for j in data["ItemRange"])
load_balance_item = model.sum(model.abs(
model.sum(data["Var"][(i,j)] * data["ItemWeight"][j] for j in data["ItemRange"]) - 30)
for i in data["KnapsackRange"])
model.maximize(data["Lambda1"] * total_weight_item + data["Lambda2"] * load_balance_item)
class CapacityConstraint(object):
def __init__(self):
pass
def add(self, model, data):
model.add_constraints((model.sum(data["Var"][(i, j)] * data["ItemWeight"][j]
for j in data["ItemRange"])<=data["KnapsackCapacity"][i]
for i in data["KnapsackRange"]), names = "CapacityConstraint")
class CountConstraint(object):
def __init__(self):
pass
def add(self, model, data):
model.add_constraints((model.sum(data["Var"][(i, j)] for j in data["ItemRange"]) >= 1
for i in data["KnapsackRange"]), names = "CountConstraint")
class UniquenessConstraint(object):
def __init__(self):
pass
def add(self, model, data):
model.add_constraints((model.sum(data["Var"][(i, j)] for i in data["KnapsackRange"]) <= 1
for j in data["ItemRange"]), names = "UniquenessConstraint")
# 建立模型
mdl = Model("Test model", cts_by_name=True)
data["Var"] = mdl.binary_var_dict([(i, j) for i in data["KnapsackRange"] for j in data["ItemRange"]], name='x') # 向數(shù)據(jù)中添加變量
constraints = [MaxWeightObj(), CapacityConstraint(), CountConstraint(), UniquenessConstraint()]
for cons in constraints:
cons.add(mdl, data)
# Output model info
mdl.print_information()
mdl.get_objective_expr()
通過打印模型,我們可以看到我們模型的信息:
Model: Test model
- number of variables: 120
- binary=100, integer=0, continuous=20
- number of constraints: 45
- linear=45
- parameters: defaults
- objective: maximize
- problem type is: MILP
CPU times: user 14.6 ms, sys: 9.96 ms, total: 24.6 ms
Wall time: 28.3 ms
docplex.mp.LinearExpr(17x_0_0+10x_0_1+5x_0_2+8x_0_3+16x_0_4+8x_0_5+12x_0_6+14x_0_7+8x_0_8+10x_0_9+7x_0_10+9x_0_11+12x_0_12+11x_0_13+13x_0_14+13x_0_15+17x_0_16+15x_0_17+6x_0_18+11x_0_19+17x_1_0+10x_1_1+5x_1_2+8x_1_3+16x_1_4+8x_1_5+12x_1_6+14x_1_7+8x_1_8+10x_1_9+7x_1_10+9x_1_11+12x_1_12+11x_1_13+13x_1_14+13x_1_15+17x_1_16+15x_1_17+6x_1_18+11x_1_19+17x_2_0+10x_2_1+5x_2_2+8x_2_3+16x_2_4+8x_2_5+12x_2_6+14x_2_7+8x_2_8+10x_2_9+7x_2_10+9x_2_11+12x_2_12+11x_2_13+13x_2_14+13x_2_15+17x_2_16+15x_2_17+6x_2_18+11x_2_19+17x_3_0+10x_3_1+5x_3_2+8x_3_3+16x_3_4+8x_3_5+12x_3_6+14x_3_7+8x_3_8+10x_3_9+7x_3_10+9x_3_11+12x_3_12+11x_3_13+13x_3_14+13x_3_15+17x_3_16+15x_3_17+6x_3_18+11x_3_19+17x_4_0+10x_4_1+5x_4_2+8x_4_3+16x_4_4+8x_4_5+12x_4_6+14x_4_7+8x_4_8+10x_4_9+7x_4_10+9x_4_11+12x_4_12+11x_4_13+13x_4_14+13x_4_15+17x_4_16+15x_4_17+6x_4_18+11x_4_19+_abs101+_abs105+_abs109+_abs113+_abs117)
目標函數(shù)的修改
下面如果說我們需要改變我們的目標函數(shù):在最大化裝入物品的總質量之余,還要求每個背包內的物品重量盡可能均勻。
那么我們可以用model.set_objective(sense, obj_fun)方法來修改模型的目標函數(shù),sense參數(shù)代表最大化或者最小化目標函數(shù),可以直接傳入string "max/min"來指定;obj_fun表示我們新指定的目標函數(shù)。
具體到我們的例子中,可以用如下代碼進行修改:
# 修改目標函數(shù)中兩項的相對權重,并且修改模型中的目標函數(shù)
total_parcel_item = mdl.sum(data["Var"][(i,j)] * data["ItemWeight"][j] for i in data["KnapsackRange"] for j in data["ItemRange"])
load_balance_item = mdl.sum(mdl.abs(
mdl.sum(data["Var"][(i,j)] * data["ItemWeight"][j] for j in data["ItemRange"]) - 30)
for i in data["KnapsackRange"])
data["Lambda1"] = 2.0
data["Lambda2"] = 3.0
mdl.set_objective("max", data["Lambda1"] * total_parcel_item + data["Lambda2"] * load_balance_item)
# 輸出模型信息
mdl.print_information()
mdl.get_objective_expr()
約束的修改
我們本來的UniquenessConstraint要求每個物品最多被裝入背包一次,假如說我們需要修改約束,使得每個物品都必須被裝入背包,我們可以直接用Constraint.set_sense()來實現(xiàn),例如:
# 通過約束名來定位到要修改的約束
# 這里我們要修改的是UniquenessConstraint
for j in data["ItemRange"]:
print(mdl.get_constraint_by_name("UniquenessConstraint{}".format(j+1)))
可以看到當前模型中的UniquenessConstraint:
UniquenessConstraint1: x_0_0+x_1_0+x_2_0+x_3_0+x_4_0 <= 1
UniquenessConstraint2: x_0_1+x_1_1+x_2_1+x_3_1+x_4_1 <= 1
UniquenessConstraint3: x_0_2+x_1_2+x_2_2+x_3_2+x_4_2 <= 1
UniquenessConstraint4: x_0_3+x_1_3+x_2_3+x_3_3+x_4_3 <= 1
UniquenessConstraint5: x_0_4+x_1_4+x_2_4+x_3_4+x_4_4 <= 1
UniquenessConstraint6: x_0_5+x_1_5+x_2_5+x_3_5+x_4_5 <= 1
UniquenessConstraint7: x_0_6+x_1_6+x_2_6+x_3_6+x_4_6 <= 1
UniquenessConstraint8: x_0_7+x_1_7+x_2_7+x_3_7+x_4_7 <= 1
UniquenessConstraint9: x_0_8+x_1_8+x_2_8+x_3_8+x_4_8 <= 1
UniquenessConstraint10: x_0_9+x_1_9+x_2_9+x_3_9+x_4_9 <= 1
UniquenessConstraint11: x_0_10+x_1_10+x_2_10+x_3_10+x_4_10 <= 1
UniquenessConstraint12: x_0_11+x_1_11+x_2_11+x_3_11+x_4_11 <= 1
UniquenessConstraint13: x_0_12+x_1_12+x_2_12+x_3_12+x_4_12 <= 1
UniquenessConstraint14: x_0_13+x_1_13+x_2_13+x_3_13+x_4_13 <= 1
UniquenessConstraint15: x_0_14+x_1_14+x_2_14+x_3_14+x_4_14 <= 1
UniquenessConstraint16: x_0_15+x_1_15+x_2_15+x_3_15+x_4_15 <= 1
UniquenessConstraint17: x_0_16+x_1_16+x_2_16+x_3_16+x_4_16 <= 1
UniquenessConstraint18: x_0_17+x_1_17+x_2_17+x_3_17+x_4_17 <= 1
UniquenessConstraint19: x_0_18+x_1_18+x_2_18+x_3_18+x_4_18 <= 1
UniquenessConstraint20: x_0_19+x_1_19+x_2_19+x_3_19+x_4_19 <= 1
通過set_sense()方法,我們將 <=修改為==
# 對UniquenessConstraint,將不等號修改為等號
for j in data["ItemRange"]:
mdl.get_constraint_by_name("UniquenessConstraint{}".format(j+1)).set_sense("eq")
# 打印修改過的約束
for j in data["ItemRange"]:
print(mdl.get_constraint_by_name("UniquenessConstraint{}".format(j+1)))
通過檢查,我們發(fā)現(xiàn)所有的不大于都改為了等于:
UniquenessConstraint1: x_0_0+x_1_0+x_2_0+x_3_0+x_4_0 == 1
UniquenessConstraint2: x_0_1+x_1_1+x_2_1+x_3_1+x_4_1 == 1
UniquenessConstraint3: x_0_2+x_1_2+x_2_2+x_3_2+x_4_2 == 1
UniquenessConstraint4: x_0_3+x_1_3+x_2_3+x_3_3+x_4_3 == 1
UniquenessConstraint5: x_0_4+x_1_4+x_2_4+x_3_4+x_4_4 == 1
UniquenessConstraint6: x_0_5+x_1_5+x_2_5+x_3_5+x_4_5 == 1
UniquenessConstraint7: x_0_6+x_1_6+x_2_6+x_3_6+x_4_6 == 1
UniquenessConstraint8: x_0_7+x_1_7+x_2_7+x_3_7+x_4_7 == 1
UniquenessConstraint9: x_0_8+x_1_8+x_2_8+x_3_8+x_4_8 == 1
UniquenessConstraint10: x_0_9+x_1_9+x_2_9+x_3_9+x_4_9 == 1
UniquenessConstraint11: x_0_10+x_1_10+x_2_10+x_3_10+x_4_10 == 1
UniquenessConstraint12: x_0_11+x_1_11+x_2_11+x_3_11+x_4_11 == 1
UniquenessConstraint13: x_0_12+x_1_12+x_2_12+x_3_12+x_4_12 == 1
UniquenessConstraint14: x_0_13+x_1_13+x_2_13+x_3_13+x_4_13 == 1
UniquenessConstraint15: x_0_14+x_1_14+x_2_14+x_3_14+x_4_14 == 1
UniquenessConstraint16: x_0_15+x_1_15+x_2_15+x_3_15+x_4_15 == 1
UniquenessConstraint17: x_0_16+x_1_16+x_2_16+x_3_16+x_4_16 == 1
UniquenessConstraint18: x_0_17+x_1_17+x_2_17+x_3_17+x_4_17 == 1
UniquenessConstraint19: x_0_18+x_1_18+x_2_18+x_3_18+x_4_18 == 1
UniquenessConstraint20: x_0_19+x_1_19+x_2_19+x_3_19+x_4_19 == 1
重新建模和修改的速度對比
其實除了在原模型基礎上修改以外,我們也可以直接新建一個模型來求解,毫無疑問在改動幅度不大時,前者會快很多,這里我們可以做一個簡單的對比:
直接重建新模型的用時
%%time
mdl2 = Model("Test model", cts_by_name=True)
### Change the floats to check the outcomings
data["Lambda1"] = 2.0 # Relative weight of total assigned parcels
data["Lambda2"] = 3.0 # Relative weight of load balance
data["Var"] = mdl2.binary_var_dict([(i, j) for i in data["KnapsackRange"] for j in data["ItemRange"]], name='x')
constraints = [MaxWeightObj(), CapacityConstraint(), CountConstraint(), UniquenessConstraint()]
for cons in constraints:
cons.add(mdl2, data)
得到結果:
CPU times: user 14.7 ms, sys: 8.89 ms, total: 23.6 ms
Wall time: 28.7 ms
在原模型基礎上進行修改的用時
%%time
total_parcel_item = mdl.sum(data["Var"][(i,j)] * data["ItemWeight"][j] for i in data["KnapsackRange"] for j in data["ItemRange"])
load_balance_item = mdl.sum(mdl.abs(
mdl.sum(data["Var"][(i,j)] * data["ItemWeight"][j] for j in data["ItemRange"]) - 30)
for i in data["KnapsackRange"])
data["Lambda1"] = 2.0
data["Lambda2"] = 3.0
mdl.set_objective("max", data["Lambda1"] * total_parcel_item + data["Lambda2"] * load_balance_item)
for j in data["ItemRange"]:
mdl.get_constraint_by_name("UniquenessConstraint{}".format(j+1)).set_sense("eq")
得到結果
CPU times: user 5.43 ms, sys: 159 μs, total: 5.58 ms
Wall time: 5.54 ms