特殊有序集 (SOS)

Pyomo 允许用户在他们的模型中声明特殊有序集(SOS)。 这些是变量集,其中只有一定数量的变量可以是非零的,并且这些非零变量必须按照给定的顺序相邻。

类型1(SOS1)和类型2(SOS2)的特殊有序集是经典的类型,但 这个概念可以推广:类型N的SOS不能有超过N个成员取非零值,并且这些成员必须在集合中是相邻的。 这些对于建模和计算性能目的非常有用。

通过明确声明这些,用户可以保持他们的公式和相应的求解时间比不这样做时更短,因为强制执行SOS的逻辑约束不需要在模型中实现,而是(理想情况下)由求解器算法处理。

特殊有序集可以逐个声明或通过其他集进行索引。

非索引特殊有序集

一个类型为N的单一SOS,涉及pyomo Var组件的所有成员,可以在一行中声明:

# import pyomo
import pyomo.environ as pyo
# declare the model
model = pyo.AbstractModel()
# the type of SOS
N = 1 # or 2, 3, ...
# the set that indexes the variables
model.A = pyo.Set()
# the variables under consideration
model.x = pyo.Var(model.A)
# the sos constraint
model.mysos = pyo.SOSConstraint(var=model.x, sos=N)

在上面的例子中,每个变量的权重是根据它们在pyomo Var组件(model.x)中的位置/顺序自动确定的。

或者,权重可以通过一个pyomo Param组件指定 (model.mysosweights),该组件由同样索引变量的集合索引 (model.A):

# the set that indexes the variables
model.A = pyo.Set()
# the variables under consideration
model.x = pyo.Var(model.A)
# the weights for each variable used in the sos constraints
model.mysosweights = pyo.Param(model.A)
# the sos constraint
model.mysos = pyo.SOSConstraint(
    var=model.x,
    sos=N,
    weights=model.mysosweights
    )

索引特殊有序集

可以创建涉及pyomo Var组件成员(model.x)的N类型的多个SOS,使用两个额外的集合(model.Amodel.mysosvarindexset):

# the set that indexes the variables
model.A = pyo.Set()
# the variables under consideration
model.x = pyo.Var(model.A)
# the set indexing the sos constraints
model.B = pyo.Set()
# the sets containing the variable indexes for each constraint
model.mysosvarindexset = pyo.Set(model.B)
# the sos constraints
model.mysos = pyo.SOSConstraint(
    model.B,
    var=model.x,
    sos=N,
    index=model.mysosvarindexset
    )

在上面的例子中,权重是根据变量的位置自动确定的。或者,它们可以通过pyomo的Param组件(model.mysosweights)和一个额外的集合(model.C)来指定:

# the set that indexes the variables
model.A = pyo.Set()
# the variables under consideration
model.x = pyo.Var(model.A)
# the set indexing the sos constraints
model.B = pyo.Set()
# the sets containing the variable indexes for each constraint
model.mysosvarindexset = pyo.Set(model.B)
# the set that indexes the variables used in the sos constraints
model.C = pyo.Set(within=model.A)
# the weights for each variable used in the sos constraints
model.mysosweights = pyo.Param(model.C)
# the sos constraints
model.mysos = pyo.SOSConstraint(
    model.B,
    var=model.x,
    sos=N,
    index=model.mysosvarindexset,
    weights=model.mysosweights,
    )

使用规则声明特殊有序集

可以说,声明SOS的最佳方式是通过规则。此选项允许用户通过rule参数提供的方法指定变量和权重。如果使用此参数,用户必须指定一个返回以下选项之一的方法:

  • SOS中的变量列表,然后根据它们的位置确定各自的权重;

  • 一个包含两个列表的元组,第一个列表用于SOS中的变量,第二个列表用于相应的权重;

  • 或者,如果不需要声明SOS,则使用pyomo.environ.SOSConstraint.Skip。

如果一个人满足于根据变量的位置来确定权重,那么使用rule参数的以下示例就足够了:

# the set that indexes the variables
model.A = pyo.Set()
# the variables under consideration
model.x = pyo.Var(model.A, domain=pyo.NonNegativeReals)
# the rule method creating the constraint
def rule_mysos(m):
    return [m.x[a] for a in m.x]
# the sos constraint(s)
model.mysos = pyo.SOSConstraint(rule=rule_mysos, sos=N)

如果必须以其他方式确定权重,则以下示例说明了如何使用rule参数为SOS的每个成员指定权重:

# the set that indexes the variables
model.A = pyo.Set()
# the variables under consideration
model.x = pyo.Var(model.A, domain=pyo.NonNegativeReals)
# the rule method creating the constraint
def rule_mysos(m):
    var_list = [m.x[a] for a in m.x]
    weight_list = [i+1 for i in range(len(var_list))]
    return (var_list, weight_list)
# the sos constraint(s)
model.mysos = pyo.SOSConstraint(rule=rule_mysos, sos=N)

rule 参数还允许用户创建包含来自不同 pyomo Var 组件的变量的 SOS,如下所示:

# the set that indexes the x variables
model.A = pyo.Set()
# the set that indexes the y variables
model.B = pyo.Set()
# the set that indexes the SOS constraints
model.C = pyo.Set()
# the x variables, which will be used in the constraints
model.x = pyo.Var(model.A, domain=pyo.NonNegativeReals)
# the y variables, which will be used in the constraints
model.y = pyo.Var(model.B, domain=pyo.NonNegativeReals)
# the x variable indices for each constraint
model.mysosindex_x = pyo.Set(model.C)
# the y variable indices for each constraint
model.mysosindex_y = pyo.Set(model.C)
# the weights for the x variable indices
model.mysosweights_x = pyo.Param(model.A)
# the weights for the y variable indices
model.mysosweights_y = pyo.Param(model.B)
# the rule method with which each constraint c is built
def rule_mysos(m, c):
    var_list = [m.x[a] for a in m.mysosindex_x[c]]
    var_list.extend([m.y[b] for b in m.mysosindex_y[c]])
    weight_list = [m.mysosweights_x[a] for a in m.mysosindex_x[c]]
    weight_list.extend([m.mysosweights_y[b] for b in m.mysosindex_y[c]])
    return (var_list, weight_list)
# the sos constraint(s)
model.mysos = pyo.SOSConstraint(
    model.C,
    rule=rule_mysos,
    sos=N
    )

兼容的求解器

并非所有的LP/MILP求解器都与SOS声明兼容,而且Pyomo可能尚未准备好与所有兼容的求解器进行交互。以下是通过Pyomo已知与特殊有序集兼容的求解器列表:

  • CBC

  • SCIP

  • Gurobi

  • CPLEX

请注意,声明SOS并不能保证求解器最终会将其用作SOS。一些求解器,即Gurobi和CPLEX,如果认为有用,可能会重新表述具有明确SOS声明的问题。

带有非索引SOS约束的完整示例

import pyomo.environ as pyo
from pyomo.opt import check_available_solvers
from math import isclose
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
    )
solver_name = 'scip'
solver_available = bool(check_available_solvers(solver_name))
if solver_available:
     opt = pyo.SolverFactory(solver_name)
     opt.solve(model, tee=False)
     assert isclose(pyo.value(model.OBJ), 0.05, abs_tol=1e-3)