CPLEX杂记(三) 热启动

对于一个MIP问题来说,找初始可行解是一个比较费时的过程,如果我们能够在求解开始时就为问题提供一个较好的初始解(不一定是可行的),那么可以大大减少求解器找初始可行解的计算量,加快求解速度。从我们提供的初始解开始进行问题求解,这就叫做热启动。

需要注意的是,在MIP的精确求解过程中,热启动并不保证能够缩小求解器的搜索范围,因此如果我们提供的初始解并不可行甚至离可行域相去甚远,那么并不会有好的加速效果。

下面我们用一个例子来看如何为CPLEX求解器添加热启动,并且简单对比一下热启动和冷启动时的求解速度。

例子与初始解

还是以背包问题为例,先进行初始问题的构建和求解:

# 导入包
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)
### Change the floats to check the outcomings
data["Lambda1"] = 1.0 # Relative weight of total assigned parcels
data["Lambda2"] = 1.0 # Relative weight of load balance
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()
# Solve model
sol_run1 = mdl.solve(log_output = True)

求解过程如下:

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
Version identifier: 12.10.0.0 | 2019-11-26 | 843d4de
CPXPARAM_Read_DataCheck                          1
Tried aggregator 1 time.
MIP Presolve eliminated 10 rows and 10 columns.
Reduced MIP has 35 rows, 110 columns, and 410 nonzeros.
Reduced MIP has 100 binaries, 0 generals, 5 SOSs, and 0 indicators.
Presolve time = 0.00 sec. (0.23 ticks)
Probing time = 0.00 sec. (0.07 ticks)
Tried aggregator 1 time.
Detecting symmetries...
Reduced MIP has 35 rows, 110 columns, and 410 nonzeros.
Reduced MIP has 100 binaries, 0 generals, 5 SOSs, and 0 indicators.
Presolve time = 0.00 sec. (0.29 ticks)
Probing time = 0.00 sec. (0.07 ticks)
Clique table members: 27.
MIP emphasis: balance optimality and feasibility.
MIP search method: dynamic search.
Parallel mode: deterministic, using up to 16 threads.
Root relaxation solution time = 0.00 sec. (0.17 ticks)

        Nodes                                         Cuts/
   Node  Left     Objective  IInf  Best Integer    Best Bound    ItCnt     Gap

      0     0     unbounded                                        113         
      0     2     unbounded                                        118         
Elapsed time = 0.02 sec. (1.15 ticks, tree = 0.02 MB, solutions = 0)
*    24+    9                          164.0000                           0.00%
*    34+   13                          180.0000                           0.00%
*    45    25      integral     0      184.0000                    193    0.00%
*    46+   13                          186.0000                           0.00%
*    76    15      integral     0      200.0000                    272    0.00%
*    99    15      integral     0      204.0000                    353    0.00%
*   148+   18                          216.0000                           0.00%
*   162    23      integral     0      218.0000      226.0000      590    3.67%
*   292    28      integral     0      222.0000      226.0000      720    1.80%
*   329    22      integral     0      226.0000      226.0000      793    0.00%

Cover cuts applied:  33

Root node processing (before b&c):
  Real time             =    0.02 sec. (1.13 ticks)
Parallel b&c, 16 threads:
  Real time             =    0.05 sec. (4.54 ticks)
  Sync time (average)   =    0.03 sec.
  Wait time (average)   =    0.00 sec.
                          ------------
Total (root+branch&cut) =    0.07 sec. (5.67 ticks)
CPU times: user 481 ms, sys: 76.6 ms, total: 557 ms
Wall time: 156 ms

为了进行对比,我们将问题稍作变换,修改一下目标函数:

# 修改目标函数中两项的相对权重,并且修改模型中的目标函数total_weight_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_weight_item + data["Lambda2"] * load_balance_item)

添加初始解,进行热启动

将原问题的解作为初始解,传入修改之后的问题中,进行求解:

%%time
# 添加热启动之后,进行求解
mdl2 = mdl.clone()
mdl2.add_mip_start(sol_run1)
mdl2.solve(log_output = True, clean_before_solve=True)

求解过程如下:

Version identifier: 12.10.0.0 | 2019-11-26 | 843d4de
CPXPARAM_Read_DataCheck                          1
1 of 1 MIP starts provided solutions.
MIP start 'm1' defined initial solution with objective 512.0000.
Tried aggregator 1 time.
MIP Presolve eliminated 20 rows and 20 columns.
Reduced MIP has 40 rows, 120 columns, and 520 nonzeros.
Reduced MIP has 100 binaries, 0 generals, 10 SOSs, and 0 indicators.
Presolve time = 0.00 sec. (0.28 ticks)
Probing time = 0.00 sec. (0.08 ticks)
Tried aggregator 1 time.
Detecting symmetries...
Reduced MIP has 40 rows, 120 columns, and 520 nonzeros.
Reduced MIP has 100 binaries, 0 generals, 10 SOSs, and 0 indicators.
Presolve time = 0.00 sec. (0.35 ticks)
Probing time = 0.00 sec. (0.08 ticks)
Clique table members: 27.
MIP emphasis: balance optimality and feasibility.
MIP search method: dynamic search.
Parallel mode: deterministic, using up to 16 threads.
Root relaxation solution time = 0.00 sec. (0.21 ticks)

        Nodes                                         Cuts/
   Node  Left     Objective  IInf  Best Integer    Best Bound    ItCnt     Gap

*     0+    0                          512.0000                           0.00%
      0     0     unbounded            512.0000                    123         
      0     2     unbounded            512.0000                    147         
Elapsed time = 0.02 sec. (1.57 ticks, tree = 0.02 MB, solutions = 1)
*   520+   37                          513.0000                           0.00%
*   572+   18                          514.0000                           0.00%
*   633    20      integral     0      515.0000                   7775    0.00%
*   886+   16                          519.0000                           0.00%

Cover cuts applied:  11

Root node processing (before b&c):
  Real time             =    0.02 sec. (1.44 ticks)
Parallel b&c, 16 threads:
  Real time             =    0.08 sec. (16.46 ticks)
  Sync time (average)   =    0.04 sec.
  Wait time (average)   =    0.00 sec.
                          ------------
Total (root+branch&cut) =    0.10 sec. (17.90 ticks)
CPU times: user 639 ms, sys: 42.6 ms, total: 682 ms
Wall time: 143 ms

可以看到,在639ms之后,得到了新问题的解。

冷启动进行求解

我们复制一下问题,清除保存的求解过程(否则CPLEX会自动从保存的求解过程继续向下寻找最优解),然后在冷启动的情况下求解:

%%time
# 冷启动进行求解
mdl3 = mdl.clone()
mdl3.solve(log_output = True, clean_before_solve=True)

求解过程如下:

Version identifier: 12.10.0.0 | 2019-11-26 | 843d4de
CPXPARAM_Read_DataCheck                          1
Tried aggregator 1 time.
MIP Presolve eliminated 20 rows and 20 columns.
Reduced MIP has 40 rows, 120 columns, and 520 nonzeros.
Reduced MIP has 100 binaries, 0 generals, 10 SOSs, and 0 indicators.
Presolve time = 0.00 sec. (0.28 ticks)
Probing time = 0.00 sec. (0.08 ticks)
Tried aggregator 1 time.
Detecting symmetries...
Reduced MIP has 40 rows, 120 columns, and 520 nonzeros.
Reduced MIP has 100 binaries, 0 generals, 10 SOSs, and 0 indicators.
Presolve time = 0.00 sec. (0.35 ticks)
Probing time = 0.00 sec. (0.08 ticks)
Clique table members: 27.
MIP emphasis: balance optimality and feasibility.
MIP search method: dynamic search.
Parallel mode: deterministic, using up to 16 threads.
Root relaxation solution time = 0.00 sec. (0.21 ticks)

        Nodes                                         Cuts/
   Node  Left     Objective  IInf  Best Integer    Best Bound    ItCnt     Gap

      0     0     unbounded                                        123         
      0     2     unbounded                                        147         
Elapsed time = 0.04 sec. (1.50 ticks, tree = 0.02 MB, solutions = 0)
*    98    29      integral     0      416.0000                    578    0.00%
*   281+   44                          442.0000                           0.00%
*   372    70      integral     0      464.0000                   3591    0.00%
*   604    60      integral     0      467.0000                   5497    0.00%
*   609+   52                          469.0000                           0.00%
*   640    34      integral     0      472.0000                   7543    0.00%
*   651    33      integral     0      476.0000                   7548    0.00%
*   657    32      integral     0      477.0000                   7572    0.00%
*   675    34      integral     0      479.0000                   7767    0.00%
*   881    41      integral     0      507.0000                   8011    0.00%
*  1048+   27                          509.0000      519.0000             1.96%
*  1089+   24                          513.0000      519.0000             1.17%
*  1106    16      integral     0      518.0000      519.0000    10449    0.19%
*  1226    16      integral     0      519.0000      519.0000    10476    0.00%

Cover cuts applied:  32
Mixed integer rounding cuts applied:  1

Root node processing (before b&c):
  Real time             =    0.02 sec. (1.42 ticks)
Parallel b&c, 16 threads:
  Real time             =    0.15 sec. (20.18 ticks)
  Sync time (average)   =    0.08 sec.
  Wait time (average)   =    0.00 sec.
                          ------------
Total (root+branch&cut) =    0.17 sec. (21.59 ticks)
CPU times: user 1.21 s, sys: 66.4 ms, total: 1.27 s
Wall time: 232 ms

可以看到,在没有热启动的情况下,1.21s之后我们得到了解。这比使用热启动的求解要慢上不少。

你可能感兴趣的:(CPLEX杂记(三) 热启动)