feat(RawCoalBlendCalculator): enhance optimal solution finding and hinting mechanism

This commit is contained in:
2025-08-27 23:28:28 +08:00
parent d38b7ac49c
commit feb98cc8f1

View File

@@ -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<IntVar> 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<String> 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<IntVar> coalPercents,
List<CoalBlendCoalInfoEntity> 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<CoalPercentVo> 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<IntVar> coalPercents,
CoalBlendResultInfoEntity optimalResult, List<CoalBlendCoalInfoEntity> 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();