Commit 85fa9adb authored by DESKTOP-VKRD9QF\Administration's avatar DESKTOP-VKRD9QF\Administration
parents 59ee6455 8cd195f9
...@@ -111,6 +111,12 @@ ...@@ -111,6 +111,12 @@
<artifactId>hutool-all</artifactId> <artifactId>hutool-all</artifactId>
<version>5.8.16</version> <version>5.8.16</version>
</dependency> </dependency>
<!-- OR-Tools CP-SAT 约束规划求解器 -->
<dependency>
<groupId>com.google.ortools</groupId>
<artifactId>ortools-java</artifactId>
<version>9.7.2996</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
......
...@@ -74,6 +74,13 @@ public class ScheduleParams { ...@@ -74,6 +74,13 @@ public class ScheduleParams {
return populationSize; return populationSize;
} }
/// <summary>
/// 种群规模
/// </summary>
public void setPopulationSize(int populationSize) {
populationSize=populationSize;
}
/// <summary> /// <summary>
/// 最大迭代次数 /// 最大迭代次数
/// </summary> /// </summary>
......
...@@ -64,6 +64,11 @@ public class Entry { ...@@ -64,6 +64,11 @@ public class Entry {
*/ */
private String mainId; private String mainId;
/**
* 拆分来源ID:当state=1时,记录原始工序的id,用于识别同一原始工序的拆分子工序
*/
private transient Integer splitSourceId;
/** /**
* 离散参数 * 离散参数
*/ */
......
package com.aps.service.Algorithm;
import com.aps.entity.Algorithm.Chromosome;
import com.aps.entity.Algorithm.GlobalOperationInfo;
import com.aps.entity.basic.Entry;
import com.aps.entity.basic.Machine;
import com.aps.entity.basic.MachineOption;
import com.google.ortools.Loader;
import com.google.ortools.sat.CpModel;
import com.google.ortools.sat.CpSolver;
import com.google.ortools.sat.CpSolverStatus;
import com.google.ortools.sat.IntVar;
import com.google.ortools.sat.IntervalVar;
import com.google.ortools.sat.LinearArgument;
import com.google.ortools.sat.LinearExpr;
import com.google.ortools.sat.Literal;
import java.time.LocalDateTime;
import java.util.*;
/**
* 基于 OR-Tools CP-SAT 的柔性作业车间调度(FJSP)建模器
* 用于生成高质量的初始染色体编码(machineSelection + operationSequencing)
*
* 建模方式:每个工序使用 Literal (BoolVar) 表示选择哪台机器,
* 通过 optional IntervalVar + NoOverlap 约束保证机器容量。
*
* 作者:佟礼
* 时间:2026-05-21
*/
public class CpSatFjspModel {
private static boolean nativeLibAvailable = false;
static {
try {
Loader.loadNativeLibraries();
nativeLibAvailable = true;
} catch (Exception ignored) {
System.err.println("[CP-SAT] 原生库加载失败,将回退到启发式初始化: " + ignored.getMessage());
}
}
public static boolean isAvailable() {
return nativeLibAvailable;
}
private final List<Machine> machines;
private final List<GlobalOperationInfo> globalOpList;
private final List<Entry> allOperations;
private final int operationCount;
private CpModel model;
private final List<IntVar> startVars = new ArrayList<>();
private final List<IntVar> endVars = new ArrayList<>();
private final List<Literal[]> presenceMatrix = new ArrayList<>();
private final Map<Long, List<IntervalVar>> machineToIntervals = new LinkedHashMap<>();
private IntVar makespanVar;
private int horizonSeconds;
public CpSatFjspModel(List<GlobalOperationInfo> globalOpList,
List<Entry> allOperations,
List<Machine> machines,
LocalDateTime baseTime) {
this.globalOpList = globalOpList;
this.allOperations = allOperations;
this.machines = machines;
this.operationCount = globalOpList.size();
}
private int estimateHorizon() {
double totalProcessing = 0;
for (GlobalOperationInfo gop : globalOpList) {
Entry op = gop.getOp();
if (op.getMachineOptions() != null && !op.getMachineOptions().isEmpty()) {
double avgTime = op.getMachineOptions().stream()
.mapToDouble(MachineOption::getProcessingTime)
.average()
.orElse(0);
totalProcessing += avgTime;
}
}
int machineCount = Math.max(machines.size(), 1);
return Math.max((int) Math.ceil(totalProcessing * 4.0 / machineCount) + 86400, 3600);
}
/**
* 构建 FJSP 的 CP-SAT 模型(创建新模型实例)
*/
public CpModel build() {
model = new CpModel();
startVars.clear();
endVars.clear();
presenceMatrix.clear();
machineToIntervals.clear();
buildModelOnModel(model);
return model;
}
/**
* 在指定 model 实例上构建全部变量和约束
*/
private void buildModelOnModel(CpModel targetModel) {
this.model = targetModel;
horizonSeconds = estimateHorizon();
for (Machine m : machines) {
machineToIntervals.put(m.getId(), new ArrayList<>());
}
// 1. 工序变量创建 + machineIndex → duration 映射
// 2. 同订单工序顺序(groupId + sequence)
// 3. Priority 硬约束
// ├── 按 distinct Priority 值分组(TreeMap 自动升序)
// ├── Priority=1 全部完成 → Priority=3 才能开始
// ├── Priority=3 全部完成 → Priority=5 才能开始
// └── ... 只需 (distinct数 - 1) 条约束
// 4. 机器容量 no_overlap
// 5. 目标函数:makespan + Priority 加权完工时间
for (int i = 0; i < operationCount; i++) {
Entry op = globalOpList.get(i).getOp();
List<MachineOption> options = op.getMachineOptions();
if (options == null || options.isEmpty()) {
throw new RuntimeException(
"工序没有设置设备: " + op.getOrderCode() + ":" + op.getSequence());
}
int maxDuration = 0;
for (MachineOption mo : options) {
maxDuration = Math.max(maxDuration, (int) mo.getProcessingTime());
}
IntVar startVar = model.newIntVar(0, horizonSeconds, "s_" + i);
IntVar endVar = model.newIntVar(0, horizonSeconds, "e_" + i);
IntervalVar mainInterval = model.newIntervalVar(
startVar, model.newConstant(maxDuration), endVar, "iv_" + i);
Literal[] presences = new Literal[options.size()];
for (int j = 0; j < options.size(); j++) {
MachineOption mo = options.get(j);
int procTime = (int) mo.getProcessingTime();
Long machineId = mo.getMachineId();
presences[j] = model.newBoolVar("p_" + i + "_" + j);
IntervalVar optInterval = model.newOptionalIntervalVar(
startVar, model.newConstant(procTime), endVar, presences[j],
"opt_" + i + "_m" + machineId);
List<IntervalVar> list = machineToIntervals.computeIfAbsent(
machineId, k -> new ArrayList<>());
list.add(optInterval);
}
model.addBoolOr(presences);
for (int j = 0; j < presences.length; j++) {
for (int k = j + 1; k < presences.length; k++) {
model.addBoolOr(new Literal[]{
presences[j].not(), presences[k].not()});
}
}
startVars.add(startVar);
endVars.add(endVar);
presenceMatrix.add(presences);
}
Map<Integer, List<Integer>> orderOpsMap = new LinkedHashMap<>();
for (int i = 0; i < operationCount; i++) {
orderOpsMap.computeIfAbsent(globalOpList.get(i).getGroupId(),
k -> new ArrayList<>()).add(i);
}
for (List<Integer> ops : orderOpsMap.values()) {
ops.sort(Comparator.comparingInt(idx -> globalOpList.get(idx).getSequence()));
for (int k = 0; k < ops.size() - 1; k++) {
model.addLessOrEqual(endVars.get(ops.get(k)), startVars.get(ops.get(k + 1)));
}
}
Map<Double, List<Integer>> priorityGroups = new TreeMap<>();
for (int i = 0; i < operationCount; i++) {
double p = globalOpList.get(i).getOp().getPriority();
if (p > 0) {
priorityGroups.computeIfAbsent(p, k -> new ArrayList<>()).add(i);
}
}
List<Double> sortedPriorities = new ArrayList<>(priorityGroups.keySet());
for (int k = 0; k < sortedPriorities.size() - 1; k++) {
List<Integer> hiPriOps = priorityGroups.get(sortedPriorities.get(k));
List<Integer> loPriOps = priorityGroups.get(sortedPriorities.get(k + 1));
IntVar hiMaxEnd = model.newIntVar(0, horizonSeconds,
"hiEnd_" + k);
IntVar loMinStart = model.newIntVar(0, horizonSeconds,
"loStart_" + k);
for (int idx : hiPriOps) {
model.addLessOrEqual(endVars.get(idx), hiMaxEnd);
}
for (int idx : loPriOps) {
model.addGreaterOrEqual(startVars.get(idx), loMinStart);
}
model.addLessOrEqual(hiMaxEnd, loMinStart);
}
for (Map.Entry<Long, List<IntervalVar>> entry : machineToIntervals.entrySet()) {
List<IntervalVar> intervals = entry.getValue();
if (intervals.size() > 1) {
model.addNoOverlap(intervals.toArray(new IntervalVar[0]));
}
}
makespanVar = model.newIntVar(0, horizonSeconds, "makespan");
for (int i = 0; i < operationCount; i++) {
model.addLessOrEqual(endVars.get(i), makespanVar);
}
double maxPriority = 0;
for (int i = 0; i < operationCount; i++) {
maxPriority = Math.max(maxPriority,
globalOpList.get(i).getOp().getPriority());
}
int priorityTerms = 0;
for (int i = 0; i < operationCount; i++) {
if (globalOpList.get(i).getOp().getPriority() > 0) priorityTerms++;
}
LinearArgument[] objVars = new LinearArgument[1 + priorityTerms];
long[] objCoeffs = new long[1 + priorityTerms];
objVars[0] = makespanVar;
objCoeffs[0] = 100;
int t = 1;
for (int i = 0; i < operationCount; i++) {
double priority = globalOpList.get(i).getOp().getPriority();
if (priority > 0) {
objVars[t] = endVars.get(i);
objCoeffs[t] = (long)(maxPriority - priority + 1);
t++;
}
}
model.minimize(LinearExpr.weightedSum(objVars, objCoeffs));
}
/**
* 单次求解,返回一个 Chromosome
*/
public Chromosome solveOnce(int timeLimitSeconds) {
if (model == null) {
build();
}
CpSolver solver = new CpSolver();
solver.getParameters().setMaxTimeInSeconds(timeLimitSeconds);
solver.getParameters().setLogSearchProgress(false);
solver.getParameters().setNumSearchWorkers(4);
CpSolverStatus status = solver.solve(model);
if (status == CpSolverStatus.OPTIMAL || status == CpSolverStatus.FEASIBLE) {
return extractChromosome(solver);
}
return null;
}
/**
* 生成多个多样性解(通过随机种子 + 不同目标权重)
*/
public List<Chromosome> generateDiverseSolutions(int targetCount,
int timeBudgetSec,
boolean randomPerturb) {
List<Chromosome> results = new ArrayList<>();
if (targetCount <= 0) return results;
Random rnd = new Random();
int perSolveTime = Math.max(timeBudgetSec / Math.min(targetCount, 5), 3);
int maxRounds = Math.min(targetCount * 2, 15);
for (int round = 0; round < maxRounds && results.size() < targetCount; round++) {
model = new CpModel();
startVars.clear();
endVars.clear();
presenceMatrix.clear();
machineToIntervals.clear();
buildModelOnModel(model);
if (round % 3 == 1 && operationCount > 0) {
double maxPriority = 0;
for (int i = 0; i < operationCount; i++) {
maxPriority = Math.max(maxPriority,
globalOpList.get(i).getOp().getPriority());
}
int priorityCount = 0;
for (int i = 0; i < operationCount; i++) {
if (globalOpList.get(i).getOp().getPriority() > 0) priorityCount++;
}
int totalTerms = 1 + operationCount + priorityCount;
LinearArgument[] objVars = new LinearArgument[totalTerms];
long[] objCoeffs = new long[totalTerms];
objVars[0] = makespanVar;
objCoeffs[0] = 100;
int t = 1;
for (int i = 0; i < operationCount; i++) {
objVars[t] = endVars.get(i);
objCoeffs[t] = 1;
t++;
}
for (int i = 0; i < operationCount; i++) {
double priority = globalOpList.get(i).getOp().getPriority();
if (priority > 0) {
objVars[t] = endVars.get(i);
objCoeffs[t] = (long)(maxPriority - priority + 1);
t++;
}
}
model.minimize(LinearExpr.weightedSum(objVars, objCoeffs));
}
CpSolver solver = new CpSolver();
solver.getParameters().setMaxTimeInSeconds(perSolveTime);
solver.getParameters().setLogSearchProgress(false);
solver.getParameters().setNumSearchWorkers(4);
solver.getParameters().setRandomSeed((int) (round * 1000L + rnd.nextInt(1000)));
CpSolverStatus status = solver.solve(model);
if (status == CpSolverStatus.OPTIMAL || status == CpSolverStatus.FEASIBLE) {
Chromosome chromo = extractChromosome(solver);
if (chromo != null && !containsDuplicate(results, chromo)) {
chromo.setGsOrls(4);
chromo.setGenerateType("CP-SAT");
results.add(chromo);
}
}
}
return results;
}
private Chromosome extractChromosome(CpSolver solver) {
Chromosome chromo = new Chromosome();
chromo.setGsOrls(4);
chromo.setGenerateType("CP-SAT");
List<Integer> ms = new ArrayList<>();
for (int i = 0; i < operationCount; i++) {
Literal[] pres = presenceMatrix.get(i);
int selectedIdx = 0;
for (int j = 0; j < pres.length; j++) {
if (solver.booleanValue(pres[j])) {
selectedIdx = j;
break;
}
}
ms.add(selectedIdx + 1);
}
chromo.setMachineSelection(ms);
List<OpTimeRecord> records = new ArrayList<>();
for (int i = 0; i < operationCount; i++) {
GlobalOperationInfo gop = globalOpList.get(i);
int machineIdx = ms.get(i) - 1;
records.add(new OpTimeRecord(
gop.getGroupId(),
gop.getSequence(),
(int) solver.value(startVars.get(i)),
machineIdx,
i));
}
records.sort(Comparator
.comparingInt(OpTimeRecord::getStartTime)
.thenComparingInt(OpTimeRecord::getMachineIdx));
List<Integer> os = new ArrayList<>();
for (OpTimeRecord rec : records) {
os.add(rec.getGroupId());
}
chromo.setOperationSequencing(os);
chromo.setMakespan(solver.value(makespanVar));
return chromo;
}
private boolean containsDuplicate(List<Chromosome> list, Chromosome candidate) {
String candidateKey = candidate.getGeneStr();
for (Chromosome c : list) {
if (c.getGeneStr().equals(candidateKey)) {
return true;
}
}
return false;
}
private static class OpTimeRecord {
private final int groupId;
private final int sequence;
private final int startTime;
private final int machineIdx;
private final int globalIdx;
OpTimeRecord(int groupId, int sequence, int startTime, int machineIdx, int globalIdx) {
this.groupId = groupId;
this.sequence = sequence;
this.startTime = startTime;
this.machineIdx = machineIdx;
this.globalIdx = globalIdx;
}
int getGroupId() { return groupId; }
int getSequence() { return sequence; }
int getStartTime() { return startTime; }
int getMachineIdx() { return machineIdx; }
}
}
\ No newline at end of file
package com.aps.service.Algorithm;
import com.aps.common.util.FileHelper;
import com.aps.entity.Algorithm.Chromosome;
import com.aps.entity.Algorithm.GlobalOperationInfo;
import com.aps.entity.basic.Entry;
import com.aps.entity.basic.Machine;
import com.aps.entity.basic.MachineOption;
import com.aps.entity.basic.Order;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
/**
* CP-SAT 自适应初始种群生成器
* 根据工序规模自动选择策略,确保不阻塞主流程
*
* 策略分级:
* ≤100道 :CP-SAT 全力求解,生成多个高质量解
* 100~500道:CP-SAT 限时截断,配合启发式补充
* 500~800道:瓶颈分解 + 分组 CP-SAT
* >800道 :纯启发式 fallback
*
* 作者:佟礼
* 时间:2026-05-21
*/
public class CpSatInitializer {
private static final int SMALL_SCALE_THRESHOLD = 100;
private static final int MEDIUM_SCALE_THRESHOLD = 500;
private static final int LARGE_SCALE_THRESHOLD = 800;
private final List<Entry> allOperations;
private final List<Machine> machines;
private final List<Order> orders;
private final LocalDateTime baseTime;
public CpSatInitializer(List<Entry> allOperations,
List<Machine> machines,
List<Order> orders,
LocalDateTime baseTime) {
this.allOperations = allOperations;
this.machines = machines;
this.orders = orders;
this.baseTime = baseTime;
}
/**
* 自适应生成初始种群(带 fallback)
*
* @param globalOpList 全局工序列表
* @param targetCount 目标 CP-SAT 解的数量
* @param timeBudgetSec 总时间预算(秒)
* @return CP-SAT 生成的 Chromosome 列表,失败时返回空列表
*/
public List<Chromosome> generate(List<GlobalOperationInfo> globalOpList,
int targetCount,
int timeBudgetSec) {
if (!CpSatFjspModel.isAvailable()) {
FileHelper.writeLogFile("[CP-SAT] 原生库不可用,跳过");
return Collections.emptyList();
}
int opCount = globalOpList.size();
FileHelper.writeLogFile(String.format(
"[CP-SAT] 工序数=%d, 目标解数=%d, 时间预算=%ds", opCount, targetCount, timeBudgetSec));
try {
if (opCount <= SMALL_SCALE_THRESHOLD) {
return smallScaleSolve(globalOpList, targetCount, timeBudgetSec);
} else if (opCount <= MEDIUM_SCALE_THRESHOLD) {
return mediumScaleSolve(globalOpList, targetCount, timeBudgetSec);
} else if (opCount <= LARGE_SCALE_THRESHOLD) {
return largeScaleDecomposed(globalOpList, targetCount, timeBudgetSec);
} else {
FileHelper.writeLogFile("[CP-SAT] 工序数超过阈值,跳过CP-SAT,回退启发式");
return Collections.emptyList();
}
} catch (Exception e) {
FileHelper.writeLogFile("[CP-SAT] 求解异常,回退启发式: " + e.getMessage());
return Collections.emptyList();
}
}
/**
* 小规模:全力求解多个高质量解
*/
private List<Chromosome> smallScaleSolve(List<GlobalOperationInfo> globalOpList,
int targetCount, int timeBudgetSec) {
CpSatFjspModel model = new CpSatFjspModel(globalOpList, allOperations, machines, baseTime);
return model.generateDiverseSolutions(
Math.min(targetCount, 8), timeBudgetSec, true);
}
/**
* 中规模:限时截断,少生成几个解
*/
private List<Chromosome> mediumScaleSolve(List<GlobalOperationInfo> globalOpList,
int targetCount, int timeBudgetSec) {
CpSatFjspModel model = new CpSatFjspModel(globalOpList, allOperations, machines, baseTime);
int effectiveTarget = Math.min(targetCount, 5);
int perSolveTime = Math.max(timeBudgetSec, 15);
return model.generateDiverseSolutions(effectiveTarget, perSolveTime, true);
}
/**
* 大规模:瓶颈分解策略
* 只对瓶颈机器上的工序使用 CP-SAT,其余用贪心分配
*/
private List<Chromosome> largeScaleDecomposed(List<GlobalOperationInfo> globalOpList,
int targetCount, int timeBudgetSec) {
List<Chromosome> results = new ArrayList<>();
int effectiveTarget = Math.min(targetCount, 3);
// 1. 识别瓶颈机器:总加工负荷最高的机器
List<Long> bottleneckMachineIds = findBottleneckMachineIds(globalOpList, 3);
// 2. 分离瓶颈工序 vs 非瓶颈工序
List<GlobalOperationInfo> bottleneckOps = new ArrayList<>();
List<GlobalOperationInfo> nonBottleneckOps = new ArrayList<>();
for (GlobalOperationInfo gop : globalOpList) {
Entry op = gop.getOp();
if (op.getMachineOptions() != null) {
boolean isOnBottleneck = op.getMachineOptions().stream()
.anyMatch(mo -> bottleneckMachineIds.contains(mo.getMachineId()));
if (isOnBottleneck) {
bottleneckOps.add(gop);
} else {
nonBottleneckOps.add(gop);
}
} else {
nonBottleneckOps.add(gop);
}
}
if (bottleneckOps.isEmpty()) {
FileHelper.writeLogFile("[CP-SAT] 未识别到瓶颈,回退中规模求解");
return mediumScaleSolve(globalOpList, effectiveTarget, timeBudgetSec);
}
FileHelper.writeLogFile(String.format(
"[CP-SAT] 瓶颈分解: 瓶颈工序=%d, 非瓶颈工序=%d",
bottleneckOps.size(), nonBottleneckOps.size()));
// 3. 对瓶颈工序用 CP-SAT
List<Machine> bottleneckMachines = machines.stream()
.filter(m -> bottleneckMachineIds.contains(m.getId()))
.collect(Collectors.toList());
if (bottleneckMachines.isEmpty()) {
bottleneckMachines = machines;
}
CpSatFjspModel model = new CpSatFjspModel(
bottleneckOps, allOperations, bottleneckMachines, baseTime);
List<Chromosome> cpSatResults = model.generateDiverseSolutions(
effectiveTarget, timeBudgetSec, true);
// 4. 对非瓶颈工序填入贪心分配
for (Chromosome chromo : cpSatResults) {
fillGreedyForNonBottleneck(chromo, bottleneckOps, nonBottleneckOps, globalOpList);
results.add(chromo);
}
return results;
}
/**
* 找出负载最高的前 N 台瓶颈机器
*/
private List<Long> findBottleneckMachineIds(List<GlobalOperationInfo> globalOpList, int topN) {
Map<Long, Double> machineTotalLoad = new LinkedHashMap<>();
for (GlobalOperationInfo gop : globalOpList) {
Entry op = gop.getOp();
if (op.getMachineOptions() != null) {
for (MachineOption mo : op.getMachineOptions()) {
machineTotalLoad.put(mo.getMachineId(),
machineTotalLoad.getOrDefault(mo.getMachineId(), 0.0)
+ mo.getProcessingTime());
}
}
}
return machineTotalLoad.entrySet().stream()
.sorted(Map.Entry.<Long, Double>comparingByValue().reversed())
.limit(topN)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
/**
* 为非瓶颈工序填入贪心 machineSelection 和 operationSequencing
*/
private void fillGreedyForNonBottleneck(Chromosome chromo,
List<GlobalOperationInfo> bottleneckOps,
List<GlobalOperationInfo> nonBottleneckOps,
List<GlobalOperationInfo> fullGlobalOpList) {
if (nonBottleneckOps.isEmpty()) return;
Map<Long, Double> machineLoad = new LinkedHashMap<>();
Random rnd = new Random();
// 按全局顺序收集所有工序的 MS 和 OS
List<Integer> fullMs = new ArrayList<>();
List<Integer> fullOs = new ArrayList<>();
int bottleneckIdx = 0;
int nonBottleneckIdx = 0;
List<Integer> cpSatMs = chromo.getMachineSelection();
List<Integer> cpSatOs = chromo.getOperationSequencing();
// 重建完整的 machineSelection(保持全局顺序)
for (GlobalOperationInfo gop : fullGlobalOpList) {
boolean isBottleneck = bottleneckOps.contains(gop);
boolean isNonBottleneck = nonBottleneckOps.contains(gop);
if (isBottleneck && bottleneckIdx < cpSatMs.size()) {
fullMs.add(cpSatMs.get(bottleneckIdx));
if (bottleneckIdx < cpSatOs.size()) {
fullOs.add(cpSatOs.get(bottleneckIdx));
}
bottleneckIdx++;
} else if (isNonBottleneck) {
Entry op = gop.getOp();
List<MachineOption> options = op.getMachineOptions();
int selectedIdx = selectMinLoadMachine(options, machineLoad, rnd);
fullMs.add(selectedIdx + 1);
MachineOption selected = options.get(selectedIdx);
machineLoad.put(selected.getMachineId(),
machineLoad.getOrDefault(selected.getMachineId(), 0.0)
+ selected.getProcessingTime());
fullOs.add(gop.getGroupId());
nonBottleneckIdx++;
}
}
chromo.setMachineSelection(fullMs);
chromo.setOperationSequencing(fullOs);
}
private int selectMinLoadMachine(List<MachineOption> options,
Map<Long, Double> machineLoad,
Random rnd) {
double minLoad = options.stream()
.mapToDouble(m -> machineLoad.getOrDefault(m.getMachineId(), 0.0)
+ m.getProcessingTime())
.min()
.orElse(Double.MAX_VALUE);
List<Integer> candidates = new ArrayList<>();
for (int i = 0; i < options.size(); i++) {
MachineOption mo = options.get(i);
double candidateLoad = machineLoad.getOrDefault(mo.getMachineId(), 0.0)
+ mo.getProcessingTime();
if (Math.abs(candidateLoad - minLoad) < 0.01) {
candidates.add(i);
}
}
return candidates.get(rnd.nextInt(candidates.size()));
}
}
\ No newline at end of file
...@@ -597,7 +597,7 @@ public class GeneticDecoder { ...@@ -597,7 +597,7 @@ public class GeneticDecoder {
//存储锚点时间 //存储锚点时间
if(isJit) if(isJit)
{ {
orderSchedulingInfo.put(order.getId(), new AbstractMap.SimpleEntry<>(false, end)); orderSchedulingInfo.put(order.getId(), new AbstractMap.SimpleEntry<>(true, end));
}else { }else {
orderSchedulingInfo.put(order.getId(), new AbstractMap.SimpleEntry<>(false, 0)); orderSchedulingInfo.put(order.getId(), new AbstractMap.SimpleEntry<>(false, 0));
...@@ -1408,11 +1408,11 @@ public class GeneticDecoder { ...@@ -1408,11 +1408,11 @@ public class GeneticDecoder {
int bomtime = 0; int bomtime = 0;
if (needMaterialCheck&&islockMachineTime) { if (needMaterialCheck&&islockMachineTime) {
if(!isJit) {
bomtime = getOperationBOMTime(operation, chromosome, earliestStartTime, 2);
bomtime = getOperationBOMTime(operation, chromosome,earliestStartTime, 2);
earliestStartTime = Math.max(earliestStartTime, bomtime); earliestStartTime = Math.max(earliestStartTime, bomtime);
} }
}
// 正式落排前,再取一次当前机台最后一道工序,保证换型计算基于最新排程结果。 // 正式落排前,再取一次当前机台最后一道工序,保证换型计算基于最新排程结果。
...@@ -1524,7 +1524,16 @@ public class GeneticDecoder { ...@@ -1524,7 +1524,16 @@ public class GeneticDecoder {
// } // }
//扣库存 //扣库存
if (needMaterialCheck && islockMachineTime) { if (needMaterialCheck && islockMachineTime) {
if(!isJit) {
EditOperationBOMTime(operation, chromosome, startTime, machineTasksCache, entryIndexById, scheduleIndexById); EditOperationBOMTime(operation, chromosome, startTime, machineTasksCache, entryIndexById, scheduleIndexById);
}else {
int bomtime1 = getOperationBOMTime(operation, chromosome, startTime, 2);
if(bomtime1<=startTime)
{
EditOperationBOMTime(operation, chromosome, startTime, machineTasksCache, entryIndexById, scheduleIndexById);
}
}
} }
//换型时间是否占用设备加工时间 //换型时间是否占用设备加工时间
//10:00 开始上班 前面的任务:24:00 结束 开始换型 休息时间 10小时 //10:00 开始上班 前面的任务:24:00 结束 开始换型 休息时间 10小时
......
...@@ -117,7 +117,9 @@ public class HybridAlgorithm { ...@@ -117,7 +117,9 @@ public class HybridAlgorithm {
// 步骤1:使用构造启发式算法生成初始种群 // 步骤1:使用构造启发式算法生成初始种群
FileHelper.writeLogFile("构造启发式初始化-----------开始-------"); FileHelper.writeLogFile("构造启发式初始化-----------开始-------");
List<Chromosome> population = initialization.generateHeuristicInitialPopulation(param); // List<Chromosome> population = initialization.generateHeuristicInitialPopulation(param);
List<Chromosome> population = initialization.generateHybridInitialPopulation(param);
FileHelper.writeLogFile("构造启发式初始化-----------结束-------"); FileHelper.writeLogFile("构造启发式初始化-----------结束-------");
......
package com.aps.service.Algorithm; package com.aps.service.Algorithm;
import com.aps.common.util.FileHelper;
import com.aps.common.util.ProductionDeepCopyUtil; import com.aps.common.util.ProductionDeepCopyUtil;
import com.aps.entity.Algorithm.Chromosome; import com.aps.entity.Algorithm.Chromosome;
import com.aps.entity.Algorithm.GlobalOperationInfo; import com.aps.entity.Algorithm.GlobalOperationInfo;
...@@ -1071,6 +1072,167 @@ public class Initialization { ...@@ -1071,6 +1072,167 @@ public class Initialization {
return shuffled; return shuffled;
} }
/**
* HybridAlgorithm 专用混合初始种群
* 先用 CP-SAT 生成高质量种子,剩余用原有构造启发式补足
*/
public List<Chromosome> generateHybridInitialPopulation(ScheduleParams param) {
int populationSize = param.getPopulationSize();
this.baseTime = param.getBaseTime();
List<GlobalOperationInfo> sharedGlobalOpList = generateGlobalOpList();
List<Chromosome> population = new ArrayList<>(populationSize);
int cpSatTarget = Math.min(Math.max(populationSize / 5, 2), 6);
int timeBudgetSec = Math.max(populationSize * 2, 15);
CpSatInitializer cpSatInit = new CpSatInitializer(
allOperations, machines, orders, param.getBaseTime());
List<Chromosome> cpSatResults = cpSatInit.generate(
sharedGlobalOpList, cpSatTarget, timeBudgetSec);
if (!cpSatResults.isEmpty()) {
for (Chromosome chromo : cpSatResults) {
chromo.setOrders(new CopyOnWriteArrayList<>(orders));
chromo.setGlobalOpList(deepCopyGlobalOpList(sharedGlobalOpList));
PriorityCheckResult r = Initialization.checkPriorityOrder(chromo);
// if (r.isPerfect()) {
// population.add(chromo);
//
// }
}
population.addAll(cpSatResults);
FileHelper.writeLogFile(
"[Hybrid初始化] CP-SAT贡献 " + cpSatResults.size() + " 个高质量种子");
}
int remaining = populationSize - population.size();
if (remaining > 0) {
ScheduleParams subParam = new ScheduleParams();
subParam.setPopulationSize(remaining);
subParam.setBaseTime(param.getBaseTime());
List<Chromosome> heuristicPopulation =
generateHeuristicInitialPopulation(subParam);
for (Chromosome chromo : heuristicPopulation) {
chromo.setOrders(new CopyOnWriteArrayList<>(orders));
}
population.addAll(heuristicPopulation);
}
long cpSatCount = population.stream()
.filter(c -> c.getGsOrls() == 4).count();
FileHelper.writeLogFile(String.format(
"[Hybrid初始化] 总种群=%d, CP-SAT=%d, 启发式=%d",
population.size(), cpSatCount,
population.size() - cpSatCount));
return population;
}
private List<GlobalOperationInfo> deepCopyGlobalOpList(
List<GlobalOperationInfo> source) {
List<GlobalOperationInfo> copy = new ArrayList<>(source.size());
for (GlobalOperationInfo info : source) {
GlobalOperationInfo newInfo = new GlobalOperationInfo();
newInfo.setGlobalOpId(info.getGlobalOpId());
newInfo.setGroupId(info.getGroupId());
newInfo.setSequence(info.getSequence());
newInfo.setOp(info.getOp());
copy.add(newInfo);
}
return copy;
}
/**
* 检查染色体的operationSequencing是否遵守Priority排序
* Priority值越小优先级越高(1最先排,99最后排)
*
* @param chromo 待检查的染色体
* @return PriorityCheckResult 包含违反次数、严重度、详细报告
*/
public static PriorityCheckResult checkPriorityOrder(Chromosome chromo) {
List<GlobalOperationInfo> globalOpList = chromo.getGlobalOpList();
List<Integer> os = chromo.getOperationSequencing();
if (os == null || os.isEmpty() || globalOpList == null || globalOpList.isEmpty()) {
return new PriorityCheckResult(0, 0, 0, "无数据");
}
Map<Integer, Double> groupPriority = new HashMap<>();
Map<Integer, List<Entry>> groupOps = new HashMap<>();
for (GlobalOperationInfo info : globalOpList) {
int gid = info.getGroupId();
groupPriority.putIfAbsent(gid, info.getOp().getPriority());
groupOps.computeIfAbsent(gid, k -> new ArrayList<>()).add(info.getOp());
}
int violationCount = 0;
int totalSeverity = 0;
double maxSingleSeverity = 0;
StringBuilder detail = new StringBuilder();
for (int i = 0; i < os.size() - 1; i++) {
int gidA = os.get(i);
int gidB = os.get(i + 1);
if (gidA == gidB) continue;
double priA = groupPriority.getOrDefault(gidA, Double.MAX_VALUE);
double priB = groupPriority.getOrDefault(gidB, Double.MAX_VALUE);
if (priA > priB) {
double severity = priA - priB;
violationCount++;
totalSeverity += severity;
maxSingleSeverity = Math.max(maxSingleSeverity, severity);
if (detail.length() < 800) {
detail.append(String.format(
" [%d→%d] groupId=%d(优先级%d) 排在 groupId=%d(优先级%d) 之前,差距=%d\n",
i, i + 1, gidA, priA, gidB, priB, severity));
}
}
}
String report = String.format(
"Priority检查: 违反次数=%d, 总严重度=%d, 最大单次差距=%f, " +
"工序组数=%d, 不同优先级组数=%d\n%s",
violationCount, totalSeverity, maxSingleSeverity,
groupPriority.size(), new HashSet<>(groupPriority.values()).size(),
detail.toString());
return new PriorityCheckResult(violationCount, totalSeverity,
maxSingleSeverity, report);
}
/**
* Priority检查结果
*/
public static class PriorityCheckResult {
public final int violationCount;
public final int totalSeverity;
public final double maxSingleSeverity;
public final String report;
PriorityCheckResult(int violationCount, int totalSeverity,
double maxSingleSeverity, String report) {
this.violationCount = violationCount;
this.totalSeverity = totalSeverity;
this.maxSingleSeverity = maxSingleSeverity;
this.report = report;
}
public boolean isPerfect() {
return violationCount == 0;
}
@Override
public String toString() {
return report;
}
}
/** /**
* 辅助类:用于权重排序 * 辅助类:用于权重排序
*/ */
......
package com.aps.service.Algorithm;
import com.aps.common.util.FileHelper;
import com.aps.common.util.ProductionDeepCopyUtil;
import com.aps.entity.Algorithm.OperationDependency;
import com.aps.entity.basic.Entry;
import com.aps.entity.basic.GlobalParam;
import com.aps.entity.basic.Machine;
import com.aps.entity.basic.MachineOption;
import java.util.*;
import java.util.stream.Collectors;
/**
* 多设备工序拆分服务
* 当 GlobalParam.IsMultipleMachine = true 时,
* 将拥有多个可选设备的工序按策略拆分为多个子工序,每道子工序分配一台设备,可并行加工。
*
* 数据对齐 ScheduleOperationService.SpiltOperation:
* - mainId:同源拆分子工序共享的 UUID
* - state=1(拆分/原始)、state=2(新建)
* - newCreate:新建标记
* - execId:第一个保留原始,其余新生成 UUID
*
* 作者:佟礼
* 时间:2026-05-21
*/
public class OperationSplitService {
/**
* 对工序列表进行多设备拆分(预处理入口,结合设备负载)
*
* @param allOperations 原始工序列表
* @param globalParam 全局参数
* @param machines 设备列表(用于计算各设备总预期负载)
* @return 拆分后的工序列表
*/
public static List<Entry> splitMultiMachineOperations(List<Entry> allOperations,
GlobalParam globalParam,
List<Machine> machines) {
if (allOperations == null || allOperations.isEmpty()) {
return allOperations;
}
if (!globalParam.isIsMultipleMachine()) {
return allOperations;
}
boolean hasMultiMachine = allOperations.stream()
.anyMatch(op -> op.getMachineOptions() != null && op.getMachineOptions().size() > 1);
if (!hasMultiMachine) {
FileHelper.writeLogFile("[工序拆分] 没有多设备工序,跳过拆分");
return allOperations;
}
FileHelper.writeLogFile("[工序拆分] 开始多设备工序拆分(负载感知),原始工序数=" + allOperations.size());
Map<Long, Double> machineTotalLoad = calcMachineTotalLoad(allOperations);
int maxId = allOperations.stream().mapToInt(Entry::getId).max().orElse(0);
int nextId = maxId + 1;
Map<Integer, List<Integer>> splitMapping = new LinkedHashMap<>();
List<Entry> result = new ArrayList<>();
Map<Integer, List<Entry>> groupMap = new LinkedHashMap<>();
for (Entry op : allOperations) {
groupMap.computeIfAbsent(op.getGroupId(), k -> new ArrayList<>()).add(op);
}
int totalSplitCount = 0;
int totalSplitOps = 0;
for (Map.Entry<Integer, List<Entry>> groupEntry : groupMap.entrySet()) {
List<Entry> groupOps = groupEntry.getValue();
groupOps.sort(Comparator.comparingInt(Entry::getSequence));
List<Entry> newGroupOps = new ArrayList<>();
for (Entry op : groupOps) {
List<MachineOption> options = op.getMachineOptions();
if (options != null && options.size() > 1) {
List<Entry> subOps = splitEntryByLoad(op, options, nextId, machineTotalLoad);
nextId += subOps.size();
newGroupOps.addAll(subOps);
splitMapping.put(op.getId(),
subOps.stream().map(Entry::getId).collect(Collectors.toList()));
totalSplitOps++;
totalSplitCount += subOps.size();
} else {
newGroupOps.add(op);
}
}
for (int i = 0; i < newGroupOps.size(); i++) {
newGroupOps.get(i).setSequence(i + 1);
}
result.addAll(newGroupOps);
}
updateAllDependencies(result, splitMapping);
FileHelper.writeLogFile(String.format(
"[工序拆分] 拆分了%d道多设备工序 → 产生%d道子工序,最终总工序数=%d",
totalSplitOps, totalSplitCount, result.size()));
return result;
}
/**
* 按设备总负载比例拆分单道工序(预处理用)
* 设备负载越高,分到的数量越少
*/
public static List<Entry> splitEntryByLoad(Entry original, List<MachineOption> options,
int startId, Map<Long, Double> machineTotalLoad) {
return doSplit(original, options, startId, machineTotalLoad, null, null);
}
/**
* 按设备即时负载比例拆分单道工序(初始化 GS/LS 用)
* 设备当前累计负载越高,分到的数量越少
*/
public static List<Entry> splitEntryByRuntimeLoad(Entry original, List<MachineOption> options,
int startId, Map<Long, Double> machineRuntimeLoad) {
return doSplit(original, options, startId, machineRuntimeLoad, null, null);
}
/**
* 随机比例拆分单道工序(随机初始化 / 启发式用)
*/
public static List<Entry> splitEntryRandom(Entry original, List<MachineOption> options,
int startId, Random rnd) {
return doSplit(original, options, startId, null, null, rnd);
}
/**
* 随机选择部分设备拆分(概率随机的部分拆分)
*/
public static List<Entry> splitEntryRandomPartial(Entry original, List<MachineOption> options,
int startId, Random rnd) {
List<MachineOption> selected = new ArrayList<>();
for (MachineOption mo : options) {
if (rnd.nextBoolean()) {
selected.add(mo);
}
}
if (selected.size() < 2) {
selected = options;
}
return doSplit(original, selected, startId, null, null, rnd);
}
private static List<Entry> doSplit(Entry original, List<MachineOption> options,
int startId, Map<Long, Double> loadMap,
Map<Long, Double> percentMap, Random rnd) {
List<Entry> subOps = new ArrayList<>();
int n = options.size();
String mainId = UUID.randomUUID().toString().replace("-", "");
double[] weights = new double[n];
double totalWeight = 0;
for (int i = 0; i < n; i++) {
MachineOption mo = options.get(i);
if (rnd != null) {
weights[i] = rnd.nextDouble() + 0.5;
} else if (loadMap != null) {
double load = loadMap.getOrDefault(mo.getMachineId(), 0.0);
weights[i] = 1.0 / Math.max(load + mo.getProcessingTime(), 0.001);
} else {
weights[i] = 1.0 / Math.max(mo.getProcessingTime(), 0.001);
}
totalWeight += weights[i];
}
double remainingQty = original.getQuantity();
int id = startId;
for (int i = 0; i < n; i++) {
MachineOption mo = options.get(i);
Entry sub = ProductionDeepCopyUtil.deepCopy(original, Entry.class);
sub.setId(id);
sub.setSplitSourceId(original.getId());
sub.setMainId(mainId);
sub.setSelectMachineID(mo.getMachineId());
sub.setMachineOptions(Collections.singletonList(mo));
if (i == 0) {
sub.setState(1);
sub.setNewCreate(false);
} else {
sub.setState(2);
sub.setNewCreate(true);
sub.setExecId(UUID.randomUUID().toString().replace("-", ""));
}
double proportion = (i == n - 1) ? 1.0 : weights[i] / totalWeight;
double splitQty = Math.max(1, Math.round(original.getQuantity() * proportion * 100.0) / 100.0);
if (i == n - 1) {
splitQty = remainingQty;
}
splitQty = Math.min(splitQty, remainingQty);
remainingQty -= splitQty;
sub.setQuantity(splitQty);
fixDependencyReferences(sub.getPrevEntryIds(), original.getId(), id, true);
fixDependencyReferences(sub.getNextEntryIds(), original.getId(), id, false);
subOps.add(sub);
id++;
}
return subOps;
}
/**
* 对启发式初始化用的工序列表进行随机拆分(直接修改列表,返回拆分后的新列表)
* 拆分后每个 group 内重新编号 sequence
*/
public static List<Entry> splitOpsForHeuristic(List<Entry> ops, Random rnd) {
Map<Integer, List<Entry>> groupMap = new LinkedHashMap<>();
for (Entry op : ops) {
groupMap.computeIfAbsent(op.getGroupId(), k -> new ArrayList<>()).add(op);
}
int maxId = ops.stream().mapToInt(Entry::getId).max().orElse(0);
int nextId = maxId + 1;
List<Entry> result = new ArrayList<>();
for (Map.Entry<Integer, List<Entry>> groupEntry : groupMap.entrySet()) {
List<Entry> expanded = new ArrayList<>();
for (Entry op : groupEntry.getValue()) {
List<MachineOption> options = op.getMachineOptions();
if (op.getSplitSourceId() == null && options != null && options.size() > 1) {
List<Entry> subOps = splitEntryRandomPartial(op, options, nextId, rnd);
nextId += subOps.size();
expanded.addAll(subOps);
} else {
expanded.add(op);
}
}
for (int i = 0; i < expanded.size(); i++) {
expanded.get(i).setSequence(i + 1);
}
result.addAll(expanded);
}
return result;
}
private static Map<Long, Double> calcMachineTotalLoad(List<Entry> allOperations) {
Map<Long, Double> load = new LinkedHashMap<>();
for (Entry op : allOperations) {
if (op.getMachineOptions() != null) {
for (MachineOption mo : op.getMachineOptions()) {
load.put(mo.getMachineId(),
load.getOrDefault(mo.getMachineId(), 0.0)
+ mo.getProcessingTime() * op.getQuantity());
}
}
}
return load;
}
private static void fixDependencyReferences(List<OperationDependency> deps,
int originalId, int newId, boolean isPrev) {
if (deps == null) return;
for (OperationDependency dep : deps) {
if (isPrev) {
if (dep.getNextOperationId() == originalId) {
dep.setNextOperationId(newId);
}
} else {
if (dep.getPrevOperationId() == originalId) {
dep.setPrevOperationId(newId);
}
}
}
}
private static void updateAllDependencies(List<Entry> allOps,
Map<Integer, List<Integer>> splitMapping) {
if (splitMapping.isEmpty()) return;
for (Entry op : allOps) {
if (op.getPrevEntryIds() != null && !op.getPrevEntryIds().isEmpty()) {
List<OperationDependency> expanded = new ArrayList<>();
for (OperationDependency dep : op.getPrevEntryIds()) {
List<Integer> splitIds = splitMapping.get(dep.getPrevOperationId());
if (splitIds != null) {
for (int splitId : splitIds) {
OperationDependency newDep = new OperationDependency();
newDep.setPrevOperationId(splitId);
newDep.setNextOperationId(dep.getNextOperationId());
newDep.setDependencyType(dep.getDependencyType());
expanded.add(newDep);
}
} else {
expanded.add(dep);
}
}
op.setPrevEntryIds(expanded);
}
if (op.getNextEntryIds() != null && !op.getNextEntryIds().isEmpty()) {
List<OperationDependency> expanded = new ArrayList<>();
for (OperationDependency dep : op.getNextEntryIds()) {
List<Integer> splitIds = splitMapping.get(dep.getNextOperationId());
if (splitIds != null) {
for (int splitId : splitIds) {
OperationDependency newDep = new OperationDependency();
newDep.setPrevOperationId(dep.getPrevOperationId());
newDep.setNextOperationId(splitId);
newDep.setDependencyType(dep.getDependencyType());
expanded.add(newDep);
}
} else {
expanded.add(dep);
}
}
op.setNextEntryIds(expanded);
}
}
FileHelper.writeLogFile(String.format(
"[工序拆分] 更新了%d个拆分的依赖引用", splitMapping.size()));
}
}
\ No newline at end of file
...@@ -409,6 +409,7 @@ if(entry.getMachineOptions()!=null) ...@@ -409,6 +409,7 @@ if(entry.getMachineOptions()!=null)
if (machine.getCapacityTypeName() == null) { if (machine.getCapacityTypeName() == null) {
machine.setCapacityTypeName(equipinfo.getCapacityTypeName()); machine.setCapacityTypeName(equipinfo.getCapacityTypeName());
} }
}else { }else {
machine.setCode(PlanResource.getReferenceCode()); machine.setCode(PlanResource.getReferenceCode());
machine.setName(PlanResource.getTitle()); machine.setName(PlanResource.getTitle());
......
...@@ -177,7 +177,7 @@ public class PlanResultService { ...@@ -177,7 +177,7 @@ public class PlanResultService {
* 后续会按场景创建人自动回退到可用的策略配置。 * 后续会按场景创建人自动回退到可用的策略配置。
*/ */
public Chromosome execute2(String SceneId) { public Chromosome execute2(String SceneId) {
return execute2(SceneId, null, null, null); return execute2(SceneId, 2361l, 241l, null);
} }
/** /**
...@@ -188,7 +188,7 @@ public class PlanResultService { ...@@ -188,7 +188,7 @@ public class PlanResultService {
try { try {
ScheduleParams param = InitScheduleParams(); ScheduleParams param = InitScheduleParams();
// param.setBaseTime(LocalDateTime.of(2026, 1, 1, 0, 0, 0));
this.baseTime=param.getBaseTime(); this.baseTime=param.getBaseTime();
// 策略读取入口:优先使用前端传入的 userId;没传时用 sceneId 查场景创建人。 // 策略读取入口:优先使用前端传入的 userId;没传时用 sceneId 查场景创建人。
Long effectiveUserId = scheduleStrategyService.resolveScheduleUserId(SceneId, userId); Long effectiveUserId = scheduleStrategyService.resolveScheduleUserId(SceneId, userId);
...@@ -2246,7 +2246,7 @@ if(job.getGeneDetails()!=null) ...@@ -2246,7 +2246,7 @@ if(job.getGeneDetails()!=null)
ApsTimeConfig apsTimeConfig= apsTimeConfigMapper.selectOne( queryWrapper); ApsTimeConfig apsTimeConfig= apsTimeConfigMapper.selectOne( queryWrapper);
ScheduleParams param = new ScheduleParams(); ScheduleParams param = new ScheduleParams();
param.setBaseTime(LocalDateTime.of(2025, 11, 1, 0, 0, 0)); param.setBaseTime(LocalDateTime.of(2026, 1, 1, 0, 0, 0));
if(apsTimeConfig!=null) { if(apsTimeConfig!=null) {
if(apsTimeConfig.getBaseTime()!=null) if(apsTimeConfig.getBaseTime()!=null)
{ {
......
...@@ -42,7 +42,7 @@ public class PlanResultServiceTest { ...@@ -42,7 +42,7 @@ public class PlanResultServiceTest {
// nsgaiiUtils.Test(); // nsgaiiUtils.Test();
// planResultService.execute2("64E64F6B68094AF38CEDC418630C3CC2");//2000 // planResultService.execute2("64E64F6B68094AF38CEDC418630C3CC2");//2000
planResultService.execute2("72744D094BAB45948F5172E84C78B260");//2000 planResultService.execute2("E1448B3C9C8743DEAB39708F2CFE348A");//2000
// planResultService.execute2("15210B13B88A453F8B84AAC7F16C7541");//2000 // 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