You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Gurobi Pool Search Mode重复输出相同线路规划解问题咨询

公共交通线路规划线性规划问题:Gurobi Pool Search Mode重复输出相同解

我目前研究公共交通领域的线路规划问题(线性规划),参考《Models for Line Planning in Public Transport》构建模型。在Python中实现了以直接乘客量与成本加权和为目标的模型,通过Gurobi求解。为获取所有可行解,启用了Gurobi的Pool Search Mode,但代码仅能输出最优解,却重复输出相同的解,无法获取所有不同的可行解及其对应的combined_obj值。对比输出的可行解参数未发现差异,推测是约束未对直接乘客分配做特定限制导致。现寻求Gurobi的解决方案,实现仅输出参数相同的结果一次。

代码实现

if __name__ == '__main__':
    if len(sys.argv) < 2:
        raise ConfigNoFileNameGivenException()
    logger.info("Start reading configuration")
    config = ConfigReader.read(sys.argv[1])
    #parameters = SolverParameters(config, "lc_")
    parameters = DirectParameters(config)

    logger.info("Finished reading configuration")

    logger.info("Begin reading input data")
    ptn = PTNReader.read(read_loads=True)
    od = DictOD()
    #od = ODReader.read(od)


    ods = ODReader.readMultiple(od)
    logger.info(f"Read in {len(ods)} OD matrices")
    logger.info("Finished reading input data")

    for index_of_od_matrix,od in enumerate(ods):


        logger.info(f"Begin execution of robust multicrieteria line planning model for OD-Matrix {index_of_od_matrix}")
        line_pool = LineReader.read(ptn, read_frequencies=False)
        solver = Solver.createSolver(parameters.getSolverType())
        model = solver.createModel()
        logger.debug("Add variables")
        # ToDo: add variables
        frequencies = {}
        for line in line_pool.getLines():
            frequency = model.addVariable(0, float('inf'), VariableType.INTEGER, name=f"f_{line.getId()}")
            frequencies[line] = frequency

        acceptableLineIds = compute_acceptable_line_ids(line_pool, compute_shortest_paths(ptn, od, parameters), ptn)

        d = {}
        for origin in ptn.getNodes():
            d[origin.getId()] = {}
            for destination in ptn.getNodes():
                d[origin.getId()][destination.getId()] = {}
                sumOfAllVariablesPerODPair = None
                if origin == destination or od.getValue(origin.getId(), destination.getId()) == 0:
                    continue
                for lineId in acceptableLineIds[(origin.getId(), destination.getId())]:
                    d[origin.getId()][destination.getId()][lineId] = model.addVariable(0, od.getValue(origin.getId(),
                                                                                                      destination.getId()),
                                                                                       VariableType.INTEGER,
                                                                                       name=f"d{origin.getId()}{destination.getId()}{lineId}")
                    if sumOfAllVariablesPerODPair is None:
                        sumOfAllVariablesPerODPair = d[origin.getId()][destination.getId()][lineId]
                    else:
                        sumOfAllVariablesPerODPair += d[origin.getId()][destination.getId()][lineId]

                if not sumOfAllVariablesPerODPair:
                    sumOfAllVariablesPerODPair = 0

                model.addConstraint(sumOfAllVariablesPerODPair, ConstraintSense.LESS_EQUAL,
                                    od.getValue(origin.getId(), destination.getId()),
                                    name=f"od_constraint{origin.getId()}_{destination.getId()}")

        logger.debug("Adding more constraints")
        # ToDo: add constraints
        for link in ptn.getEdges():
            sum_freq_per_line = model.createExpression()
            for line in line_pool.getLines():
                if link in line.getLinePath().getEdges():
                    sum_freq_per_line.add(frequencies[line])
            model.addConstraint(sum_freq_per_line, ConstraintSense.LESS_EQUAL, link.getUpperFrequencyBound(),
                                f"u_{link.getId()}")
            model.addConstraint(sum_freq_per_line, ConstraintSense.GREATER_EQUAL, link.getLowerFrequencyBound(),
                                f"l_{link.getId()}")

        logger.debug("Add capacity constraints")
        # Constraint 3.7 -> Ensure that the capacity of each line is not exceeded
        capacity = parameters.get_capacity()
        for link in ptn.getEdges():
            direct_travellers_on_line_and_edge = {}
            for line in line_pool.getLines():
                if link not in line.getLinePath().getEdges():
                    continue
                for origin in ptn.getNodes():
                    for destination in ptn.getNodes():
                        if origin == destination or od.getValue(origin.getId(), destination.getId()) == 0:
                            continue
                        od_pair = (origin.getId(), destination.getId())
                        if line.getId() not in acceptableLineIds.get(od_pair):
                            continue
                        if link not in acceptableLineIds.get(od_pair).get(line.getId()).getEdges():
                            continue

                        if line.getId() not in direct_travellers_on_line_and_edge:
                            direct_travellers_on_line_and_edge[line.getId()] = model.createExpression()
                        direct_travellers_on_line_and_edge[line.getId()].multiAdd(1,
                                                                                  d.get(origin.getId()).get(
                                                                                      destination.getId()).get(
                                                                                      line.getId()))
                capacity_of_line = model.createExpression()
                capacity_of_line.multiAdd(capacity, frequencies.get(line))
                # There may be a line that is not acceptable for anybody
                direct_travellers_on_line = direct_travellers_on_line_and_edge.get(line.getId())
                if direct_travellers_on_line is not None:
                    model.addConstraint(direct_travellers_on_line, ConstraintSense.LESS_EQUAL, capacity_of_line,
                                        "capacity_constraint_" + str(link.getId()) + "_" + str(line.getId()))

        logger.debug("Add parameters")
        parameters.setSolverParameters(model)
        # model.setSense(OptimizationSense.MINIMIZE)

        #objectives = list()
        model.getObjective().clear()
        obj = None
        for line, frequency in frequencies.items():
            if obj is None:
                obj = line.getCost() * frequency
            else:
                obj += line.getCost() * frequency

        #objectives.append(obj)

        obj2 = None
        for origin, destinations in d.items():
            for destination, lines in destinations.items():
                for line, variable in lines.items():
                    if obj2 is None:
                        obj2 = variable
                    else:
                        obj2 += variable

        #objectives.append(obj2)
        if obj2:
            combined_obj = obj - obj2
        else:
            combined_obj = obj

解决方案

针对Gurobi重复输出相同解的问题,可通过以下配置和优化解决:

1. 配置解池参数,过滤重复解

Gurobi默认允许解池中存在等价解(变量值不同但目标函数值相同,或对关键决策变量无影响的冗余变量差异)。需设置以下参数:

  • PoolSearchMode:设为2,启用严格的解搜索模式,尽可能找到所有不同的解
  • PoolGap:设置为0.0,仅保留目标值与最优值完全一致的解
  • PoolSolutions:设置最大保留的解数量(按需调整)
  • NonConvex:若模型涉及非凸整数规划,设为2以启用相关求解逻辑

添加参数设置代码:

# 配置解池参数
model.setParam('PoolSearchMode', 2)  # 严格搜索所有不同解
model.setParam('PoolGap', 0.0)       # 仅保留目标值等于最优值的解
model.setParam('PoolSolutions', 100) # 最多保留100个解(可按需修改)
model.setParam('NonConvex', 2)       # 处理非凸整数规划场景(如果适用)

2. 自定义解的唯一性判断

模型中d变量(直接乘客分配)存在冗余:约束仅限制OD对的总分配量,未指定具体分配方式,导致不同d变量组合对应相同的线路频率方案(核心决策变量)。若仅关心线路频率的唯一性,可在获取解时过滤重复的核心变量组合:

# 求解模型
model.optimize()

# 存储已出现的线路频率方案
seen_frequency_schemes = set()

# 遍历解池中的所有解
for sol_idx in range(model.SolCount):
    model.setParam('SolutionNumber', sol_idx)
    # 获取核心变量frequencies的值,转为可哈希的元组
    freq_tuple = tuple((line.getId(), round(model.getVarByName(f"f_{line.getId()}").Xn, 4)) for line in line_pool.getLines())
    # 检查是否已出现过该方案
    if freq_tuple not in seen_frequency_schemes:
        seen_frequency_schemes.add(freq_tuple)
        # 输出该解的信息
        print(f"唯一解 {len(seen_frequency_schemes)}:")
        print(f"目标函数值: {model.PoolObjVal}")
        for line_id, freq in freq_tuple:
            print(f"线路 {line_id} 频率: {freq}")
        # 如需输出d变量值可在此添加逻辑

3. 优化模型约束减少冗余

若不需要d变量的所有可能分配,可添加约束限制其唯一性,例如:

  • 对每个OD对,指定优先分配到某条线路
  • 或删除d变量,将直接乘客量计算整合到目标函数中(若业务逻辑允许)

从根源上减少等价解的产生,让解池中的解均为真正不同的决策方案。


内容的提问来源于stack exchange,提问作者2XCL

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 16:15:57