在做计算的时候,我们有时候会先进行一些模型试算,根据试算结果修改我们的目标函数和约束。DOCPLEX提供了一系列方法,让我们修改已经建立好的模型中的目标函数以及约束,这里我们通过小例子看一下这些方法。
例子
这里我们以一个多重背包问题为例,假设我们有一些背包和一些重物,我们当前的目标是将重物尽可能装入背包中,使得装入物品的总质量最大,那么我们的模型可以以如下方式创建:
# 导入包
from docplex.mp.model import Model
import pandas as pd
import numpy as np
# 创建数据
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') # 向数据中添加变量
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)
目标函数的修改
下面如果说我们需要改变我们的目标函数:在最大化装入物品的总质量之余,还要求每个背包内的物品重量尽可能均匀。
那么我们可以用model.set_objective(sense, obj_fun)
方法来修改模型的目标函数,sense
参数代表最大化或者最小化目标函数,可以直接传入string "max/min"来指定;obj_fun
表示我们新指定的目标函数。
具体到我们的例子中,可以用如下代码进行修改:
# 修改目标函数中两项的相对权重,并且修改模型中的目标函数
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()
来实现,例如:
# 通过约束名来定位到要修改的约束
# 这里我们要修改的是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)))
通过检查,我们发现所有的不大于都改为了等于:
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