CPLEX杂记(二) 已有模型目标函数和约束的修改

在做计算的时候,我们有时候会先进行一些模型试算,根据试算结果修改我们的目标函数和约束。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

你可能感兴趣的:(CPLEX杂记(二) 已有模型目标函数和约束的修改)