OR-Tools(Google Optimization Tools)是谷歌开源的优化工具库,支持线性规划(LP)、混合整数规划(MIP)等多种问题,内置 CBC、SCIP 等高效求解器。结合 ojAlgo 的模型构建能力与 OR-Tools 的求解能力,可在最小化代码修改的前提下,提升复杂优化问题的求解效率。本文以具体示例介绍这种整合方案。
方案优势
- 模型构建复用:用 ojAlgo 的直观 API 定义变量和约束,无需重写模型逻辑。
- 求解能力增强:OR-Tools 支持多种高效求解器(如 SCIP、CBC),尤其擅长处理大规模整数规划问题。
- 低侵入性:仅需修改求解部分代码,业务逻辑(变量、约束定义)保持不变。
环境配置
依赖引入
在pom.xml中添加 ojAlgo 和 OR-Tools 的依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13
| <dependency> <groupId>org.ojalgo</groupId> <artifactId>ojAlgo</artifactId> <version>51.4.1</version> </dependency>
<dependency> <groupId>com.google.ortools</groupId> <artifactId>ortools-java</artifactId> <version>8.2.9025</version> </dependency>
|
问题定义
求解以下整数线性规划问题(与前文 ojAlgo 示例一致):
目标函数:minimize 5x₁ + 6x₂ + 23x₃ + 5x₄ + 24x₅ + 6x₆ + 23x₇ + 5x₈
约束条件:
2x₁ + x₂ + x₃ + x₄ ≥ 100
2x₂ + x₃ + 3x₅ + 2x₆ + x₇ ≥ 150
x₁ + x₃ + 3x₄ + 2x₆ + 3x₇ + 5x₈ ≥ 100
变量约束:所有变量为非负整数(xᵢ ≥ 0且为整数)
代码实现与解析
核心逻辑
- 用 ojAlgo 构建模型:定义变量、目标函数和约束条件(与纯 ojAlgo 方案完全一致)。
- 用 OR-Tools 求解:通过自定义的
SolverORTools类,将 ojAlgo 模型转换为 OR-Tools 可处理的格式,调用 SCIP 求解器求解。
完整代码
1. 模型构建与求解入口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| import org.ojalgo.optimisation.ExpressionsBasedModel; import org.ojalgo.optimisation.Expression; import org.ojalgo.optimisation.Optimisation; import org.ojalgo.optimisation.Variable;
public class OjAlgoOrToolsDemo { public static void main(String[] args) { ExpressionsBasedModel model = new ExpressionsBasedModel();
model.addVariable(Variable.makeInteger("x1").lower(0).weight(5)); model.addVariable(Variable.makeInteger("x2").lower(0).weight(6)); model.addVariable(Variable.makeInteger("x3").lower(0).weight(23)); model.addVariable(Variable.makeInteger("x4").lower(0).weight(5)); model.addVariable(Variable.makeInteger("x5").lower(0).weight(24)); model.addVariable(Variable.makeInteger("x6").lower(0).weight(6)); model.addVariable(Variable.makeInteger("x7").lower(0).weight(23)); model.addVariable(Variable.makeInteger("x8").lower(0).weight(5));
Expression constraint1 = model.addExpression("constraint1"); constraint1.set(0, 2); constraint1.set(1, 1); constraint1.set(2, 1); constraint1.set(3, 1); constraint1.lower(100);
Expression constraint2 = model.addExpression("constraint2"); constraint2.set(1, 2); constraint2.set(2, 1); constraint2.set(4, 3); constraint2.set(5, 2); constraint2.set(6, 1); constraint2.lower(150);
Expression constraint3 = model.addExpression("constraint3"); constraint3.set(0, 1); constraint3.set(2, 1); constraint3.set(3, 3); constraint3.set(5, 2); constraint3.set(6, 3); constraint3.set(7, 5); constraint3.lower(100);
SolverORTools solver = SolverORTools.INTEGRATION.build(model); Optimisation.Result result = solver.solve(null);
System.out.println("求解状态:" + result.getState()); System.out.println("目标函数最小值:" + result.getValue()); System.out.println("变量最优解:"); double[] solution = result.getSolution(); for (int i = 0; i < solution.length; i++) { System.out.println("x" + (i + 1) + " = " + solution[i]); } } }
|
该类负责将 ojAlgo 模型转换为 OR-Tools 的MPSolver可处理的格式,是连接两者的核心:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
| import com.google.ortools.Loader; import com.google.ortools.linearsolver.MPConstraint; import com.google.ortools.linearsolver.MPObjective; import com.google.ortools.linearsolver.MPSolver; import com.google.ortools.linearsolver.MPVariable; import org.ojalgo.optimisation.*; import org.ojalgo.structure.IntIndex; import org.ojalgo.structure.Structure1D;
import java.math.BigDecimal; import java.util.Iterator; import java.util.List; import java.util.stream.Collectors;
public final class SolverORTools implements Optimisation.Solver { public static final Integration INTEGRATION = new Integration(); private final MPSolver solver; private final MPVariable[] variables;
static { Loader.loadNativeLibraries(); }
private SolverORTools(MPSolver solver, MPVariable[] variables) { this.solver = solver; this.variables = variables; }
@Override public Result solve(Result kickStarter) { try { MPSolver.ResultStatus status = solver.solve(); State state = translate(status); double objectiveValue = solver.objective().value(); double[] solution = new double[variables.length]; if (state.isFeasible()) { for (int i = 0; i < variables.length; i++) { solution[i] = variables[i].solutionValue(); } } return Result.of(objectiveValue, state, solution); } catch (Exception e) { e.printStackTrace(); return null; } }
private static State translate(MPSolver.ResultStatus status) { switch (status) { case OPTIMAL: return State.OPTIMAL; case FEASIBLE: return State.FEASIBLE; case INFEASIBLE: case UNBOUNDED: return State.INFEASIBLE; default: return State.FAILED; } }
@Override public void dispose() { if (solver != null) { solver.delete(); } }
public static class Integration extends ExpressionsBasedModel.Integration<SolverORTools> { @Override public SolverORTools build(ExpressionsBasedModel model) { MPSolver solver = MPSolver.createSolver("SCIP"); if (solver == null) { throw new RuntimeException("无法创建SCIP求解器"); }
List<Variable> ojVariables = model.getVariables(); int varCount = ojVariables.size(); MPVariable[] mpVariables = new MPVariable[varCount]; double infinity = MPSolver.infinity();
for (int i = 0; i < varCount; i++) { Variable var = ojVariables.get(i); double lower = var.getLowerLimit() != null ? var.getLowerLimit().doubleValue() : 0; double upper = var.getUpperLimit() != null ? var.getUpperLimit().doubleValue() : infinity; boolean isInteger = var.isInteger(); mpVariables[i] = solver.makeVar(lower, upper, isInteger, var.getName()); }
List<Expression> constraints = model.constraints().collect(Collectors.toList()); for (Expression expr : constraints) { double lower = expr.getLowerLimit() != null ? expr.getLowerLimit().doubleValue() : -infinity; double upper = expr.getUpperLimit() != null ? expr.getUpperLimit().doubleValue() : infinity; MPConstraint mpConstraint = solver.makeConstraint(lower, upper, expr.getName());
Iterator<Structure1D.IntIndex> iterator = expr.getLinearKeySet().iterator(); while (iterator.hasNext()) { IntIndex index = iterator.next(); int varIndex = index.index; double coefficient = expr.get(index).doubleValue(); mpConstraint.setCoefficient(mpVariables[varIndex], coefficient); } }
Expression objective = model.objective(); MPObjective mpObjective = solver.objective(); mpObjective.setOptimizationDirection(model.getOptimisationSense() == Sense.MAX); Iterator<Structure1D.IntIndex> objIterator = objective.getLinearKeySet().iterator(); while (objIterator.hasNext()) { IntIndex index = objIterator.next(); int varIndex = index.index; double coefficient = objective.get(index).doubleValue(); mpObjective.setCoefficient(mpVariables[varIndex], coefficient); }
return new SolverORTools(solver, mpVariables); }
@Override public boolean isCapable(ExpressionsBasedModel model) { return !model.isAnyExpressionQuadratic(); }
@Override protected boolean isSolutionMapped() { return false; } } }
|
结果解析
输出结果
1 2 3 4 5 6 7 8 9 10 11
| 求解状态:OPTIMAL 目标函数最小值:600.0 变量最优解: x1 = 30.0 x2 = 40.0 x3 = 0.0 x4 = 0.0 x5 = 0.0 x6 = 35.0 x7 = 0.0 x8 = 0.0
|
结果验证
- 目标函数值:
5×30 + 6×40 + 6×35 = 150 + 240 + 210 = 600,符合最小值要求。
- 约束满足:三个约束均取等号(100、150、100),满足所有条件。
- 多求解器支持:内置 CBC(默认)、SCIP、GLPK 等求解器,可根据问题类型切换(示例中用 SCIP)。
- 高效求解:针对大规模 MIP 问题优化,求解速度优于纯 ojAlgo。
- 无需单独安装求解器:OR-Tools 内置 SCIP 等求解器的二进制库,无需在服务器上手动安装。
- 扩展性强:支持约束规划(CP-SAT)、车辆路径规划(VRP)等复杂问题。
总结
通过 ojAlgo 构建模型、OR-Tools 求解的方案,兼顾了模型定义的简洁性和求解效率。这种整合方式特别适合:
- 已有 ojAlgo 模型代码,需提升求解性能的场景;
- 处理大规模整数规划或混合整数规划问题;
- 需要灵活切换求解器(如从 CBC 切换到 SCIP)的需求。
OR-Tools 的强大求解能力与 ojAlgo 的友好 API 结合,为 Java 优化问题提供了高效且易维护的解决方案