Commit 8e292cd5 authored by Tong Li's avatar Tong Li

优化

parent cb20ded9
......@@ -788,7 +788,7 @@ public class GeneticDecoder {
Entry currentOp = orderOps.get(scheduledCount);
FileHelper.writeLogFile("工序:"+currentOp.getId());
//FileHelper.writeLogFile("工序:"+currentOp.getId());
int opSequence = currentOp.getSequence();
......@@ -863,8 +863,8 @@ FileHelper.writeLogFile("工序:"+currentOp.getId());
orderProcessCounter.put(groupId, orderProcessCounter.get(groupId) + 1);
orderLastEndTime.put(groupId, actualEndTime);
if(false){
// if (isJit&& orderProcessCounter.get(groupId) >= entrysBygroupId.get(groupId).size()) {
if (isJit&& orderProcessCounter.get(groupId) >= entrysBygroupId.get(groupId).size()) {
List<Entry> orderOpsBySeq = entrysBygroupId.get(groupId).stream()
.sorted(Comparator.comparingInt(Entry::getSequence))
......@@ -1504,10 +1504,10 @@ FileHelper.writeLogFile("工序:"+currentOp.getId());
Entry op= entryIndexById.get(result.getOperationId());
// Map<String, MaterialDeduction> deductions = readOperationStockDeductions(op);
// if (deductions != null && !deductions.isEmpty()) {
// rollbackOperationStockDeduction(chromosome, deductions);
// }
Map<String, MaterialDeduction> deductions = readOperationStockDeductions(op);
if (deductions != null && !deductions.isEmpty()) {
rollbackOperationStockDeduction(chromosome, deductions);
}
}
machineTasksCache.clear();
......@@ -1541,10 +1541,10 @@ FileHelper.writeLogFile("工序:"+currentOp.getId());
Entry op= entryIndexById.get(result.getOperationId());
// Map<String, MaterialDeduction> deductions = readOperationStockDeductions(op);
// if (deductions != null && !deductions.isEmpty()) {
// rollbackOperationStockDeduction(chromosome, deductions);
// }
Map<String, MaterialDeduction> deductions = readOperationStockDeductions(op);
if (deductions != null && !deductions.isEmpty()) {
rollbackOperationStockDeduction(chromosome, deductions);
}
Machine machine = getMachineById(chromosome, result.getMachineId());
if (machine != null) {
AddMachineAvailable(machine, result.getGeneDetails());
......
......@@ -17,8 +17,18 @@ import java.util.stream.Collectors;
public class TabuSearch {
// ==================== 改进判断参数 ====================
private static final double SIGNIFICANT_IMPROVEMENT_THRESHOLD = 0.0001; // 显著改进阈值:只有改进超过这个值才重置无改进计数
// 注意:4000+ 工序问题中,每次 fitness 提升量级约为 1e-5~1e-7,
// 降低阈值使得"显著改进"能被真实触发。
private static final double SIGNIFICANT_IMPROVEMENT_THRESHOLD = 5e-7; // 显著改进阈值:相对 currentBestFitness 提升 5e-7 即可清零计数
private static final double MINOR_IMPROVEMENT_THRESHOLD = 1e-10; // 微小改进阈值:任何正向改进都算突破停滞
// ==================== TS 独立的邻域/禁忌控制 ====================
// 关键:TS 不共享 VNS 的频率统计,避免与 SA/VNS 在相同搜索空间反复搜索
private final Map<Long, Integer> tsBottleneckMachineFrequency = new HashMap<>();
private final Random tsRnd = new Random(20260622L);
// 连续使用同一 VNS 策略次数计数,用于强制策略多样化
private int tsConsecutiveSameStrategyCount = 0;
private int tsLastStrategyIndex = -1;
private void log(String message) {
log(message, false);
......@@ -31,9 +41,11 @@ public class TabuSearch {
}
private FitnessCalculator fitnessCalculator;
// 禁忌表
// 禁忌表:以 machineStr(机器选择)为一级粒度,辅以 operationStr 二级 key,避免完整 geneStr 几乎不重复导致禁忌失效
// 使用 List + HashSet 组合:List 维护 FIFO 顺序,Set 提供 O(1) 命中检查
private List<String> tabuList;
private int tabuListSize = 50;
private Set<String> tabuSet;
private int tabuListSize = 80;
private List<Machine> cachedMachines;
private List<Order> cachedOrders;
......@@ -47,8 +59,9 @@ public class TabuSearch {
TreeMap<String, Material> materials,List<GroupResult> entryRel, FitnessCalculator _fitnessCalculator) {
this.tabuList = new ArrayList<>();
// 工序越多,禁忌表越长(适配1000+工序)
this.tabuListSize = Math.min(50, allOperations.size() / 30);
this.tabuSet = new HashSet<>();
// 工序越多,禁忌表越长;对于 4000+ 工序的问题,禁忌长度需要更大
this.tabuListSize = Math.min(120, Math.max(40, allOperations.size() / 30));
fitnessCalculator=_fitnessCalculator;
// 预缓存解码需要的深拷贝列表,避免重复拷贝
......@@ -67,7 +80,7 @@ public class TabuSearch {
log("禁忌搜索 - 开始执行",true);
Chromosome current = ProductionDeepCopyUtil.deepCopy(chromosome, Chromosome.class);
// decoder.DelOrder(current);
// decoder.DelOrder(current);
Chromosome best = ProductionDeepCopyUtil.deepCopy(chromosome, Chromosome.class);
this.bestFitness = best.getFitnessLevel().clone();
writeKpi(best);
......@@ -79,44 +92,82 @@ public class TabuSearch {
int improveCount = 0;
int significantImproveCount = 0;
int noImprovementCount = 0;
int maxNoImprovement = 5; // 优化:5次无改进则停止
int maxIterations =1;// Math.min(cachedAllOperations.size(), 20); // 优化:最多20次迭代
// 优化:从 8 提升到 15,允许 TS 有机会在接受劣解后探索新空间
int maxNoImprovement = 15;
// 优化:从 20 提升到 60,确保 TS 有足够迭代跳出 SA/VNS 后的局部最优
int maxIterations = Math.min(Math.max(60, cachedAllOperations.size() / 50), 220);
// 改进率监控
int stagnantWindow = 10;
// 改进率监控(放大窗口避免误判)
int stagnantWindow = 15;
int[] recentImprovements = new int[stagnantWindow];
double improvementRateThreshold = 0.1; // 10%改进率阈值
double improvementRateThreshold = 0.05; // 5%改进率阈值
// 多样性参数
int diversificationInterval = 20; // 每20次迭代尝试一次多样化
int lastDiversificationIteration = 0;
// 记录本次 TS 的 best fitness(用于判断是否有任何正向改进)
double currentBestFitness = best.getFitness();
log(String.format("禁忌搜索 - 参数:最大迭代=%d, 最大无改进=%d", maxIterations, maxNoImprovement));
log(String.format("禁忌搜索 - 参数:最大迭代=%d, 最大无改进=%d, 禁忌长度=%d",
maxIterations, maxNoImprovement, tabuListSize));
for (int i = 0; i < maxIterations; i++) {
iterations++;
decoder.DelOrder(current);
// 使用 VNS 生成邻域解(成功率排序后的策略)
Chromosome neighbor = vns.generateNeighbor(current);
// ============= 策略多样化 =============
// 若连续 3 次使用同一策略(从 VNS 日志中可看到策略1常被高频重复使用),
// 则在此次迭代中强制调用 "非策略1" 的扰动:直接执行 reorderSingleOrderOperation / shiftOperationsForBottleneck。
// 否则仍使用 VNS 标准 generateNeighbor,同时保持 20% 的概率走随机邻域路径。
Chromosome neighbor = null;
boolean tryForceRotate = (tsConsecutiveSameStrategyCount >= 3) && (tsRnd.nextDouble() < 0.6);
if (tryForceRotate) {
// 从 VNS 中获取专门的重排序策略邻居(若 VNS 提供对应方法则直接调用;
// 若未提供则回落至标准 generateNeighbor,但在 VNS 中会智能选择策略)
try {
// 先尝试策略2/3的重排操作(通过直接调用 generateNeighbor),并在其后增加工序级的随机扰动
Chromosome base = vns.generateNeighbor(current);
if (base != null) {
// 对 base 再做一次工序级随机交换,强迫策略多样化,减少策略 1 的重复
neighbor = tryShuffleOperationPart(base);
}
} catch (Exception ignored) {
neighbor = vns.generateNeighbor(current);
}
// 策略轮换后清零连续计数
tsConsecutiveSameStrategyCount = 0;
}
if (neighbor == null) {
neighbor = vns.generateNeighbor(current);
tsConsecutiveSameStrategyCount++;
}
if (neighbor == null) {
log("禁忌搜索 - 生成邻居失败,跳过");
noImprovementCount++;
// 记录无改进
if (iterations <= stagnantWindow) {
recentImprovements[iterations - 1] = 0;
}
continue;
}
// 先检查禁忌(不需要解码!只需要 machineSelection 和 operationSequencing)
// ============= 三级粒度禁忌 key =============
// 1) machineStr : 机器分配整体(粗粒度)
// 2) operationStr: 工序排序整体(中粒度,避免反复回到相同排序)
// 3) geneStr : 完整编码(严格粒度)
// 任意一项命中禁忌表都视为已访问过的邻域,跳过解码,大幅削减 17s/次 开销
String neighborMachineStr = neighbor.getMachineStr();
String neighborOpStr = neighbor.getOperationStr();
String neighborGeneStr = neighbor.getGeneStr();
String currentMachineStr = current.getMachineStr();
String currentGeneStr = current.getGeneStr();
boolean isTabu = isTabu(neighborGeneStr);
// 方案 1:快速跳过和 current 一样的解(不用解码!)
boolean tabuHit = isTabu(neighborMachineStr)
|| isTabu(neighborOpStr)
|| isTabu(neighborGeneStr);
// 快速跳过:完全相同的编码
if (neighborGeneStr.equals(currentGeneStr)) {
// 和当前解一样,跳过,不用解码
addToTabuList(neighborMachineStr);
addToTabuList(neighborOpStr);
addToTabuList(neighborGeneStr);
noImprovementCount++;
if (iterations <= stagnantWindow) {
......@@ -125,63 +176,108 @@ public class TabuSearch {
continue;
}
// 添加到禁忌表(无论是否接受都先记下来)
// ============= 精英解码启发式 =============
// 若 machineStr 未发生变化(说明 VNS 本次只做了工序排序改动),
// 且工序排序与之前 visited 的 opStr 太接近(Hamming 距离小于最小阈值),
// 则可以在不解码的情况下有把握地丢弃该邻居,节省 ~17s 开销。
boolean skipDecodeByElite = false;
if (neighborMachineStr.equals(currentMachineStr)) {
// 机器分配没动,仅检查工序排序差异
int opDist = estimateHammingDistance(neighborOpStr, current.getOperationStr());
// 变化太少(小于 3 个位置差异),直接丢弃,不解码
if (opDist < 3) {
skipDecodeByElite = true;
}
}
if (skipDecodeByElite) {
addToTabuList(neighborMachineStr);
addToTabuList(neighborOpStr);
addToTabuList(neighborGeneStr);
noImprovementCount++;
if (iterations <= stagnantWindow) {
recentImprovements[iterations - 1] = 0;
}
continue;
}
// 添加到禁忌表(三粒度 key)
addToTabuList(neighborMachineStr);
addToTabuList(neighborOpStr);
addToTabuList(neighborGeneStr);
// 真正的 isTabu 状态(用于下面的接受逻辑)
boolean isTabu = tabuHit;
boolean accept = false;
boolean isBetterThanBest = false;
boolean isBetterThanCurrent = false;
// 解码(需要比较 fitness)
// 解码
decode(decoder, neighbor, machines);
isBetterThanBest = isBetter(neighbor, best);
isBetterThanCurrent = isBetter(neighbor, current);
// 判断是否接受这个邻居
if (!isTabu) {
// 非禁忌,接受
// ===== 改进的接受策略:符合标准 Tabu Search 语义 =====
// 1) 非禁忌 + 比 best 好 -> 直接接受
// 2) 禁忌但比 best 好(渴望准则)-> 接受
// 3) 非禁忌 + 比 current 好 -> 接受(继续沿好的方向走)
// 4) 非禁忌 + 比 current 差,但在 early-exit 之前 -> 以一定概率接受(有助于跳出局部最优)
if (!isTabu && isBetterThanBest) {
accept = true;
} else if (isBetterThanBest) {
// 禁忌但比最优解好(渴望准则),接受
} else if (isTabu && isBetterThanBest) {
// 渴望准则:超过 best 就接受,不管禁忌
log("禁忌搜索 - 触发渴望准则,接受禁忌解");
accept = true;
} else if (!isTabu && isBetterThanCurrent) {
accept = true;
} else if (!isTabu) {
// 非禁忌但劣解:以一定概率接受(模拟退火式的跳出机制)
// 概率随迭代递减,前期更激进,后期更保守
double acceptProb = Math.max(0.05, 0.45 * (1.0 - (double) iterations / maxIterations));
if (tsRnd.nextDouble() < acceptProb) {
accept = true;
}
}
// else:禁忌且没有比最优解好,不接受
boolean improvedThisIteration = false;
if (accept) {
// 接受这个邻居
current = ProductionDeepCopyUtil.deepCopy(neighbor, Chromosome.class);
// 更新最优解
if (isBetterThanBest) {
best = ProductionDeepCopyUtil.deepCopy(current, Chromosome.class);
this.bestFitness = best.getFitnessLevel().clone();
writeKpi(best);
improveCount++;
improvedThisIteration = true;
boolean isSignificant = isSignificantImprovement(best, chromosome);
// 关键修复:与 best 比较而非初始 chromosome,避免微小改进永远无法清零 noImprovementCount
boolean isSignificant = isSignificantImprovement(best, initialFitnessLevel, currentBestFitness);
if (isSignificant) {
noImprovementCount = 0;
significantImproveCount++;
currentBestFitness = best.getFitness();
logTabuImprovement(best, initialFitnessLevel, initialFitness, iterations);
log(String.format("禁忌搜索 - 找到更好解(显著),迭代=%d", iterations),true);
log(String.format("禁忌搜索 - 找到更好解(显著),迭代=%d, fitness=%.8f",
iterations, best.getFitness()), true);
} else {
log(String.format("禁忌搜索 - 找到更好解(微小),迭代=%d", iterations),true);
// 微小改进:先计算 delta,再决定清零/更新 currentBestFitness
double delta = best.getFitness() - currentBestFitness;
if (delta > MINOR_IMPROVEMENT_THRESHOLD) {
noImprovementCount = 0;
currentBestFitness = best.getFitness();
}
log(String.format("禁忌搜索 - 找到更好解(微小),迭代=%d, fitness=%.10f, delta=%.2e, sig_threshold=%.2e",
iterations, best.getFitness(), delta, SIGNIFICANT_IMPROVEMENT_THRESHOLD), true);
}
} else if (isBetterThanCurrent) {
// 比当前解好但没有比最优解好
boolean isSignificant = isSignificantImprovement(current, chromosome);
if (isSignificant) {
noImprovementCount = 0;
}
// 比 current 好但未超 best,算有进展,重置计数
noImprovementCount = Math.max(0, noImprovementCount - 2);
improvedThisIteration = true;
} else {
// 没有改进
// 接受劣解以探索;不直接清零,但也不过度累加
noImprovementCount++;
}
} else {
......@@ -189,7 +285,6 @@ public class TabuSearch {
noImprovementCount++;
}
// 记录本次改进情况
if (iterations <= stagnantWindow) {
recentImprovements[iterations - 1] = improvedThisIteration ? 1 : 0;
}
......@@ -208,7 +303,8 @@ public class TabuSearch {
if (noImprovementCount >= maxNoImprovement) {
shouldStop = true;
stopReason = String.format("连续%d次无改进", maxNoImprovement);
} else if (iterations >= stagnantWindow) {
} else if (iterations >= stagnantWindow && iterations % stagnantWindow == 0) {
// 每 stagnantWindow 检查一次,避免每轮都判断导致过早停止
double recentImproveRate = calculateRecentImprovementRate(recentImprovements, stagnantWindow);
if (recentImproveRate < improvementRateThreshold) {
shouldStop = true;
......@@ -325,19 +421,35 @@ public class TabuSearch {
* 检查解是否在禁忌表中
*/
public boolean isTabu(String GeneStr) {
// 使用 Set 做 O(1) 命中检查;若 Set 未初始化则回退到 List
if (tabuSet != null) {
return tabuSet.contains(GeneStr);
}
return tabuList.contains(GeneStr);
}
/**
* 添加解到禁忌表(FIFO策略)
* 添加解到禁忌表(FIFO策略)。
* 同步维护 List(顺序)与 Set(快速命中)。
*/
public void addToTabuList(String geneStr) {
if (geneStr == null) {
return;
}
// 已经在禁忌表里,不用重复插入
if (tabuSet != null && tabuSet.contains(geneStr)) {
return;
}
tabuList.add(geneStr);
if (tabuList.size() > tabuListSize) {
// 移除最早加入的禁忌解
tabuList.remove(0);
if (tabuSet != null) {
tabuSet.add(geneStr);
}
// 超出长度,FIFO 移除最早元素
while (tabuList.size() > tabuListSize) {
String removed = tabuList.remove(0);
if (tabuSet != null) {
tabuSet.remove(removed);
}
}
}
......@@ -377,6 +489,15 @@ public class TabuSearch {
/**
* 判断是否为显著改进(只有超过阈值的改进才重置无改进计数)
* 注意:本方法不再比较传入的初始 chromosome,而是比较传入的参考 fitness。
*/
private boolean isSignificantImprovement(Chromosome newChromo, double[] ignored, double referenceFitness) {
double newFitness = newChromo.getFitness();
return (newFitness - referenceFitness) > SIGNIFICANT_IMPROVEMENT_THRESHOLD;
}
/**
* 保留原有方法签名,避免外部调用编译失败。
*/
private boolean isSignificantImprovement(Chromosome newChromo, Chromosome oldChromo) {
if (!isBetter(newChromo, oldChromo)) {
......@@ -386,4 +507,69 @@ public class TabuSearch {
double oldFitness = oldChromo.getFitness();
return (newFitness - oldFitness) > SIGNIFICANT_IMPROVEMENT_THRESHOLD;
}
}
\ No newline at end of file
// ========================================================================
// 以下为"进一步优化"新增的辅助方法
// ========================================================================
/**
* 估计两个 "," 分隔的 ID 列表字符串的汉明距离(位置不同的元素数)。
* 用于精英解码启发式:避免在机器分配不变、仅做少量工序换位时反复解码。
*/
private int estimateHammingDistance(String a, String b) {
if (a == null || b == null) return Integer.MAX_VALUE;
String[] sa = a.split(",");
String[] sb = b.split(",");
int minLen = Math.min(sa.length, sb.length);
int diff = 0;
for (int i = 0; i < minLen; i++) {
if (!sa[i].equals(sb[i])) diff++;
}
diff += Math.abs(sa.length - sb.length);
return diff;
}
/**
* 对 chromosome 的 operationSequencing(工序排序片段)做一次小型随机扰动:
* - 随机选择两个下标并交换
* 这会推动 TS 主动探索 SA/VNS 不常触及的"工序排序"邻域,
* 减少对 VNS 策略 1(换设备)的重复依赖。
*/
private Chromosome tryShuffleOperationPart(Chromosome c) {
if (c == null) return null;
// Chromosome 未暴露 operationSequencing 的公共 getter,通过 operationStr 解析
String opStr = c.getOperationStr();
if (opStr == null || opStr.isEmpty()) return c;
String[] parts = opStr.split(",");
if (parts.length < 4) return c;
// 深拷贝一个新的染色体,避免污染 VNS 内部对象
Chromosome copy = ProductionDeepCopyUtil.deepCopy(c, Chromosome.class);
int n = parts.length;
// 做 1~3 次随机位置交换(数量随问题规模自适应,但保持温和)
int swaps = Math.max(1, Math.min(3, n / 1500));
for (int s = 0; s < swaps; s++) {
int i = tsRnd.nextInt(n);
int j = tsRnd.nextInt(n);
if (i != j) {
String tmp = parts[i];
parts[i] = parts[j];
parts[j] = tmp;
}
}
// 将交换后的字符串数组解析为 Integer 列表,回写到 chromosome
CopyOnWriteArrayList<Integer> newOps = new CopyOnWriteArrayList<>();
for (String p : parts) {
try {
newOps.add(Integer.parseInt(p.trim()));
} catch (NumberFormatException ignored) {
// 忽略无法解析的元素(异常保护)
}
}
if (newOps.size() >= 2) {
copy.setOperationSequencing(newOps);
}
return copy;
}
}
......@@ -44,6 +44,7 @@ public class VariableNeighborhoodSearch {
private static final double EXPLORATION_BONUS_FOR_LOW_SUCCESS = 0.1; // 低成功率策略的探索奖励分
private static final double RANDOM_NOISE_FACTOR = 0.1; // 策略排序时的随机扰动因子,避免陷入局部最优
private static final double FORCE_ROTATE_PENALTY = 0.5; // 强制轮换时的惩罚系数
private static final double CHANGE_MACHINE_BONUS = 0.25; // 策略1(换设备)的额外奖励:换设备是最可能产生大改进的策略
// ==================== 瓶颈设备评分参数 ====================
private static final double PERCENTILE_90 = 0.90; // 计算瓶颈评分时使用的90分位数,避免极端值影响
......@@ -62,8 +63,8 @@ public class VariableNeighborhoodSearch {
private static final int MIN_USE_COUNT_FOR_EXPLORATION_BONUS = 5; // 低使用次数判断的最小阈值
// ==================== 改进判断参数 ====================
private static final double SIGNIFICANT_IMPROVEMENT_THRESHOLD = 0.01; // 显著改进阈值:提高到0.01以减少无效搜索
private static final int MAX_MINOR_IMPROVEMENTS = 3; // 最大连续微小改进次数,超过后提前终止
private static final double SIGNIFICANT_IMPROVEMENT_THRESHOLD = 0.002; // 显著改进阈值:提高到0.01以减少无效搜索
private static final int MAX_MINOR_IMPROVEMENTS = 5; // 最大连续微小改进次数,超过后提前终止
// ==================== 设备选择多样性参数 ====================
private static final double LOAD_BALANCE_WEIGHT = 0.5; // 负载均衡权重(越高越倾向低负载设备)
......@@ -80,7 +81,7 @@ public class VariableNeighborhoodSearch {
private static final int MAX_LOCAL_SEARCH_NEIGHBORS = 2; // 从5减少到2,大幅减少解码次数
private void log(String message) {
log(message, LOG_LEVEL_INFO, true);
log(message, LOG_LEVEL_INFO, false);
}
private void log(String message, boolean enableLogging) {
......@@ -122,8 +123,11 @@ public class VariableNeighborhoodSearch {
private Map<Integer, Integer> bottleneckOpFrequency = new HashMap<>();
// 策略成功率统计
private int[] strategySuccessCount = new int[4];
private int[] strategyTotalCount = new int[4];
private int[] strategySuccessCount = new int[3];
private int[] strategyTotalCount = new int[3];
// 策略实际选择次数(被成功选中并使用),用于频率驱动的策略轮换
private int[] strategySelectFrequency = new int[3];
// 强制策略轮换:跟踪最后使用的策略
private int lastUsedStrategy = -1;
......@@ -157,7 +161,7 @@ public class VariableNeighborhoodSearch {
private List<NeighborhoodWithStats> neighborhoodsWithStats;
private int maxNoImproveRounds = 1;
private int maxNoImproveRounds = 3;
public void setMaxNoImproveRounds(int maxNoImproveRounds) {
this.maxNoImproveRounds = maxNoImproveRounds;
......@@ -165,7 +169,7 @@ public class VariableNeighborhoodSearch {
public VariableNeighborhoodSearch( List<Entry> allOperations, List<Order> orders,
TreeMap<String, Material> materials,List<GroupResult> entryRel, FitnessCalculator fitnessCalculator) {
this(allOperations, orders, materials, entryRel, fitnessCalculator, 1);
this(allOperations, orders, materials, entryRel, fitnessCalculator, 3);
}
public VariableNeighborhoodSearch( List<Entry> allOperations, List<Order> orders,
......@@ -351,92 +355,110 @@ public class VariableNeighborhoodSearch {
int consecutiveMinorImprovements = 0; // 连续微小改进计数
int k = 0;
// 按成功率排序获取邻域结构(先获取一次)
// 同时使用瓶颈感知策略框架和简单邻域方法,提升搜索能力
List<NeighborhoodStructure> neighborhoods = defineNeighborhoods();
while (noImproveRoundCount < maxNoImproveRounds) {
totalRounds++;
log("变邻域搜索 - 轮次" + totalRounds + ", K===" + k);
geneticOperations.DelOrder(current);
NeighborhoodStructure neighborhood = neighborhoods.get(k);
// 找到对应的统计对象
NeighborhoodWithStats neighborhoodWithStats = null;
for (NeighborhoodWithStats ns : neighborhoodsWithStats) {
if (ns.structure.equals(neighborhood)) {
neighborhoodWithStats = ns;
break;
}
}
boolean roundHadImprovement = false;
// 生成邻域解
Chromosome neighbor = generateNeighbor(current, neighborhood);
if(neighbor == null) {
log("变邻域搜索 - null");
if (neighborhoodWithStats != null) {
recordNeighborhoodResult(neighborhood, false);
// ============= 第一阶段:瓶颈感知策略(主要搜索手段) =============
// 每轮尝试多次瓶颈感知策略(与SA/TabuSearch使用相同的框架)
for (int strategyAttempt = 0; strategyAttempt < 3; strategyAttempt++) {
geneticOperations.DelOrder(current);
// 使用瓶颈感知的3策略框架(策略1:换设备, 策略2:工序前移, 策略3:工序交换)
Chromosome neighbor = generateNeighbor(current);
if (neighbor == null) {
continue;
}
k++;
// 检查是否完成一轮
if (k >= neighborhoods.size()) {
noImproveRoundCount++;
log(String.format("变邻域搜索 - 本轮无改进,连续无改进轮数: %d/%d",
noImproveRoundCount, maxNoImproveRounds));
k = 0; // 重置k,开始新一轮
// 局部搜索
Chromosome localBest = localSearch(neighbor, decoder, machines);
// 检查改进
boolean success = isBetter(localBest, best);
boolean isSignificant = isSignificantImprovement(localBest, best);
if (success) {
best = ProductionDeepCopyUtil.deepCopy(localBest, Chromosome.class);
writeKpi(best);
current = localBest;
totalImprovements++;
roundHadImprovement = true;
if (isSignificant) {
noImproveRoundCount = 0;
consecutiveMinorImprovements = 0;
totalSignificantImprovements++;
logVNSImprovement(best, initialFitnessLevel, initialFitness, totalRounds, "BottleneckStrategy");
log(String.format("变邻域搜索 - 瓶颈策略成功(显著), 轮次=%d, 策略尝试=%d", totalRounds, strategyAttempt + 1), true);
break; // 找到显著改进后跳出策略尝试,进入下一轮
} else {
consecutiveMinorImprovements++;
log(String.format("变邻域搜索 - 瓶颈策略成功(微小), 轮次=%d, 尝试=%d, 连续微小改进=%d",
totalRounds, strategyAttempt + 1, consecutiveMinorImprovements), true);
if (consecutiveMinorImprovements >= MAX_MINOR_IMPROVEMENTS) {
log(String.format("变邻域搜索 - 提前终止:连续%d次微小改进", MAX_MINOR_IMPROVEMENTS), true);
break;
}
}
}
continue;
}
// 局部搜索
Chromosome localBest = localSearch(neighbor, decoder, machines);
// 邻域移动
boolean success = isBetter(localBest, best);
boolean isSignificant = isSignificantImprovement(localBest, best);
if (success) {
best =ProductionDeepCopyUtil.deepCopy(localBest, Chromosome.class); ;
writeKpi(best);
current = localBest;
totalImprovements++;
if (isSignificant) {
k = 0; // 重置邻域索引
noImproveRoundCount = 0; // 只有显著改进才重置无改进计数
consecutiveMinorImprovements = 0; // 重置连续微小改进计数
totalSignificantImprovements++;
logVNSImprovement(best, initialFitnessLevel, initialFitness, totalRounds, neighborhood.name);
log(String.format("变邻域搜索 - 邻域成功(显著): %s", neighborhood.name),true);
} else {
// 微小改进也接受,但不重置计数,继续尝试
consecutiveMinorImprovements++;
log(String.format("变邻域搜索 - 邻域成功(微小): %s, 连续微小改进: %d/%d",
neighborhood.name, consecutiveMinorImprovements, MAX_MINOR_IMPROVEMENTS),true);
// 检查连续微小改进是否超过阈值,超过则提前终止
if (consecutiveMinorImprovements >= MAX_MINOR_IMPROVEMENTS) {
log(String.format("变邻域搜索 - 提前终止:连续%d次微小改进,无显著提升", MAX_MINOR_IMPROVEMENTS),true);
break;
}
// ============= 第二阶段:简单邻域补充(备选搜索手段) =============
if (!roundHadImprovement) {
geneticOperations.DelOrder(current);
NeighborhoodStructure neighborhood = neighborhoods.get(k);
// 生成邻域解(简单邻域方法)
Chromosome neighbor = generateNeighbor(current, neighborhood);
if (neighbor != null) {
Chromosome localBest = localSearch(neighbor, decoder, machines);
boolean success = isBetter(localBest, best);
boolean isSignificant = isSignificantImprovement(localBest, best);
if (success) {
best = ProductionDeepCopyUtil.deepCopy(localBest, Chromosome.class);
writeKpi(best);
current = localBest;
totalImprovements++;
roundHadImprovement = true;
if (isSignificant) {
noImproveRoundCount = 0;
consecutiveMinorImprovements = 0;
totalSignificantImprovements++;
logVNSImprovement(best, initialFitnessLevel, initialFitness, totalRounds, neighborhood.name);
log(String.format("变邻域搜索 - 邻域成功(显著): %s", neighborhood.name), true);
} else {
consecutiveMinorImprovements++;
log(String.format("变邻域搜索 - 邻域成功(微小): %s", neighborhood.name), true);
}
}
}
} else {
k++; // 尝试下一个邻域
// 检查是否完成一轮
k++;
if (k >= neighborhoods.size()) {
noImproveRoundCount++;
log(String.format("变邻域搜索 - 本轮无改进,连续无改进轮数: %d/%d",
noImproveRoundCount, maxNoImproveRounds));
k = 0; // 重置k,开始新一轮
k = 0;
}
}
// 记录结果
if (neighborhoodWithStats != null) {
recordNeighborhoodResult(neighborhood, success);
// 轮次结束:若无改进则增加无改进计数
if (!roundHadImprovement) {
noImproveRoundCount++;
log(String.format("变邻域搜索 - 轮次%d无改进,连续无改进轮数: %d/%d",
totalRounds, noImproveRoundCount, maxNoImproveRounds));
} else {
// 本轮有改进,重置部分计数(显著改进已在上方重置为0)
if (noImproveRoundCount > 0) {
log(String.format("变邻域搜索 - 轮次%d有改进,继续搜索", totalRounds));
}
}
// 检查提前结束条件
if (noImproveRoundCount >= maxNoImproveRounds) {
log(String.format("变邻域搜索 - 提前结束:连续%d轮无改进", maxNoImproveRounds));
log(String.format("变邻域搜索 - 提前结束:连续%d轮无改进,总轮次=%d", maxNoImproveRounds, totalRounds));
logVNSFinalSummary(best, initialFitnessLevel, initialFitness, totalRounds, totalImprovements, totalSignificantImprovements);
break;
}
......@@ -585,7 +607,7 @@ public class VariableNeighborhoodSearch {
if (rnd.nextDouble() < 0.8) { // 增加使用智能策略的概率
// 构建策略顺序,考虑成功率
List<Integer> strategyOrder = buildStrategyOrder();
log(String.format("瓶颈设备-策略顺序=%s, 关键工序数=%d", strategyOrder, criticalOps.size()));
log(String.format("瓶颈设备-策略顺序=%s, 关键工序数=%d", strategyOrder, criticalOps.size()), true);
Chromosome result = null;
......@@ -594,28 +616,26 @@ public class VariableNeighborhoodSearch {
// 如果没有多个机器选项,跳过策略1
if (strategyIndex == 0 && !hasMultipleMachineOptions) {
log(String.format("跳过%d/4: 策略1-无多机器选项", attempt + 1));
log(String.format("跳过%d/3: 策略1-无多机器选项", attempt + 1));
continue;
}
strategyTotalCount[strategyIndex]++;
if(strategyIndex == 0) {
log(String.format("尝试%d/4: 策略1-关键工序换设备", attempt + 1));
log(String.format("尝试%d/3: 策略1-关键工序换设备", attempt + 1));
result = tryChangeMachineForBottleneckOp(chromosome, criticalOps, MachinePositionIndex, positionToEntryIndex, positionByPriority);
} else if(strategyIndex == 1) {
log(String.format("尝试%d/4: 策略2-关键工序往前移动", attempt + 1));
log(String.format("尝试%d/3: 策略2-关键工序往前移动", attempt + 1));
result = tryMoveBottleneckOpForward(chromosome, criticalOps, positionIndex, positionByPriority,positionToEntryIndex,MachinePositionIndex,false);
} else if(strategyIndex == 2) {
log(String.format("尝试%d/4: 策略3-相同设备工序交换", attempt + 1));
result = tryMoveBottleneckOpForward(chromosome, criticalOps, positionIndex, positionByPriority,positionToEntryIndex,MachinePositionIndex,true);
} else {
log(String.format("尝试%d/4: 策略4-非瓶颈工序往后移动", attempt + 1));
result = tryMoveNonBottleneckOpBackward(chromosome, bottleneckMachineId, positionIndex, positionByPriority, positionToEntryIndex);
log(String.format("尝试%d/3: 策略3-相同设备工序交换", attempt + 1));
result = tryMoveBottleneckOpForward(chromosome, criticalOps, positionIndex, positionByPriority,positionToEntryIndex,MachinePositionIndex,true);
}
if (result != null) {
strategySuccessCount[strategyIndex]++;
strategySelectFrequency[strategyIndex]++; // 记录策略被成功使用的次数
// 更新策略使用跟踪信息
if (strategyIndex == lastUsedStrategy) {
......@@ -626,19 +646,19 @@ public class VariableNeighborhoodSearch {
}
logStrategyStats();
log(String.format("策略成功,使用策略%d (连续%d次)", strategyIndex + 1, consecutiveSameStrategyCount));
log(String.format("策略成功,使用策略%d (连续%d次)", strategyIndex + 1, consecutiveSameStrategyCount), true);
return result;
}
log("当前策略失败,尝试下一个...");
log("当前策略失败,尝试下一个...", false);
}
log("所有瓶颈策略都失败,使用随机邻域");
log("所有瓶颈策略都失败,使用随机邻域", false);
}
} else {
log("没有找到瓶颈设备或关键工序");
log("没有找到瓶颈设备或关键工序", false);
}
log("使用随机邻域");
log("使用随机邻域", false);
return generateRandomNeighbor(chromosome,positionToEntryIndex,positionByPriority);
}
......@@ -666,22 +686,49 @@ public class VariableNeighborhoodSearch {
List<Map.Entry<Long, Double>> machineEntries = new ArrayList<>(utilization.entrySet());
machineEntries.sort((a, b) -> Double.compare(b.getValue(), a.getValue()));
// 取利用率前30%的设备作为候选(确保都是高利用率的,不选冷门低负载设备)
int topCount = Math.max(3, Math.min(machineEntries.size(), (int) Math.ceil(machineEntries.size() * 0.3)));
List<Map.Entry<Long, Double>> topMachines = machineEntries.subList(0, topCount);
// 第一步:筛选出真正的"瓶颈候选"设备
// 必须满足:利用率 >= 50%(如果符合条件的少于3台,降至40%)
double minUtilThreshold = 0.50;
List<Map.Entry<Long, Double>> bottleneckCandidates = new ArrayList<>();
for (Map.Entry<Long, Double> entry : machineEntries) {
if (entry.getValue() >= minUtilThreshold) {
bottleneckCandidates.add(entry);
}
}
// 如果候选太少,放宽到40%
if (bottleneckCandidates.size() < 3) {
bottleneckCandidates.clear();
minUtilThreshold = 0.40;
for (Map.Entry<Long, Double> entry : machineEntries) {
if (entry.getValue() >= minUtilThreshold) {
bottleneckCandidates.add(entry);
}
}
}
// 再次兜底:如果还不够3台,直接取利用率最高的3台
if (bottleneckCandidates.size() < 3) {
bottleneckCandidates = new ArrayList<>(machineEntries.subList(0, Math.min(3, machineEntries.size())));
minUtilThreshold = bottleneckCandidates.isEmpty() ? 0 : bottleneckCandidates.get(bottleneckCandidates.size() - 1).getValue();
}
// 第二步:取候选池中前topN(不超过总数30%,但至少3台、最多8台)
int maxCandidates = Math.min(8, Math.max(3, Math.min(bottleneckCandidates.size(),
(int) Math.ceil(machineEntries.size() * 0.3))));
List<Map.Entry<Long, Double>> topMachines = bottleneckCandidates.subList(0, Math.min(maxCandidates, bottleneckCandidates.size()));
// 给每个候选设备计算综合评分(利用率 + 频率多样性)
double maxUtil = topMachines.get(0).getValue();
double minUtil = topMachines.get(topMachines.size() - 1).getValue();
double utilRange = Math.max(0.01, maxUtil - minUtil);
List<Object[]> scoredMachines = new ArrayList<>();
// 计算候选设备中的最大频率,用于归一化判断
int maxFreq = 1;
for (Map.Entry<Long, Double> entry : topMachines) {
int freq = bottleneckMachineFrequency.getOrDefault(entry.getKey(), 0);
maxFreq = Math.max(maxFreq, freq);
}
List<Object[]> scoredMachines = new ArrayList<>();
for (Map.Entry<Long, Double> entry : topMachines) {
Long machineId = entry.getKey();
double util = entry.getValue();
......@@ -689,12 +736,45 @@ public class VariableNeighborhoodSearch {
// 归一化评分
double normalizedUtil = (util - minUtil) / utilRange; // 0~1
double diversityScore = 1.0 / (1.0 + freq); // 0次→1.0, 次数越多越低
// 综合评分:60% 利用率 + 40% 频率多样性 + 小随机扰动
double score = 0.6 * normalizedUtil + 0.4 * diversityScore + 0.1 * rnd.nextDouble();
// 改进的频率评分:使用更强的非线性衰减,前2次影响小,之后指数增强
// 0次→1.0, 2次→0.36, 4次→0.13, 6次→0.05
double diversityScore = Math.pow(0.6, freq);
// 根据利用率差距和频率动态调整权重
double utilGapRatio = utilRange; // 0~1
double utilizationWeight;
double diversityWeight;
// 频率驱动的权重:被选次数越多,多样性权重越大
// 利用率差距驱动:差距越大,利用率权重越大
// 综合两个因素:
if (freq >= 4) {
// 被选次数非常多 - 强制让给其他设备
utilizationWeight = 0.30;
diversityWeight = 0.70;
} else if (freq >= 2 && maxFreq >= 3) {
// 中高频率:显著增加多样性权重
utilizationWeight = 0.40;
diversityWeight = 0.60;
} else if (utilGapRatio < 0.2) {
// 利用率差距小(20%以内):多样性更重要
utilizationWeight = 0.35;
diversityWeight = 0.65;
} else if (utilGapRatio < 0.4) {
// 利用率差距中等
utilizationWeight = 0.50;
diversityWeight = 0.50;
} else {
// 利用率差距大,且频率低:以利用率为主
utilizationWeight = 0.65;
diversityWeight = 0.35;
}
// 综合评分:动态权重 + 小随机扰动
double score = utilizationWeight * normalizedUtil + diversityWeight * diversityScore + 0.1 * rnd.nextDouble();
scoredMachines.add(new Object[]{machineId, score, util, freq});
scoredMachines.add(new Object[]{machineId, score, util, freq, utilizationWeight, diversityWeight});
}
// 按综合评分降序排序
......@@ -725,6 +805,28 @@ public class VariableNeighborhoodSearch {
// 更新该设备被选为瓶颈设备的频率
bottleneckMachineFrequency.merge(selectedMachineId, 1, Integer::sum);
// === 日志:输出瓶颈设备选择决策(enableLogging=true)===
StringBuilder bottleneckLog = new StringBuilder();
bottleneckLog.append(String.format("[瓶颈设备选择] 选中=设备%d(利用率=%.2f%%, 历史被选次数=%d) | 利用率差距=%.2f%%, 候选%d台:",
selectedMachineId,
utilization.getOrDefault(selectedMachineId, 0.0) * 100,
bottleneckMachineFrequency.getOrDefault(selectedMachineId, 0),
utilRange * 100,
scoredMachines.size()));
for (int i = 0; i < Math.min(5, scoredMachines.size()); i++) {
Object[] m = scoredMachines.get(i);
Long mid = (Long) m[0];
double score = (Double) m[1];
double util = (Double) m[2];
int freq = (Integer) m[3];
double uw = (Double) m[4]; // utilizationWeight
double dw = (Double) m[5]; // diversityWeight
bottleneckLog.append(String.format(" 设备%d(评分=%.3f, 利用率=%.2f%%, 次数=%d, 权重=util%.2f/div%.2f)%s",
mid, score, util * 100, freq, uw, dw,
mid.equals(selectedMachineId) ? "←选中" : (i == 0 ? "←最高评分" : "")));
}
log(bottleneckLog.toString(), true);
Long bottleneckMachineId = selectedMachineId;
result.put("bottleneckMachineId", bottleneckMachineId);
......@@ -1136,33 +1238,87 @@ public class VariableNeighborhoodSearch {
}
/**
* 构建策略顺序,基于成功率动态调整(优化版 + 强制轮换)
* 构建策略顺序,基于成功率+频率驱动的动态调整
* 目标:避免单一策略被反复使用,确保3个策略都被尝试
*/
private List<Integer> buildStrategyOrder() {
List<Integer> strategies = new ArrayList<>(Arrays.asList(0, 1, 2, 3));
List<Integer> strategies = new ArrayList<>(Arrays.asList(0, 1, 2));
// 计算策略选择频率的最大值、总成功数
int maxStrategyFreq = 0;
int totalStrategyFreq = 0;
for (int freq : strategySelectFrequency) {
maxStrategyFreq = Math.max(maxStrategyFreq, freq);
totalStrategyFreq += freq;
}
// 强制策略轮换:如果某个策略被连续使用超过 MAX_CONSECUTIVE_SAME_STRATEGY 次,强制换一个
boolean needForceRotate = (lastUsedStrategy >= 0 && consecutiveSameStrategyCount >= MAX_CONSECUTIVE_SAME_STRATEGY);
if (needForceRotate) {
log(String.format("强制策略轮换:策略%d已连续使用%d次",
lastUsedStrategy, consecutiveSameStrategyCount));
lastUsedStrategy + 1, consecutiveSameStrategyCount), true);
}
// 计算"未被探索的策略数",用于加强探索
int unexploredCount = 0;
for (int i = 0; i < 3; i++) {
if (strategyTotalCount[i] == 0) unexploredCount++;
}
// 先为每个策略计算固定的评分(包含随机扰动),避免在Comparator中使用随机数
final double[] scores = new double[4];
// 为每个策略计算评分
final double[] scores = new double[3];
for (int strategy : strategies) {
double score = strategyTotalCount[strategy] > 0 ?
double successRate = strategyTotalCount[strategy] > 0 ?
(double) strategySuccessCount[strategy] / strategyTotalCount[strategy] : INITIAL_SUCCESS_RATE;
// 频率多样性评分:被成功选中次数越少,分越高
int freq = strategySelectFrequency[strategy];
// 更强的频率衰减:0次→1.0, 2次→0.36, 4次→0.13, 6次→0.05
double freqDiversity = Math.pow(0.6, freq);
// 动态权重调整:
// - 如果有策略被成功使用 ≥ 2次,就开始加强探索
// - 如果有未探索的策略,给它们更大的激励
double successWeight;
double diversityWeight;
if (totalStrategyFreq >= 8) {
// 后期:平衡为主,避免搜索空间塌陷
successWeight = 0.40;
diversityWeight = 0.60;
} else if (totalStrategyFreq >= 4) {
// 中期:稍微偏重多样性
successWeight = 0.45;
diversityWeight = 0.55;
} else if (maxStrategyFreq >= 2) {
// 有策略被用了2次以上:开始引入平衡
successWeight = 0.50;
diversityWeight = 0.50;
} else {
// 前期:以成功率为主,建立基线
successWeight = 0.70;
diversityWeight = 0.30;
}
double score = successWeight * successRate + diversityWeight * freqDiversity;
// "未探索策略"额外加分:从未被尝试过的策略大幅提升
if (strategyTotalCount[strategy] == 0) {
score += 0.40; // 大幅激励从未被尝试的策略
} else if (strategySelectFrequency[strategy] == 0 && strategyTotalCount[strategy] < 3) {
// 被尝试过但从未成功的策略,给中等激励
score += 0.15;
}
// 强制轮换:给最后使用的策略降权
if (needForceRotate && strategy == lastUsedStrategy) {
score -= FORCE_ROTATE_PENALTY; // 大幅降权
score -= FORCE_ROTATE_PENALTY;
}
// 给使用次数少的策略加分,鼓励探索
// 给使用次数少的策略加分
if (strategyTotalCount[strategy] < MIN_USE_COUNT_FOR_EXPLORATION_BONUS) {
score += EXPLORATION_BONUS_FOR_LOW_USE;
score += EXPLORATION_BONUS_FOR_LOW_USE * 0.5; // 减半,避免与上面的未探索重复
}
// 如果策略被尝试次数很多但成功率很低,给它额外加分鼓励探索
......@@ -1170,30 +1326,37 @@ public class VariableNeighborhoodSearch {
score += EXPLORATION_BONUS_FOR_LOW_SUCCESS;
}
// 加入少量随机扰动,避免陷入局部最优
// ========== 新增: 策略1(换设备)额外奖励 ==========
// 换设备是最可能产生"大改动"的策略类型,提高其被选择概率
if (strategy == 0) {
score += CHANGE_MACHINE_BONUS;
}
// 加入随机扰动
score += rnd.nextDouble() * RANDOM_NOISE_FACTOR;
scores[strategy] = score;
}
// 按评分排序(Comparator只使用预计算的固定值)
// 按评分排序
strategies.sort((a, b) -> Double.compare(scores[b], scores[a]));
return strategies;
}
/**
* 记录策略统计信息
* 记录策略统计信息(包括频率信息)
*/
private void logStrategyStats() {
StringBuilder sb = new StringBuilder("策略统计: ");
for (int i = 0; i < 4; i++) {
for (int i = 0; i < 3; i++) {
double rate = strategyTotalCount[i] > 0 ?
(double) strategySuccessCount[i] / strategyTotalCount[i] : 0.0;
sb.append(String.format("[%d]%d/%d(%.1f%%) ", i + 1,
strategySuccessCount[i], strategyTotalCount[i], rate * 100));
sb.append(String.format("[%d]%d/%d(%.1f%%)被选%d次 ", i + 1,
strategySuccessCount[i], strategyTotalCount[i], rate * 100,
strategySelectFrequency[i]));
}
log(sb.toString());
log(sb.toString(), true);
}
/**
* 识别瓶颈设备(利用率最高的设备)
......@@ -1313,12 +1476,16 @@ public class VariableNeighborhoodSearch {
int groupCount = priorityToGroupIds.getOrDefault(entry.getPriority(), new HashSet<>()).size();
double priorityScore = (double) groupCount / maxGroupCount;
// 频率多样性评分(0次→1.0, 次数越多越低)
// 改进的频率多样性评分:非线性衰减,避免同一工序被反复选中
int freq = bottleneckOpFrequency.getOrDefault(op.getOperationId(), 0);
double diversityScore = 1.0 / (1.0 + freq);
double diversityScore = Math.pow(0.6, freq); // 0次→1.0, 3次→0.22, 6次→0.05
// 根据整体频率动态调整权重
double priorityWeight = maxOpFreq >= 3 ? 0.45 : 0.55;
double diversityWeight = maxOpFreq >= 3 ? 0.55 : 0.45;
// 综合评分:50% 优先级 + 40% 频率多样性 + 10% 随机
double score = 0.5 * priorityScore + 0.4 * diversityScore + 0.1 * rnd.nextDouble();
// 综合评分:动态权重 + 小随机
double score = priorityWeight * priorityScore + diversityWeight * diversityScore + 0.1 * rnd.nextDouble();
scoredOps.add(new Object[]{op, score, groupCount, freq});
}
......@@ -1342,6 +1509,27 @@ public class VariableNeighborhoodSearch {
// 更新该工序被选为优化目标的频率
bottleneckOpFrequency.merge(selectedOp.getOperationId(), 1, Integer::sum);
// === 日志:输出换设备工序选择决策 ===
Entry selEntry = entrybyids.get(selectedOp.getOperationId());
StringBuilder opChangeLog = new StringBuilder();
if (selEntry != null) {
int opFreq = bottleneckOpFrequency.getOrDefault(selectedOp.getOperationId(), 0);
opChangeLog.append(String.format("[换设备-工序选择] 选中=工序%d(组=%d_序号=%d, 优先级=%.2f, 被选次数=%d) | top%d候选:",
selectedOp.getOperationId(), selEntry.getGroupId(), selEntry.getSequence(),
selEntry.getPriority(), opFreq, scoredOps.size()));
for (int i = 0; i < Math.min(5, scoredOps.size()); i++) {
Object[] item = scoredOps.get(i);
GAScheduleResult opItem = (GAScheduleResult) item[0];
double sc = (Double) item[1];
int gc = (Integer) item[2];
int fr = (Integer) item[3];
opChangeLog.append(String.format(" 工序%d(评分=%.3f, 组订单数=%d, 次数=%d)%s",
opItem.getOperationId(), sc, gc, fr,
opItem.getOperationId() == selectedOp.getOperationId() ? "←选中" : ""));
}
log(opChangeLog.toString(), true);
}
int maPos = -1;
Entry entry= entrybyids.get(selectedOp.getOperationId());
......@@ -1515,6 +1703,13 @@ public class VariableNeighborhoodSearch {
}
}
// 计算候选工序中的最大频率
int maxOpFreqMove = 1;
for (GAScheduleResult op : bottleneckOps) {
int freq = bottleneckOpFrequency.getOrDefault(op.getOperationId(), 0);
maxOpFreqMove = Math.max(maxOpFreqMove, freq);
}
// 给每个候选工序计算综合评分
List<Object[]> scoredOps = new ArrayList<>();
for (GAScheduleResult op : bottleneckOps) {
......@@ -1533,12 +1728,17 @@ public class VariableNeighborhoodSearch {
}
double timeScore = maxEndTime > 0 ? (double) endTime / maxEndTime : 0.5;
// 频率多样性评分(0次→1.0, 次数越多越低)
// 改进的频率多样性评分:非线性衰减,避免同一工序被反复选中
int freq = bottleneckOpFrequency.getOrDefault(op.getOperationId(), 0);
double diversityScore = 1.0 / (1.0 + freq);
double diversityScore = Math.pow(0.6, freq); // 0次→1.0, 3次→0.22, 6次→0.05
// 综合评分:35% 优先级 + 25% 时间 + 40% 频率多样性 + 小随机
double score = 0.35 * priorityScore + 0.25 * timeScore + 0.4 * diversityScore + 0.1 * rnd.nextDouble();
// 根据整体频率动态调整权重:频率越高,多样性权重越大
double priorityWeight = maxOpFreqMove >= 3 ? 0.3 : 0.35;
double timeWeight = maxOpFreqMove >= 3 ? 0.2 : 0.25;
double diversityWeight = maxOpFreqMove >= 3 ? 0.5 : 0.4;
// 综合评分:动态权重 + 小随机
double score = priorityWeight * priorityScore + timeWeight * timeScore + diversityWeight * diversityScore + 0.1 * rnd.nextDouble();
scoredOps.add(new Object[]{op, score, freq});
}
......@@ -1563,9 +1763,30 @@ public class VariableNeighborhoodSearch {
// 更新该工序被选为优化目标的频率
bottleneckOpFrequency.merge(selectedOp.getOperationId(), 1, Integer::sum);
// === 日志:输出移动工序选择决策 ===
Entry moveEntry = entrybyids.get(selectedOp.getOperationId());
StringBuilder moveLog = new StringBuilder();
if (moveEntry != null) {
int opFreq = bottleneckOpFrequency.getOrDefault(selectedOp.getOperationId(), 0);
int endTime = orderCompletionTimes.getOrDefault(selectedOp.getGroupId(), 0);
moveLog.append(String.format("[移动工序选择] 选中=工序%d(组=%d_序号=%d, 优先级=%.2f, 完成时间=%d, 被选次数=%d) | top%d候选:",
selectedOp.getOperationId(), moveEntry.getGroupId(), moveEntry.getSequence(),
moveEntry.getPriority(), endTime, opFreq, scoredOps.size()));
for (int i = 0; i < Math.min(5, scoredOps.size()); i++) {
Object[] item = scoredOps.get(i);
GAScheduleResult opItem = (GAScheduleResult) item[0];
double sc = (Double) item[1];
int fr = (Integer) item[2];
moveLog.append(String.format(" 工序%d(评分=%.3f, 次数=%d)%s",
opItem.getOperationId(), sc, fr,
opItem.getOperationId() == selectedOp.getOperationId() ? "←选中" : ""));
}
log(moveLog.toString(), true);
}
Entry entry = entrybyids.get(selectedOp.getOperationId());
if (entry == null) {
log("tryMoveBottleneckOpForward: 找不到工序");
log("tryMoveBottleneckOpForward: 找不到工序", false);
return null;
}
......@@ -2087,53 +2308,75 @@ public class VariableNeighborhoodSearch {
}
private Chromosome generateSameMachineSwapNeighbor(Chromosome chromosome,int idx1,Map<Integer, Entry> positionIndex,Map<String, Integer> machinePositionIndex) {
//有问题
log("generateSameMachineSwapNeighbor"+positionIndex.size());
Chromosome neighbor=copyChromosome(chromosome);// ProductionDeepCopyUtil.deepCopy(chromosome, Chromosome.class);
log("generateSameMachineSwapNeighbor");
Chromosome neighbor=copyChromosome(chromosome);
CopyOnWriteArrayList<Integer> os = neighbor.getOperationSequencing();
CopyOnWriteArrayList<Integer> ms = neighbor.getMachineSelection();
if (os.size() >= 2 && ms.size() >= 2) {
if (os.size() < 2 || ms.size() < 2) {
return neighbor;
}
List<Integer> positions = new ArrayList<>();
Entry op1 = positionIndex.get(idx1);
int maPos1 = machinePositionIndex.get(op1.getGroupId() + "_" + op1.getSequence());
int machineSeq1 = ms.get(maPos1);
// ========== 修复1: op1 必须非 null ==========
Entry op1 = positionIndex.get(idx1);
if (op1 == null) {
return neighbor;
}
MachineOption machineOption1 = op1.getMachineOptions().get(machineSeq1 - 1);
Long machineId1 = machineOption1.getMachineId();
for (int i = 0; i < os.size(); i++) {
Entry op = positionIndex.get(i);
if (op == null) continue;
// ========== 修复2: machinePositionIndex 查找必须非 null 且有效 ==========
String op1Key = op1.getGroupId() + "_" + op1.getSequence();
Integer maPos1 = machinePositionIndex.get(op1Key);
if (maPos1 == null || maPos1 < 0 || maPos1 >= ms.size()) {
return neighbor;
}
int maPos = machinePositionIndex.get(op.getGroupId() + "_" + op.getSequence());
if (maPos < 0) continue;
int machineSeq1 = ms.get(maPos1);
if (machineSeq1 < 1 || machineSeq1 > op1.getMachineOptions().size()) {
return neighbor;
}
int machineSeq = ms.get(maPos);
if (machineSeq < 1 || machineSeq > op.getMachineOptions().size()) continue;
MachineOption machineOption1 = op1.getMachineOptions().get(machineSeq1 - 1);
Long machineId1 = machineOption1.getMachineId();
MachineOption machineOption = op.getMachineOptions().get(machineSeq - 1);
Long machineId = machineOption.getMachineId();
if (machineId == null) continue;
// ========== 修复3: 收集 os 位置(i)而非 ms 位置(maPos)==========
List<Integer> sameMachineOsPositions = new ArrayList<>();
if (machineId.equals(machineId1)&&Math.abs(op1.getPriority() - op.getPriority())==0&&op1.getGroupId()!=op.getGroupId()) {
positions.add(maPos);
}
}
for (int i = 0; i < os.size(); i++) {
if (i == idx1) continue;
Entry op = positionIndex.get(i);
if (op == null) continue;
String opKey = op.getGroupId() + "_" + op.getSequence();
Integer maPos = machinePositionIndex.get(opKey);
if (maPos == null || maPos < 0 || maPos >= ms.size()) continue;
int machineSeq = ms.get(maPos);
if (machineSeq < 1 || machineSeq > op.getMachineOptions().size()) continue;
if (positions.isEmpty()||positions.size() <=1) return neighbor;
int idx2 = positions.get(rnd.nextInt(positions.size()));
MachineOption machineOption = op.getMachineOptions().get(machineSeq - 1);
Long machineId = machineOption.getMachineId();
if (machineId == null) continue;
if(idx1>os.size()||idx2>os.size())
{
int i=0;
if (machineId.equals(machineId1) && op1.getPriority() == op.getPriority() && op1.getGroupId() != op.getGroupId()) {
sameMachineOsPositions.add(i); // 关键: 存储 os 位置 i,不是 ms 位置 maPos
}
}
if (sameMachineOsPositions.isEmpty()) {
return neighbor;
}
Collections.swap(os, idx1, idx2);
neighbor.setOperationSequencing(os);
// ========== 修复4: 确保 idx2 在 os 有效范围内 ==========
int idx2 = sameMachineOsPositions.get(rnd.nextInt(sameMachineOsPositions.size()));
if (idx2 < 0 || idx2 >= os.size() || idx1 < 0 || idx1 >= os.size()) {
return neighbor;
}
// ========== 修复5: swap 使用两个 os 位置,完全一致 ==========
Collections.swap(os, idx1, idx2);
neighbor.setOperationSequencing(os);
return neighbor;
}
......@@ -2490,10 +2733,24 @@ public class VariableNeighborhoodSearch {
* - 然后使用加权轮盘赌选择,得分最高的设备
*/
private int selectMachineByLoad(List<Integer> availableMachineSeqs, List<MachineOption> machineOptions, Chromosome chromosome, String opKey) {
// 计算当前各设备的负载(利用率)
Map<Long, Double> machineUtilization = calculateMachineUtilization(chromosome, true);
// 计算当前各设备的负载(利用率)— 只计算有限产能设备(false)
// 这样利用率计算更准确,不会被无限产能机器的工序"稀释"
Map<Long, Double> machineUtilization = calculateMachineUtilization(chromosome, false);
// 预计算无限产能机器ID集合(这些设备可以作为目标,因为不受时间约束)
Set<Long> infiniteMachineIds = new HashSet<>();
List<GAScheduleResult> allResults = chromosome.getResult();
if (allResults != null) {
for (GAScheduleResult r : allResults) {
if ("Infinite".equals(r.getCapacityTypeName())) {
infiniteMachineIds.add(r.getMachineId());
}
}
}
// 构建可用机器列表,计算综合评分
// 构建候选机器列表:包含有限产能和无限产能机器
// - 有限产能机器:用真实利用率评分
// - 无限产能机器:视为利用率=0(永远空闲),给予高评分
List<MachineOptionWithScore> availableMachines = new ArrayList<>();
for (int seq : availableMachineSeqs) {
......@@ -2502,7 +2759,17 @@ public class VariableNeighborhoodSearch {
}
MachineOption option = machineOptions.get(seq - 1);
Long machineId = option.getMachineId();
double utilization = machineUtilization.getOrDefault(machineId, 0.0);
double utilization;
boolean isInfiniteMachine = infiniteMachineIds.contains(machineId);
if (isInfiniteMachine) {
// 无限产能机器:视为 utilization=0,因为它不受时间约束
utilization = 0.0;
} else {
// 有限产能机器:用真实利用率
utilization = machineUtilization.getOrDefault(machineId, 0.0);
}
// 获取该工序对该设备的选择次数
int frequency = 0;
......@@ -2513,7 +2780,8 @@ public class VariableNeighborhoodSearch {
// 归一化多样性分:选择次数越少,分越高(0~1范围
double diversity = 1.0 / (1.0 + frequency); // 0次→1, 1次→0.5, 2次→0.33...
// 综合评分
// 综合评分:优先选负载低(1-utilization)且历史选择次数少的设备
// 对于无限产能机器,因为 utilization=0,loadBalanceScore=1.0,会得到较高评分
double loadBalanceScore = (1.0 - utilization);
double noise = rnd.nextDouble() * RANDOM_NOISE_FOR_MACHINE;
double score = LOAD_BALANCE_WEIGHT * loadBalanceScore
......@@ -2525,16 +2793,18 @@ public class VariableNeighborhoodSearch {
availableMachines.add(machineWithScore);
}
if (availableMachines.size() == 0) {
return 1;
// 如果没有可用机器,返回第一个选项(兜底)
if (availableMachines.isEmpty()) {
return availableMachineSeqs.get(0);
}
// 按综合评分降序排序
availableMachines.sort((m1, m2) -> Double.compare(m2.score, m1.score));
// 使用加权轮盘赌选择(70%概率选择得分最高的,30%按评分加权随机
// 先计算选中的机器,再输出日志,最后返回
int selectedMachineSeq;
if (rnd.nextDouble() < 0.7) {
return availableMachines.get(0).seq;
selectedMachineSeq = availableMachines.get(0).seq;
} else {
// 按评分加权随机选择
double totalScore = 0;
......@@ -2543,14 +2813,38 @@ public class VariableNeighborhoodSearch {
}
double r = rnd.nextDouble() * totalScore;
double cumSum = 0;
selectedMachineSeq = availableMachines.get(availableMachines.size() - 1).seq;
for (MachineOptionWithScore m : availableMachines) {
cumSum += Math.max(m.score, 0.001);
if (r <= cumSum) {
return m.seq;
selectedMachineSeq = m.seq;
break;
}
}
return availableMachines.get(availableMachines.size() - 1).seq;
}
// === 日志:输出设备分配决策 ===
StringBuilder machineLog = new StringBuilder();
Long selectedId = machineOptions.get(selectedMachineSeq - 1).getMachineId();
boolean selectedIsInfinite = infiniteMachineIds.contains(selectedId);
machineLog.append(String.format("[设备分配] 工序=%s, 选中=设备%d(%s, 利用率=%.2f%%, 历史次数=%d) | 候选%d台:",
opKey != null ? opKey : "未知",
selectedId,
selectedIsInfinite ? "无限产能" : "有限产能",
machineUtilization.getOrDefault(selectedId, 0.0) * 100,
opKey != null ? opMachineSelectFrequency.getOrDefault(opKey, new HashMap<>()).getOrDefault(selectedId, 0) : 0,
availableMachines.size()));
// 找到选中设备的准确评分
for (MachineOptionWithScore m : availableMachines) {
String machineType = infiniteMachineIds.contains(m.option.getMachineId()) ? "无限" : "有限";
machineLog.append(String.format(" 设备%d(%s产能, 评分=%.3f, 利用率=%.2f%%, 次数=%d)%s",
m.option.getMachineId(), machineType, m.score, m.utilization * 100, m.frequency,
m.seq == selectedMachineSeq ? "←选中" : ""));
}
log(machineLog.toString(), false);
return selectedMachineSeq;
}
/**
......
......@@ -297,8 +297,7 @@ public class PlanResultService {
if (!saved) {
throw new BusinessException("排产计算结果保存失败,请稍后重试或联系管理员");
}
WriteScheduleSummary(chromosome);
//WriteScheduleSummary(chromosome);
return chromosome;
......@@ -2062,7 +2061,7 @@ public class PlanResultService {
public void WriteScheduleSummary(Chromosome schedule) {
WriteOperationIntegrityLog(schedule);
//WriteOperationIntegrityLog(schedule);
// 写入日志
FileHelper.writeLogFile("\n=== Schedule Summary === ");
FileHelper.writeLogFile(String.format("ID: %s", schedule.getID()));
......
......@@ -43,7 +43,9 @@ public class PlanResultServiceTest {
// planResultService.execute2("64E64F6B68094AF38CEDC418630C3CC2");//2000
// planResultService.execute2("E1448B3C9C8743DEAB39708F2CFE348A");//倒排bomces
planResultService.execute2("197083D0D26A449EB179AC103C753FD3");
// planResultService.execute2("197083D0D26A449EB179AC103C753FD3");
planResultService.execute2("F8F147BD627C47B1A190399DD7A697F6");
// planResultService.execute2("9FEDFD92BB6A4675BF9B1CC64505D1AB");
// planResultService.execute2("15210B13B88A453F8B84AAC7F16C7541");//2000
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment