diff --git a/src/main/java/cn/lihongjie/coal/coalBlend/util/RawCoalBlendCalculator.java b/src/main/java/cn/lihongjie/coal/coalBlend/util/RawCoalBlendCalculator.java index d35ba920..912d139c 100644 --- a/src/main/java/cn/lihongjie/coal/coalBlend/util/RawCoalBlendCalculator.java +++ b/src/main/java/cn/lihongjie/coal/coalBlend/util/RawCoalBlendCalculator.java @@ -64,6 +64,38 @@ public class RawCoalBlendCalculator { /** // 添加参数约束 addParameterConstraints(model, coalPercents, coals, constrains, paramFlags); + // 第一步:求出最优解(coalPercents之和的最大值) + CoalBlendResultInfoEntity optimalResult = findOptimalSolution(model, coalPercents, coals, paramFlags, maxTime); + if (optimalResult != null) { + results.add(optimalResult); + log.info("找到最优解的总比例: {}", optimalResult.getPercentSum()); + } + + // 如果已经达到要求的数量,直接返回 + if (results.size() >= count) { + results.sort((r1, r2) -> Double.compare(r2.getPercentSum(), r1.getPercentSum())); + return results; + } + + // 重新创建模型来求解所有解 + CpModel newModel = new CpModel(); + List newCoalPercents = new ArrayList<>(); + for (int i = 0; i < coals.size(); i++) { + CoalBlendCoalInfoEntity coal = coals.get(i); + int maxPercent = (int) (coal.getYmPercent() * 100); + newCoalPercents.add(newModel.newIntVar(0, maxPercent, "coal_" + i)); + } + + // 重新添加约束 + newModel.addGreaterThan(LinearExpr.sum(newCoalPercents.toArray(new IntVar[0])), 0); + addSequentialConstraints(newModel, newCoalPercents, coals); + addParameterConstraints(newModel, newCoalPercents, coals, constrains, paramFlags); + + // 将最优解作为hint来指导求解 + if (optimalResult != null) { + addOptimalSolutionAsHint(newModel, newCoalPercents, optimalResult, coals); + } + // 创建求解器 CpSolver solver = new CpSolver(); SatParameters.Builder parameters = solver.getParameters(); @@ -72,17 +104,21 @@ public class RawCoalBlendCalculator { /** parameters.setLogSearchProgress(false); HashSet seen = new HashSet<>(); + // 将最优解的key加入seen集合,避免重复 + if (optimalResult != null) { + seen.add(generateSolutionKey(optimalResult)); + } // 求解 - solver.solve(model, new CpSolverSolutionCallback() { + solver.solve(newModel, new CpSolverSolutionCallback() { @Override public void onSolutionCallback() { // 检查解的有效性 - if (!isValidSolution(this, coalPercents)) { + if (!isValidSolution(this, newCoalPercents)) { return; } - CoalBlendResultInfoEntity result = createResult(this, coalPercents, coals, paramFlags); + CoalBlendResultInfoEntity result = createResult(this, newCoalPercents, coals, paramFlags); // 去重 String key = generateSolutionKey(result); @@ -103,7 +139,123 @@ public class RawCoalBlendCalculator { /** log.error("原煤配煤计算失败", e); } + // 按照coalPercents之和从大到小排序,最优解在最前面 + results.sort((r1, r2) -> Double.compare(r2.getPercentSum(), r1.getPercentSum())); + return results; + } + + /** + * 求出最优解(coalPercents之和最大的完整解) + */ + private static CoalBlendResultInfoEntity findOptimalSolution(CpModel model, List coalPercents, + List coals, AtomicBoolean[] paramFlags, Integer maxTime) { + try { + // 创建总和变量 + IntVar totalPercent = model.newIntVar(0, Integer.MAX_VALUE, "total_percent"); + model.addEquality(totalPercent, LinearExpr.sum(coalPercents.toArray(new IntVar[0]))); + + // 设置目标:最大化总和 + model.maximize(totalPercent); + + // 创建求解器 + CpSolver solver = new CpSolver(); + SatParameters.Builder parameters = solver.getParameters(); + parameters.setMaxTimeInSeconds(maxTime); + parameters.setLogSearchProgress(false); + + CpSolverStatus status = solver.solve(model); + + if (status == CpSolverStatus.OPTIMAL || status == CpSolverStatus.FEASIBLE) { + // 创建一个匿名的回调实现来获取解 + return new CoalBlendResultInfoEntity() {{ + // 设置比例信息 + List percentInfos = new ArrayList<>(); + double totalPercentValue = 0; + + for (int i = 0; i < coalPercents.size(); i++) { + long percentValue = solver.value(coalPercents.get(i)); + if (percentValue > 0) { + double percent = percentValue / 100.0; // 转换回百分比 + percentInfos.add(new CoalPercentVo( + coals.get(i).getName(), + percent, + 0.0, + percent)); + totalPercentValue += percent; + } + } + + setPercentInfos(percentInfos); + setPercentSum(totalPercentValue); + + // 计算各参数值 + if (totalPercentValue > 0) { + for (int paramIndex = 1; paramIndex <= 20; paramIndex++) { + if (!paramFlags[paramIndex - 1].get()) { + continue; + } + + String paramName = "param" + paramIndex; + double weightedSum = 0; + + for (int i = 0; i < coals.size(); i++) { + long percentValue = solver.value(coalPercents.get(i)); + if (percentValue > 0) { + Double paramValue = coals.get(i).getParam(paramName); + if (paramValue != null) { + weightedSum += (percentValue / 100.0) * paramValue; + } + } + } + + double averageValue = weightedSum / totalPercentValue; + + try { + String setterName = "setParam" + paramIndex; + getClass().getMethod(setterName, Double.class).invoke(this, averageValue); + } catch (Exception e) { + log.warn("设置参数{}值失败", paramName, e); + } + } + } + }}; + } + } catch (Exception e) { + log.warn("求解最优解失败", e); + } + + return null; + } + + /** + * 将最优解作为hint添加到新模型中 + */ + private static void addOptimalSolutionAsHint(CpModel model, List coalPercents, + CoalBlendResultInfoEntity optimalResult, List coals) { + try { + // 为每个变量设置hint值 + for (int i = 0; i < coalPercents.size(); i++) { + String coalName = coals.get(i).getName(); + long hintValue = 0; + + // 从最优解中找到对应煤种的比例 + for (CoalPercentVo percentVo : optimalResult.getPercentInfos()) { + if (coalName.equals(percentVo.getName())) { + hintValue = (long) (percentVo.getPercent() * 100); // 转换为0.01%单位 + break; + } + } + + // 添加hint + model.addHint(coalPercents.get(i), hintValue); + } + + log.info("已将最优解作为hint添加到模型中,总比例: {}", optimalResult.getPercentSum()); + + } catch (Exception e) { + log.warn("添加最优解hint失败", e); + } } /** * 添加顺序约束:只有前一种煤完全使用后才能使用下一种煤 */ @@ -165,14 +317,16 @@ public class RawCoalBlendCalculator { /** try { // 计算加权参数值 = Σ(煤i的比例 * 煤i的参数值) / 总比例 + // 使用1000倍精度来减少精度损失 + final int PRECISION_MULTIPLIER = 1000; long[] coefficients = new long[coalPercents.size()]; boolean hasValidCoal = false; for (int i = 0; i < coals.size(); i++) { Double paramValue = coals.get(i).getParam(paramName); if (paramValue != null) { - // 系数需要放大100倍来处理小数(比例是百分比*100) - coefficients[i] = (long) (paramValue * 100); + // 系数需要放大1000倍来处理小数(比例是百分比*100) + coefficients[i] = (long) (paramValue * PRECISION_MULTIPLIER); hasValidCoal = true; } else { coefficients[i] = 0; @@ -208,7 +362,7 @@ public class RawCoalBlendCalculator { /** } // 添加最小值约束:weightedSum >= minValue * totalPercent - long minBound = (long) (minValue * 100); + long minBound = (long) (minValue * PRECISION_MULTIPLIER); LinearExpr minConstraint = LinearExpr.newBuilder() .addTerm(totalPercent, minBound) .build(); @@ -235,8 +389,16 @@ public class RawCoalBlendCalculator { /** return; } - // 添加最大值约束:weightedSum <= maxValue * totalPercent - long maxBound = (long) (maxValue * 100); + // 为等式约束(min == max)添加小的容差 + long maxBound; + if (minValue != null && Math.abs(maxValue - minValue) < 0.001) { + // 等式约束,添加小的容差(0.1%) + maxBound = (long) (maxValue * PRECISION_MULTIPLIER + PRECISION_MULTIPLIER * 0.001); + log.debug("参数{}等式约束,添加容差: 原值={}, 调整后上界={}", paramName, maxValue, maxBound / (double) PRECISION_MULTIPLIER); + } else { + maxBound = (long) (maxValue * PRECISION_MULTIPLIER); + } + LinearExpr maxConstraint = LinearExpr.newBuilder() .addTerm(totalPercent, maxBound) .build();