7.3. Pyomo 的建模与优化

Pyomo 是基于 Python 开发的第三方开源建模语言,它支持对线性规划、混合整数规划、非线性规划等问题建模和分析,并调用其他商用或开源的求解器进行求解。

目前,MindOpt 可对在 Windows/Linux/macOS 平台上通过 Pyomo 建立的 线性规划模型 进行求解。关于 Pyomo 的更多详细内容,请参考 Pyomo 官方文档库

在本节中,我们将介绍如何使用 Pyomo API 来建立 线性规划问题示例 中的优化问题,并调用 MindOpt 求解。

7.3.1. 安装 Pyomo

用户需首先安装 MindOpt。关于 MindOpt 的安装与配置请参考 单机版安装MindOpt 安装后,用户可以通过以下 pip 的命令方式来安装 Pyomo:

pip install pyomo

关于 Pyomo 的详细安装方式,请参考 Pyomo Installation

7.3.2. Pyomo 调用接口

MindOpt 为Pyomo提供两种不同的插件接口,分别是mindopt_direct和mindopt_persistent。

mindopt_direct是一种简单的接口,它在每次调用求解器时都会重新建立一个MindOpt模型,并通过将pyomo模型中的约束和变量转化为MindOpt模型的约束和变量来进行求解。这种接口适用于简单的优化问题,对于复杂的问题可能会导致一些求解器性能上的损失。

mindopt_persistent是一种更高级的接口,它将模型对象存于内存中,允许多次修改和重复求解。对于需要多次求解相同模型的问题非常有用,可以避免重复创建和销毁模型对象产生的开销,从而提高求解效率。

7.3.2.1. 调用 Pyomo Direct 接口

MindOpt 在 Pyomo Direct接口文件( mindopt_pyomo.py )内定义了 Pyomo 调用 MindOpt 所需的相关接口。此接口继承自 Pyomo 的 DirectSolver 类别,实现细节见安装包内的接口文件:

<MDOHOME>/<VERSION>/<PLATFORM>/lib/pyomo/mindopt_persistent.py
<MDOHOME>/<VERSION>/<PLATFORM>/lib/pyomo/mindopt_pyomo.py

用户在使用时需首先将该接口文件移到当前工作目录中,并在 Python 代码中加载该模块中定义的 MindoptDirect 类:

30from mindopt_pyomo import MindoptDirect

同时,我们需要导入Pyomo库:

25from pyomo.environ import *
26from pyomo.core.base.initializer import Initializer
27from pyomo.opt import SolverFactory
28from pyomo.opt import SolverStatus, TerminationCondition

接着,我们调用 Pyomo API 来建立 线性规划问题示例 中的优化问题。关于 Pyomo API 的详细说明,请参考 Pyomo 官方文档API

33def ConstraintsRule(model, p):
34    return sum(constraint_coeff[i, p] * model.Variable[i] for i in variable_names)
35
36def fb(model, i):
37    return (var_lb[i], var_ub[i])
38
39# Define variables and constraints.
40variable_names = ['x0', 'x1', 'x2', 'x3']
41var_lb = {'x0':0, 'x1':0, 'x2':0, 'x3':0}
42var_ub = {'x0':10, 'x1':None, 'x2':None, 'x3':None}
43objective_coeff = {'x0': 1, 'x1': 1, 'x2': 1, 'x3': 1}
44constraint_names = ['c0', 'c1']
45constraint_bound = {'c0': 1, 'c1': 1}
46constraint_coeff = {('x0', 'c0'): 1, ('x1', 'c0'): 1, ('x2', 'c0'): 2, ('x3', 'c0'): 3,
47                    ('x0', 'c1'): 1, ('x1', 'c1'): -1, ('x2', 'c1'): 0, ('x3', 'c1'): 6}
48
49# Create model.
50model = ConcreteModel(name="ex1")
51
52# Build decision variables.
53model.Set = Set(initialize=variable_names)
54model.Variable = Var(model.Set, within=NonNegativeReals, bounds=fb)
55
56# Objective.
57model.obj = Objective(expr=sum(objective_coeff[var_name] * model.Variable[var_name] for var_name in variable_names), sense=minimize)
58
59# Constraints.
60model.dual = Suffix(direction=Suffix.IMPORT)
61model.cons1 = Constraint(expr = ConstraintsRule(model, 'c0') >= constraint_bound['c0'])
62model.cons2 = Constraint(expr = ConstraintsRule(model, 'c1') == constraint_bound['c1'])

求解前,我们指定使用 MindOpt 求解器,并对求解的相关参数进行设置(求解器参数请查阅 参数):

67# Solve problem by MindOpt solver.
68opt = SolverFactory("mindopt_direct")
69
70# Set options.
71opt.options['Method'] = -1
72opt.options['IPM/PrimalTolerance'] = 1e-10

最后,调用 Pyomo 的求解函数 solve() 进行求解,并获取相关的结果:

74# Solve.
75results = opt.solve(model)
76
77if (results.solver.status == SolverStatus.ok) and (results.solver.termination_condition == TerminationCondition.optimal):
78    print("The solution is optimal and feasible.")
79    model.Variable.display()
80    model.dual.display()
81    model.obj.display()
82elif (results.solver.termination_condition == TerminationCondition.infeasible):
83    print("The model is infeasible.")
84    print("Solver Status: ",  results.solver.status)
85else:
86    print("Something else is wrong.")
87    print("Solver Status: ",  results.solver.status)

7.3.2.2. 调用 Pyomo Persistent 接口文件

MindOpt 在 Pyomo Persistent接口文件( mindopt_persistent.py )内定义了 Pyomo 调用 MindOpt 所需的相关persistent接口。此接口继承自 Pyomo 的 PersistentSolver 类别,实现细节见安装包内的接口文件:

<MDOHOME>/<VERSION>/<PLATFORM>/lib/pyomo/mindopt_persistent.py

用户在使用时需首先将该接口文件移到当前工作目录中,并在 Python 代码中加载该模块中定义的 MindoptPersistent 类:

from mindopt_persistent import MindoptPersistent

同时,导入Pyomo库:

from pyomo.environ import *
from pyomo.core.base.initializer import Initializer
from pyomo.opt import SolverFactory
from pyomo.opt import SolverStatus, TerminationCondition

接着,我们调用 Pyomo API 来建立优化问题。关于 Pyomo API 的详细说明,请参考 Pyomo 官方文档API

model = ConcreteModel()

products = ['A', 'B']
materials = ['X', 'Y']

model.x = Var(products, domain=NonNegativeReals)

model.profit = Objective(expr=10 * model.x['A'] + 15 * model.x['B'], sense=maximize)

model.material_x = Constraint(expr=2 * model.x['A'] + model.x['B'] <= 10)
model.material_y = Constraint(expr=model.x['A'] + 2 * model.x['B'] <= 6)

solver = SolverFactory('mindopt_persistent')

在求解前必须使用set_instance将模型保存在内存中。

solver.set_instance(model)
solver.solve(model)

print(solver._solver_model.objval)

求解后也可以使用Persistent提供的函数获取模型属性/参数,也可以修改原有模型。

# Retrieving variable attributes
var_attr = solver.get_var_attr(model.x['A'], 'lb')
print(var_attr)

# Setting and retrieving MindOpt parameters
solver.set_mindopt_param("MaxTime", 1000)
solver.set_mindopt_param("MaxTi*", 100)
print(solver.get_mindopt_param_info("MaxTi*"))

7.3.3. 使用 SOS 功能

SOS(Special Ordered Sets)约束是一种特殊的约束类型,指的是一组有序变量集合中最多只能有N个变量取非零值。SOS约束在某些优化问题中非常有用,比如在调度问题中,可以用SOS约束来确保每个时间点只能选择一个任务执行。

无论是使用mindopt_direct还是mindopt_persistent接口,都可以使用 MindOpt 的SOS功能。

用户在通过Pyomo建模代码使用SOS功能时,需要首先定义一个SOS集合,将特定变量分组到有序集合中。例如,假设有一类变量y属于同一个SOS组,则可实现如下代码:

from pyomo.environ import *
from mindopt_pyomo import MindoptDirect

import pyomo.environ as pyo

N = 1
model = pyo.ConcreteModel()
model.x = pyo.Var([1], domain=pyo.NonNegativeReals, bounds=(0,40))
model.A = pyo.Set(initialize=[1,2,4,6])
model.y = pyo.Var(model.A, domain=pyo.NonNegativeReals, bounds=(0,2))
model.OBJ = pyo.Objective(
    expr=(1*model.x[1]+
          2*model.y[1]+
          3*model.y[2]+
          -0.1*model.y[4]+
          0.5*model.y[6])
)
model.ConstraintYmin = pyo.Constraint(
    expr = (model.x[1]+
            model.y[1]+
            model.y[2]+
            model.y[6] >= 0.25
            )
)
model.mysos = pyo.SOSConstraint(
    var=model.y,
    sos=N
)
opt = pyo.SolverFactory('mindopt_direct')
opt.solve(model, tee=False)
print(opt._solver_model.objval)

在上述示例中, SOSConstraint 的参数var用于指定变量属于SOS集合的规则。在此示例中,我们将变量y分组到同一个SOS集合中。

7.3.4. 使用 Callback 功能

无论是使用mindopt_direct还是mindopt_persistent接口,用户都可以使用 MindOpt 的回调功能,提供一些启发式决策用以优化求解速度。

但是考虑到需要使用回调功能的模型一般比较复杂,推荐用户使用mindopt_persistent接口。

在Pyomo中使用Mindopt的回调函数功能,首先需要定义一个回调函数来处理特定的事件。

import pyomo.environ as pe
from mindopt_persistent import MindoptPersistent
m = pe.ConcreteModel()
m.x = pe.Var(bounds=(0, 4))
m.y = pe.Var(within=pe.Integers, bounds=(0, None))
m.obj = pe.Objective(expr=2*m.x + m.y)
m.cons = pe.ConstraintList() # for the cutting planes

def my_callback(model, where):
    where_names = {
        3: "MIPSTART",
        4: "MIPSOL",
        5: "MIPNODE"
    }

    print("where = {}".format(where_names[where]))

    # Count call times
    model._calls += 1

    # When using the Python SDK, the model passed to the callback is a presolved model. Note that the C SDK uses the original model.
    # Print the constraint matrix of the presolved model here. The getA() function returns a scipy.sparse.csc_matrix.
    # The constraint matrix returned by getA() is a reference, and any modifications to the constraint matrix should not be allowed as it may lead to unforeseen issues.
    print(model.getA())

    cur_sols = model.cbGetSolution(model.getVars())

    # Print the current best solutions
    print(cur_sols)

    rel_sols = model.cbGetNodeRel(model.getVars())

    # Print  the current relaxation solution
    print(rel_sols)

在上述示例中,我们定义了一个名为my_callback的回调函数。在此回调函数中,我们使用cbGet方法获取当前预处理模型的一些信息,比如变量的lower bound等。 然后,您需要将回调函数与Mindopt求解器连接起来,并为特定的事件注册回调函数。

opt = pe.SolverFactory('mindopt_persistent')
opt.set_instance(m)

# Register the callback function
opt.set_callback(my_callback)

results = opt.solve()

在上述示例中,我们使用SolverFactory创建了一个mindopt_persistent求解器实例,并将回调函数my_callback注册到该求解器中。然后,我们使用solve方法解决模型,并触发相应的回调事件。

7.3.5. 建模示例: mdo_pyomo_lo_ex1

文件链接 mdo_pyomo_lo_ex1.py 中提供了完整代码:

 1"""
 2/**
 3 *  Description
 4 *  -----------
 5 *
 6 *  Linear optimization.
 7 *
 8 *  Formulation
 9 *  -----------
10 *
11 *  Minimize
12 *    obj: 1 x0 + 1 x1 + 1 x2 + 1 x3
13 *  Subject To
14 *   c1 : 1 x0 + 1 x1 + 2 x2 + 3 x3 >= 1
15 *   c2 : 1 x0 - 1 x2        + 6 x3 == 1
16 *  Bounds
17 *    0 <= x0 <= 10
18 *    0 <= x1
19 *    0 <= x2
20 *    0 <= x3
21 *  End
22 */
23"""
24
25from pyomo.environ import *
26from pyomo.core.base.initializer import Initializer
27from pyomo.opt import SolverFactory
28from pyomo.opt import SolverStatus, TerminationCondition
29
30from mindopt_pyomo import MindoptDirect
31
32
33def ConstraintsRule(model, p):
34    return sum(constraint_coeff[i, p] * model.Variable[i] for i in variable_names)
35
36def fb(model, i):
37    return (var_lb[i], var_ub[i])
38
39# Define variables and constraints.
40variable_names = ['x0', 'x1', 'x2', 'x3']
41var_lb = {'x0':0, 'x1':0, 'x2':0, 'x3':0}
42var_ub = {'x0':10, 'x1':None, 'x2':None, 'x3':None}
43objective_coeff = {'x0': 1, 'x1': 1, 'x2': 1, 'x3': 1}
44constraint_names = ['c0', 'c1']
45constraint_bound = {'c0': 1, 'c1': 1}
46constraint_coeff = {('x0', 'c0'): 1, ('x1', 'c0'): 1, ('x2', 'c0'): 2, ('x3', 'c0'): 3,
47                    ('x0', 'c1'): 1, ('x1', 'c1'): -1, ('x2', 'c1'): 0, ('x3', 'c1'): 6}
48
49# Create model.
50model = ConcreteModel(name="ex1")
51
52# Build decision variables.
53model.Set = Set(initialize=variable_names)
54model.Variable = Var(model.Set, within=NonNegativeReals, bounds=fb)
55
56# Objective.
57model.obj = Objective(expr=sum(objective_coeff[var_name] * model.Variable[var_name] for var_name in variable_names), sense=minimize)
58
59# Constraints.
60model.dual = Suffix(direction=Suffix.IMPORT)
61model.cons1 = Constraint(expr = ConstraintsRule(model, 'c0') >= constraint_bound['c0'])
62model.cons2 = Constraint(expr = ConstraintsRule(model, 'c1') == constraint_bound['c1'])
63
64# Print formulation of model.
65model.pprint()
66
67# Solve problem by MindOpt solver.
68opt = SolverFactory("mindopt_direct")
69
70# Set options.
71opt.options['Method'] = -1
72opt.options['IPM/PrimalTolerance'] = 1e-10
73
74# Solve.
75results = opt.solve(model)
76
77if (results.solver.status == SolverStatus.ok) and (results.solver.termination_condition == TerminationCondition.optimal):
78    print("The solution is optimal and feasible.")
79    model.Variable.display()
80    model.dual.display()
81    model.obj.display()
82elif (results.solver.termination_condition == TerminationCondition.infeasible):
83    print("The model is infeasible.")
84    print("Solver Status: ",  results.solver.status)
85else:
86    print("Something else is wrong.")
87    print("Solver Status: ",  results.solver.status)

其他 Pyomo 的示例请参考 Pyomo Gallery