CPLEX雜記(二) 已有模型目標函數(shù)和約束的修改

在做計算的時候,我們有時候會先進行一些模型試算,根據(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
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容