APS开源源码解读: 排程工具 optaplanner

鼠扑  论坛元老 | 2025-4-18 01:20:02 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 2013|帖子 2013|积分 6039

抽象条理非常好,广义优化工具。用于排产没有复杂的落地示例
move 真实的规划变量 -> trigger shadowvariable更新 -> StartTimeListener
  

  • https://github.com/apache/incubator-kie-optaplanner/blob/main/optaplanner-examples/src/main/java/org/optaplanner/examples/projectjobscheduling/app/ProjectJobSchedulingApp.java
  • https://github.com/eugenp/tutorials/tree/master/timefold-solver
  • https://github.com/kisszhu/aps
安装



  • java
  • maven
设置




  • xml设置
  • solutionClass
  • entityClass
  • constraintProviderClass

    • 约束设置

  • termination: 5min
  • constructionHeuristic: FIRST_FIT

    • first fit

  • localSearch:
也即是说,先定义对象“entityClass”, 转化为约束“constraintProviderClass”,然后运用 constructionHeuristic + localSearch的方式进行求解
其中,一个团体的使命叫做project, 资源有可再生,非可再生。
工序叫做Job,job跟着若干project。每个工序有自己的资源,ResourceRequirement. 执行模式Execution Mode. 分配allocation.


  • resourceRequirement和allocation都要设置execution mode
  • 每个工序JOb, 有自己的resourceRequirement, executation mode, allocation

   最好先跑一个实例中的quick-start: https://docs.timefold.ai/timefold-solver/latest/quickstart/hello-world/hello-world-quickstart
  基本概念

   

  • 最简单的宏观视角来说,构建一个solver = solverFactory.buildSolver(),构建一个题目建模solution=new PlanningSolution(machines, tasks),solver.solve(solution)即可.
  • 其中PlanningEntity, 约束位于solution之中; Planning Variable (可设置变化范围)位于PlanningEntity中,由于一些联动的关系,可以设置影子变量ShadowVariable,一个entity的变量改变了,其影子变量也跟着改变,例如下一道工序的开始时间;如果是双向的,及两个变量恣意个发生变化,另一个都跟着变化,则设置为InverseRelationShadowVariable
    计划相干:https://docs.timefold.ai/timefold-solver/latest/responding-to-change/responding-to-change#continuousPlanning
  

  • PlanningSolution

    • 定义Problem,以及解

  • planning entity

    • Allocation

  • planing variable

    • executionMode
    • delay

  • shadow variable

    • predecessorsDoneDate
    • https://www.optaplanner.org/docs/optaplanner/latest/shadow-variable/shadow-variable.html
    • ShadowVariable和VariableListener之间的关系紧密相干

  • planning score
  • 核心的优化算法
其他使命的domain 模子对怎样建模比力重要

Java基本概念



  • 反射就是Java可以给我们在运行时获取类的信息,例如在类上加上@Component注解,Spring就帮你创建对象

建模

建模非常关键,也就是想清晰一个计划或分配题目,真正分配的是什么,或者计划过程中,真正变化的什么。很多只是变化引起的
优化过程中,交换的是什么?
课程表

   安排课程,也就是将时间和教室分配给课程
  拿quick-start中的课程表为例,分配的资源是教室和时间,其中的约束为


  • A room can have at most one lesson at the same time.
  • A teacher can teach at most one lesson at the same time.
  • A student can attend at most one lesson at the same time.
  • A teacher prefers to teach all lessons in the same room.
  • A teacher prefers to teach sequential lessons and dislikes gaps between lessons.
  • A student dislikes sequential lessons on the same subject.



  • 其中的planningvariable应该是多对一的,多个planning variable对应到一个其他entity,而不是一个planning variable对应到多个entity。使用到该planning variable的其他entity,是1,可以直接对应到多个planning entity中的planning variable。
车辆规划

   每一辆车分给一个使命队列,依次去这些地方


  排产-Project Job Scheduling

   排产也就是把使命分配给资源和时间.
  官方实例中分配资源也就是选择execution mode,目标是淘汰project delay. 其中真正分配和变化的是execution mode. 也就是选择差别的资源
排产-TaskAssigning

约束

比如排产中的工序依赖关系
  1. import org.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore;
  2. import org.timefold.solver.core.api.score.stream.ConstraintProvider;
  3. import org.timefold.solver.core.api.score.stream.Constraint;
  4. import org.timefold.solver.core.api.score.stream.ConstraintStream;
  5. import org.timefold.solver.core.api.score.stream.Joiners;
  6. public class JobShopConstraintProvider implements ConstraintProvider {
  7.     @Override
  8.     public Constraint[] defineConstraints(ConstraintFactory constraintFactory) {
  9.         return new Constraint[] {
  10.             // Ensure operations follow the sequence within each job
  11.             constraintFactory.from(Operation.class)
  12.                 .join(Operation.class, Joiners.filteringEach(otherOp ->
  13.                     otherOp.getJob().equals(op.getJob()) &&
  14.                     otherOp.getSequence() == op.getSequence() + 1
  15.                 ))
  16.                 .penalize("Operations must follow sequence",
  17.                     HardSoftScore.ONE_HARD,
  18.                     (op, otherOp) -> 1),
  19.             
  20.             // Ensure machine constraints are respected
  21.             constraintFactory.from(Operation.class)
  22.                 .join(Operation.class, Joiners.filteringEach((op1, op2) ->
  23.                     op1.getMachine().equals(op2.getMachine()) &&
  24.                     op1.getEndTime() > op2.getStartTime() &&
  25.                     op1.getStartTime() < op2.getEndTime() &&
  26.                     !op1.equals(op2))
  27.                 )
  28.                 .penalize("Machine cannot process two operations at once",
  29.                     HardSoftScore.ONE_HARD,
  30.                     (op1, op2) -> 1)
  31.         };
  32.     }
  33. }
复制代码
官方示例

入口在APP的main
  1. public static void main(String[] args) {
  2.         prepareSwingEnvironment();
  3.         new ProjectJobSchedulingApp().init();
  4.     }
复制代码
init
  1. public void init() {
  2.         init(null, true);
  3.     }
  4.     public void init(Component centerForComponent, boolean exitOnClose) {
  5.         solutionBusiness = createSolutionBusiness();
  6.         solverAndPersistenceFrame = new SolverAndPersistenceFrame<>(solutionBusiness, createSolutionPanel(),
  7.                 createExtraActions());
  8.         solverAndPersistenceFrame
  9.                 .setDefaultCloseOperation(exitOnClose ? WindowConstants.EXIT_ON_CLOSE : WindowConstants.DISPOSE_ON_CLOSE);
  10.         solverAndPersistenceFrame.init(centerForComponent);
  11.         solverAndPersistenceFrame.setVisible(true);
  12.     }
复制代码
其中,solution business
- SolverFactory.createFromXmlResource建立了solver
  1. public SolutionBusiness<Solution_, ?> createSolutionBusiness() {
  2.         SolutionBusiness<Solution_, ?> solutionBusiness = new SolutionBusiness<>(this,
  3.                 SolverFactory.createFromXmlResource(solverConfigResource));
  4.         solutionBusiness.setDataDir(determineDataDir(dataDirName));
  5.         solutionBusiness.setSolutionFileIO(createSolutionFileIO());
  6.         solutionBusiness.setImporters(createSolutionImporters());
  7.         solutionBusiness.setExporters(createSolutionExporters());
  8.         solutionBusiness.updateDataDirs();
  9.         return solutionBusiness;
  10.     }
复制代码
在APP类继承的solution中,示例采用的是schedule,也就是planningsolution,作为题目和排产效果
  1. package org.optaplanner.examples.projectjobscheduling.domain;
  2. import java.util.List;
  3. import org.optaplanner.core.api.domain.solution.PlanningEntityCollectionProperty;
  4. import org.optaplanner.core.api.domain.solution.PlanningScore;
  5. import org.optaplanner.core.api.domain.solution.PlanningSolution;
  6. import org.optaplanner.core.api.domain.solution.ProblemFactCollectionProperty;
  7. import org.optaplanner.core.api.score.buildin.hardmediumsoft.HardMediumSoftScore;
  8. import org.optaplanner.examples.common.domain.AbstractPersistable;
  9. import org.optaplanner.examples.projectjobscheduling.domain.resource.Resource;
  10. @PlanningSolution
  11. public class Schedule extends AbstractPersistable {
  12.     private List<Project> projectList;
  13.     private List<Job> jobList;
  14.     private List<ExecutionMode> executionModeList;
  15.     private List<Resource> resourceList;
  16.     private List<ResourceRequirement> resourceRequirementList;
  17.     private List<Allocation> allocationList;
  18.     private HardMediumSoftScore score;
  19.     public Schedule() {
  20.     }
  21.     public Schedule(long id) {
  22.         super(id);
  23.     }
  24.     @ProblemFactCollectionProperty
  25.     public List<Project> getProjectList() {
  26.         return projectList;
  27.     }
  28.     public void setProjectList(List<Project> projectList) {
  29.         this.projectList = projectList;
  30.     }
  31.     @ProblemFactCollectionProperty
  32.     public List<Job> getJobList() {
  33.         return jobList;
  34.     }
  35.     public void setJobList(List<Job> jobList) {
  36.         this.jobList = jobList;
  37.     }
  38.     @ProblemFactCollectionProperty
  39.     public List<ExecutionMode> getExecutionModeList() {
  40.         return executionModeList;
  41.     }
  42.     public void setExecutionModeList(List<ExecutionMode> executionModeList) {
  43.         this.executionModeList = executionModeList;
  44.     }
  45.     @ProblemFactCollectionProperty
  46.     public List<Resource> getResourceList() {
  47.         return resourceList;
  48.     }
  49.     public void setResourceList(List<Resource> resourceList) {
  50.         this.resourceList = resourceList;
  51.     }
  52.     @ProblemFactCollectionProperty
  53.     public List<ResourceRequirement> getResourceRequirementList() {
  54.         return resourceRequirementList;
  55.     }
  56.     public void setResourceRequirementList(List<ResourceRequirement> resourceRequirementList) {
  57.         this.resourceRequirementList = resourceRequirementList;
  58.     }
  59.     @PlanningEntityCollectionProperty
  60.     public List<Allocation> getAllocationList() {
  61.         return allocationList;
  62.     }
  63.     public void setAllocationList(List<Allocation> allocationList) {
  64.         this.allocationList = allocationList;
  65.     }
  66.     @PlanningScore
  67.     public HardMediumSoftScore getScore() {
  68.         return score;
  69.     }
  70.     public void setScore(HardMediumSoftScore score) {
  71.         this.score = score;
  72.     }
  73.     // ************************************************************************
  74.     // Complex methods
  75.     // ************************************************************************
  76. }
复制代码
Timefold 示例

Solver job接受到problem,开始run
  1. @Deprecated(forRemoval = true, since = "1.6.0")
  2.     default SolverJob<Solution_, ProblemId_> solve(ProblemId_ problemId,
  3.             Solution_ problem, Consumer<? super Solution_> finalBestSolutionConsumer,
  4.             BiConsumer<? super ProblemId_, ? super Throwable> exceptionHandler) {
  5.         SolverJobBuilder<Solution_, ProblemId_> builder = solveBuilder()
  6.                 .withProblemId(problemId)
  7.                 .withProblem(problem);
  8.         if (finalBestSolutionConsumer != null) {
  9.             builder.withFinalBestSolutionConsumer(finalBestSolutionConsumer);
  10.         }
  11.         if (exceptionHandler != null) {
  12.             builder.withExceptionHandler(exceptionHandler);
  13.         }
  14.         return builder.run();
  15.     }
复制代码
  1. solverStatus = SolverStatus.SOLVING_ACTIVE;
  2.             // Create the consumer thread pool only when this solver job is active.
  3.             consumerSupport = new ConsumerSupport<>(getProblemId(), bestSolutionConsumer, finalBestSolutionConsumer,
  4.                     firstInitializedSolutionConsumer, exceptionHandler, bestSolutionHolder);
  5.             Solution_ problem = problemFinder.apply(problemId);
  6.             // add a phase lifecycle listener that unlock the solver status lock when solving started
  7.             solver.addPhaseLifecycleListener(new UnlockLockPhaseLifecycleListener());
  8.             // add a phase lifecycle listener that consumes the first initialized solution
  9.             solver.addPhaseLifecycleListener(new FirstInitializedSolutionPhaseLifecycleListener(consumerSupport));
  10.             solver.addEventListener(this::onBestSolutionChangedEvent);
  11.             final Solution_ finalBestSolution = solver.solve(problem);
  12.             consumerSupport.consumeFinalBestSolution(finalBestSolution);
  13.             return finalBestSolution;
复制代码
理解




  • https://www.optaplanner.org/docs/optaplanner/latest/shadow-variable/shadow-variable.html
  • build_solver/ default_solver_factory
  1.     public Solver<Solution_> buildSolver(SolverConfigOverride<Solution_> configOverride) {
  2.         Objects.requireNonNull(configOverride, "Invalid configOverride (null) given to SolverFactory.");
  3.         var isDaemon = Objects.requireNonNullElse(solverConfig.getDaemon(), false);
  4.         var solverScope = new SolverScope<Solution_>();
  5.         var monitoringConfig = solverConfig.determineMetricConfig();
  6.         solverScope.setMonitoringTags(Tags.empty());
  7.         var metricsRequiringConstraintMatchSet = Collections.<SolverMetric> emptyList();
  8.         if (!monitoringConfig.getSolverMetricList().isEmpty()) {
  9.             solverScope.setSolverMetricSet(EnumSet.copyOf(monitoringConfig.getSolverMetricList()));
  10.             metricsRequiringConstraintMatchSet = solverScope.getSolverMetricSet().stream()
  11.                     .filter(SolverMetric::isMetricConstraintMatchBased)
  12.                     .filter(solverScope::isMetricEnabled)
  13.                     .toList();
  14.         } else {
  15.             solverScope.setSolverMetricSet(EnumSet.noneOf(SolverMetric.class));
  16.         }
  17.         var environmentMode = solverConfig.determineEnvironmentMode();
  18.         var constraintMatchEnabled = !metricsRequiringConstraintMatchSet.isEmpty() || environmentMode.isAsserted();
  19.         if (constraintMatchEnabled && !environmentMode.isAsserted()) {
  20.             LOGGER.info(
  21.                     "Enabling constraint matching as required by the enabled metrics ({}). This will impact solver performance.",
  22.                     metricsRequiringConstraintMatchSet);
  23.         }
  24.         var innerScoreDirector = scoreDirectorFactory.buildScoreDirector(true, constraintMatchEnabled);
  25.         solverScope.setScoreDirector(innerScoreDirector);
  26.         solverScope.setProblemChangeDirector(new DefaultProblemChangeDirector<>(innerScoreDirector));
  27.         var moveThreadCount = resolveMoveThreadCount(true);
  28.         var bestSolutionRecaller = BestSolutionRecallerFactory.create().<Solution_> buildBestSolutionRecaller(environmentMode);
  29.         var randomFactory = buildRandomFactory(environmentMode);
  30.         var configPolicy = new HeuristicConfigPolicy.Builder<>(
  31.                 environmentMode,
  32.                 moveThreadCount,
  33.                 solverConfig.getMoveThreadBufferSize(),
  34.                 solverConfig.getThreadFactoryClass(),
  35.                 solverConfig.getNearbyDistanceMeterClass(),
  36.                 randomFactory.createRandom(),
  37.                 scoreDirectorFactory.getInitializingScoreTrend(),
  38.                 solutionDescriptor,
  39.                 ClassInstanceCache.create()).build();
  40.         var basicPlumbingTermination = new BasicPlumbingTermination<Solution_>(isDaemon);
  41.         var termination = buildTerminationConfig(basicPlumbingTermination, configPolicy, configOverride);
  42.         var phaseList = buildPhaseList(configPolicy, bestSolutionRecaller, termination);
  43.         return new DefaultSolver<>(environmentMode, randomFactory, bestSolutionRecaller, basicPlumbingTermination,
  44.                 termination, phaseList, solverScope,
  45.                 moveThreadCount == null ? SolverConfig.MOVE_THREAD_COUNT_NONE : Integer.toString(moveThreadCount));
  46.     }
复制代码
solver的主流程
  1. @Override
  2.     public final Solution_ solve(Solution_ problem) {
  3.         if (problem == null) {
  4.             throw new IllegalArgumentException("The problem (" + problem + ") must not be null.");
  5.         }
  6.         // No tags for these metrics; they are global
  7.         LongTaskTimer solveLengthTimer = Metrics.more().longTaskTimer(SolverMetric.SOLVE_DURATION.getMeterId());
  8.         Counter errorCounter = Metrics.counter(SolverMetric.ERROR_COUNT.getMeterId());
  9.         solverScope.setBestSolution(problem);
  10.         solverScope.setSolver(this);
  11.         outerSolvingStarted(solverScope);
  12.         boolean restartSolver = true;
  13.         while (restartSolver) {
  14.             LongTaskTimer.Sample sample = solveLengthTimer.start();
  15.             try {
  16.                 // solvingStarted will call registerSolverSpecificMetrics(), since
  17.                 // the solverScope need to be fully initialized to calculate the
  18.                 // problem's scale metrics
  19.                 solvingStarted(solverScope);
  20.                 runPhases(solverScope);
  21.                 solvingEnded(solverScope);
  22.             } catch (Exception e) {
  23.                 errorCounter.increment();
  24.                 solvingError(solverScope, e);
  25.                 throw e;
  26.             } finally {
  27.                 sample.stop();
  28.                 unregisterSolverSpecificMetrics();
  29.             }
  30.             restartSolver = checkProblemFactChanges();
  31.         }
  32.         outerSolvingEnded(solverScope);
  33.         return solverScope.getBestSolution();
  34.     }
复制代码


  • run_phase /abstract_solver
  1. protected void runPhases(SolverScope<Solution_> solverScope) {
  2.         if (!solverScope.getSolutionDescriptor().hasMovableEntities(solverScope.getScoreDirector())) {
  3.             logger.info("Skipped all phases ({}): out of {} planning entities, none are movable (non-pinned).",
  4.                     phaseList.size(), solverScope.getWorkingEntityCount());
  5.             return;
  6.         }
  7.         Iterator<Phase<Solution_>> it = phaseList.iterator();
  8.         while (!solverTermination.isSolverTerminated(solverScope) && it.hasNext()) {
  9.             Phase<Solution_> phase = it.next();
  10.             phase.solve(solverScope);
  11.             // If there is a next phase, it starts from the best solution, which might differ from the working solution.
  12.             // If there isn't, no need to planning clone the best solution to the working solution.
  13.             if (it.hasNext()) {
  14.                 solverScope.setWorkingSolutionFromBestSolution();
  15.             }
  16.         }
  17.     }
复制代码


  • solver表面的phase, PhaseFactory
  • dostep
    局部搜索在当前解上尝试多个移动,并选择最佳的被接受的移动作为这一步。A step is the winning Move。在每一步,它尝试全部选定的移动,除非是选定的step,否则它不会进一步研究谁人解。这就是局部搜索具有很高可扩展性的原因之一。
  1. private void doStep(CustomStepScope<Solution_> stepScope, CustomPhaseCommand<Solution_> customPhaseCommand) {
  2.         InnerScoreDirector<Solution_, ?> scoreDirector = stepScope.getScoreDirector();
  3.         customPhaseCommand.changeWorkingSolution(scoreDirector);
  4.         calculateWorkingStepScore(stepScope, customPhaseCommand);
  5.         solver.getBestSolutionRecaller().processWorkingSolutionDuringStep(stepScope);
  6.     }
复制代码


  • 决定下一步

    • A MoveSelector which selects the possible moves of the current solution. See the chapter move and neighborhood selection.
    • An Acceptor which filters out unacceptable moves.
    • A Forager which gathers accepted moves and picks the next step from them.

  1.   <localSearch>
  2.     <unionMoveSelector>
  3.       ...
  4.     </unionMoveSelector>
  5.     <acceptor>
  6.       ...
  7.     </acceptor>
  8.     <forager>
  9.       ...
  10.     </forager>
  11.   </localSearch>
复制代码

从底向上看,理解大概的move。如果是entity+value组合,或者是entity和entity进行新的组合。也许这就是叫做组合优化的原因?
chatgpt产生的一些简单代码

  1. // File: pom.xml
  2. <?xml version="1.0" encoding="UTF-8"?>
  3. <project xmlns="http://maven.apache.org/POM/4.0.0"
  4.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  5.          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  6.     <modelVersion>4.0.0</modelVersion>
  7.     <groupId>org.example</groupId>
  8.     <artifactId>jobshop-scheduler</artifactId>
  9.     <version>1.0-SNAPSHOT</version>
  10.     <properties>
  11.         <maven.compiler.source>17</maven.compiler.source>
  12.         <maven.compiler.target>17</maven.compiler.target>
  13.         <timefold.version>1.6.0</timefold.version>
  14.     </properties>
  15.     <dependencies>
  16.         <dependency>
  17.             <groupId>ai.timefold.solver</groupId>
  18.             <artifactId>timefold-solver-core</artifactId>
  19.             <version>${timefold.version}</version>
  20.         </dependency>
  21.         <dependency>
  22.             <groupId>org.projectlombok</groupId>
  23.             <artifactId>lombok</artifactId>
  24.             <version>1.18.30</version>
  25.             <scope>provided</scope>
  26.         </dependency>
  27.         <dependency>
  28.             <groupId>ch.qos.logback</groupId>
  29.             <artifactId>logback-classic</artifactId>
  30.             <version>1.4.11</version>
  31.         </dependency>
  32.     </dependencies>
  33. </project>
  34. // File: src/main/resources/logback.xml
  35. <?xml version="1.0" encoding="UTF-8"?>
  36. <configuration>
  37.     <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
  38.         <encoder>
  39.             <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
  40.         </encoder>
  41.     </appender>
  42.     <logger name="ai.timefold.solver" level="info"/>
  43.     <root level="warn">
  44.         <appender-ref ref="STDOUT"/>
  45.     </root>
  46. </configuration>
  47. // File: src/main/resources/solver-config.xml
  48. <?xml version="1.0" encoding="UTF-8"?>
  49. <solver xmlns="https://timefold.ai/xsd/solver" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  50.         xsi:schemaLocation="https://timefold.ai/xsd/solver https://timefold.ai/xsd/solver/solver.xsd">
  51.     <solutionClass>org.example.jobshop.domain.JobShopSchedule</solutionClass>
  52.     <entityClass>org.example.jobshop.domain.JobAllocation</entityClass>
  53.     <scoreDirectorFactory>
  54.         <constraintProviderClass>org.example.jobshop.solver.JobShopConstraintProvider</constraintProviderClass>
  55.     </scoreDirectorFactory>
  56.     <termination>
  57.         <minutesSpentLimit>5</minutesSpentLimit>
  58.     </termination>
  59.     <constructionHeuristic>
  60.         <constructionHeuristicType>FIRST_FIT_DECREASING</constructionHeuristicType>
  61.     </constructionHeuristic>
  62.     <localSearch>
  63.         <unionMoveSelector>
  64.             <changeMoveSelector/>
  65.             <swapMoveSelector/>
  66.             <moveListFactory>
  67.                 <moveListFactoryClass>org.example.jobshop.solver.JobAllocationMoveFactory</moveListFactoryClass>
  68.             </moveListFactory>
  69.         </unionMoveSelector>
  70.         <acceptor>
  71.             <lateAcceptanceSize>400</lateAcceptanceSize>
  72.         </acceptor>
  73.         <forager>
  74.             <acceptedCountLimit>4</acceptedCountLimit>
  75.         </forager>
  76.     </localSearch>
  77. </solver>
  78. // File: src/main/java/org/example/jobshop/domain/Project.java
  79. package org.example.jobshop.domain;
  80. @lombok.Data
  81. @lombok.NoArgsConstructor
  82. @lombok.AllArgsConstructor
  83. public class Project {
  84.     private String id;
  85.     private String name;
  86.     private LocalDateTime releaseDate;
  87.     private LocalDateTime dueDate;
  88.     private List<Job> jobs;
  89. }
  90. // File: src/main/java/org/example/jobshop/domain/Job.java
  91. package org.example.jobshop.domain;
  92. @lombok.Data
  93. @lombok.NoArgsConstructor
  94. @lombok.AllArgsConstructor
  95. public class Job {
  96.     private String id;
  97.     private String name;
  98.     private Project project;
  99.     private Duration processingTime;
  100.     private List<Resource> compatibleResources;
  101.     private Job previousJob;
  102.     private Job nextJob;
  103. }
  104. // File: src/main/java/org/example/jobshop/domain/Resource.java
  105. package org.example.jobshop.domain;
  106. @lombok.Data
  107. @lombok.NoArgsConstructor
  108. @lombok.AllArgsConstructor
  109. public class Resource {
  110.     private String id;
  111.     private String name;
  112.     private ResourceType type;
  113.     private LocalDateTime availableFrom;
  114.     private double costPerHour;
  115. }
  116. // File: src/main/java/org/example/jobshop/domain/JobAllocation.java
  117. package org.example.jobshop.domain;
  118. @PlanningEntity
  119. @lombok.Data
  120. @lombok.NoArgsConstructor
  121. @lombok.AllArgsConstructor
  122. public class JobAllocation {
  123.     private String id;
  124.     private Job job;
  125.    
  126.     @PlanningVariable(valueRangeProvider = "resourceRange")
  127.     private Resource resource;
  128.    
  129.     @PlanningVariable(valueRangeProvider = "allocationRange")
  130.     private JobAllocation previousAllocation;
  131.    
  132.     @CustomShadowVariable(
  133.         variableListenerClass = StartTimeUpdatingVariableListener.class,
  134.         sources = {@PlanningVariableReference(variableName = "previousAllocation"),
  135.                   @PlanningVariableReference(variableName = "resource")})
  136.     private LocalDateTime startTime;
  137.    
  138.     private LocalDateTime endTime;
  139.    
  140.     // ... other methods from previous versions
  141. }
  142. // File: src/main/java/org/example/jobshop/solver/JobShopSolver.java
  143. package org.example.jobshop.solver;
  144. import ai.timefold.solver.core.api.solver.SolverFactory;
  145. import ai.timefold.solver.core.config.solver.SolverConfig;
  146. public class JobShopSolver {
  147.     private final SolverFactory<JobShopSchedule> solverFactory;
  148.     public JobShopSolver() {
  149.         SolverConfig solverConfig = SolverConfig.createFromXmlResource(
  150.                 "solver-config.xml");
  151.         this.solverFactory = SolverFactory.create(solverConfig);
  152.     }
  153.     public JobShopSchedule solve(JobShopSchedule problem) {
  154.         return solverFactory.buildSolver().solve(problem);
  155.     }
  156. }
  157. // File: src/main/java/org/example/jobshop/Main.java
  158. package org.example.jobshop;
  159. public class Main {
  160.     public static void main(String[] args) {
  161.         // Create sample problem
  162.         JobShopSchedule problem = createSampleProblem();
  163.         
  164.         // Create and run solver
  165.         JobShopSolver solver = new JobShopSolver();
  166.         JobShopSchedule solution = solver.solve(problem);
  167.         
  168.         // Print solution
  169.         printSolution(solution);
  170.     }
  171.     private static JobShopSchedule createSampleProblem() {
  172.         // Create resources
  173.         List<Resource> resources = new ArrayList<>();
  174.         resources.add(new Resource("R1", "Machine 1", ResourceType.MACHINE,
  175.             LocalDateTime.now(), 100.0));
  176.         resources.add(new Resource("R2", "Machine 2", ResourceType.MACHINE,
  177.             LocalDateTime.now(), 150.0));
  178.         
  179.         // Create projects and jobs
  180.         List<Project> projects = new ArrayList<>();
  181.         List<JobAllocation> allocations = new ArrayList<>();
  182.         
  183.         Project project1 = new Project("P1", "Project 1",
  184.             LocalDateTime.now(), LocalDateTime.now().plusDays(5), new ArrayList<>());
  185.         
  186.         // Create jobs for project 1
  187.         Job job1 = new Job("J1", "Job 1", project1, Duration.ofHours(4),
  188.             resources, null, null);
  189.         Job job2 = new Job("J2", "Job 2", project1, Duration.ofHours(3),
  190.             resources, job1, null);
  191.         job1.setNextJob(job2);
  192.         
  193.         project1.setJobs(Arrays.asList(job1, job2));
  194.         projects.add(project1);
  195.         
  196.         // Create allocations
  197.         allocations.add(new JobAllocation("A1", job1, null, null, null, null));
  198.         allocations.add(new JobAllocation("A2", job2, null, null, null, null));
  199.         
  200.         return new JobShopSchedule(projects, allocations, resources);
  201.     }
  202.     private static void printSolution(JobShopSchedule solution) {
  203.         System.out.println("\nSolution found:");
  204.         
  205.         // Print by resource
  206.         for (Resource resource : solution.getResources()) {
  207.             System.out.println("\nResource: " + resource.getName());
  208.             
  209.             // Find all allocations for this resource and sort by start time
  210.             List<JobAllocation> resourceAllocations = solution.getAllocations().stream()
  211.                 .filter(a -> a.getResource() == resource)
  212.                 .sorted(Comparator.comparing(JobAllocation::getStartTime))
  213.                 .collect(Collectors.toList());
  214.             
  215.             for (JobAllocation allocation : resourceAllocations) {
  216.                 System.out.printf("  %s: %s -> %s (%s)\n",
  217.                     allocation.getJob().getName(),
  218.                     allocation.getStartTime().format(DateTimeFormatter.ISO_LOCAL_TIME),
  219.                     allocation.getEndTime().format(DateTimeFormatter.ISO_LOCAL_TIME),
  220.                     allocation.getJob().getProject().getName());
  221.             }
  222.         }
  223.         
  224.         // Print by project
  225.         for (Project project : solution.getProjects()) {
  226.             System.out.println("\nProject: " + project.getName());
  227.             
  228.             List<JobAllocation> projectAllocations = solution.getAllocations().stream()
  229.                 .filter(a -> a.getJob().getProject() == project)
  230.                 .sorted(Comparator.comparing(JobAllocation::getStartTime))
  231.                 .collect(Collectors.toList());
  232.             
  233.             for (JobAllocation allocation : projectAllocations) {
  234.                 System.out.printf("  %s: %s on %s\n",
  235.                     allocation.getJob().getName(),
  236.                     allocation.getStartTime().format(DateTimeFormatter.ISO_LOCAL_TIME),
  237.                     allocation.getResource().getName());
  238.             }
  239.         }
  240.     }
  241. }
复制代码
  1. // Domain classes
  2. @PlanningEntity
  3. public class Allocation {
  4.     private Job job;
  5.    
  6.     @PlanningVariable(valueRangeProviderRefs = "resourceRange")
  7.     private Resource resource;
  8.    
  9.     // This is the anchor planning variable
  10.     @PlanningVariable(valueRangeProviderRefs = "allocationRange")
  11.     private Allocation previousAllocation;
  12.    
  13.     // Shadow variables that get updated automatically
  14.     @CustomShadowVariable(
  15.         variableListenerClass = StartTimeUpdatingVariableListener.class,
  16.         sources = {@PlanningVariableReference(variableName = "previousAllocation"),
  17.                   @PlanningVariableReference(variableName = "resource")})
  18.     private Integer startTime;
  19.    
  20.     @CustomShadowVariable(
  21.         variableListenerClass = EndTimeUpdatingVariableListener.class,
  22.         sources = {@PlanningVariableReference(variableName = "startTime")})
  23.     private Integer endTime;
  24. }
  25. // Move implementation for changing resources
  26. public class ResourceChangeMove extends AbstractMove<JobShopSchedule> {
  27.     private final Allocation allocation;
  28.     private final Resource toResource;
  29.    
  30.     @Override
  31.     protected void doMoveOnGenuineVariables(ScoreDirector<JobShopSchedule> scoreDirector) {
  32.         // Step 1: Change the resource
  33.         scoreDirector.beforeVariableChanged(allocation, "resource");
  34.         allocation.setResource(toResource);
  35.         scoreDirector.afterVariableChanged(allocation, "resource");
  36.         
  37.         // Step 2: Update the chain
  38.         // The allocation becomes the last in the new resource's chain
  39.         Allocation lastInResource = findLastAllocationInResource(toResource);
  40.         
  41.         scoreDirector.beforeVariableChanged(allocation, "previousAllocation");
  42.         allocation.setPreviousAllocation(lastInResource);
  43.         scoreDirector.afterVariableChanged(allocation, "previousAllocation");
  44.     }
  45. }
  46. // Move implementation for sequence changes
  47. public class SequenceChangeMove extends AbstractMove<JobShopSchedule> {
  48.     private final Allocation allocation;
  49.     private final Allocation beforeAllocation;
  50.    
  51.     @Override
  52.     protected void doMoveOnGenuineVariables(ScoreDirector<JobShopSchedule> scoreDirector) {
  53.         // Step 1: Update the chain links
  54.         Allocation oldNextAllocation = allocation.getNextAllocation();
  55.         Allocation oldPreviousAllocation = allocation.getPreviousAllocation();
  56.         
  57.         // Connect old previous and next allocations
  58.         if (oldNextAllocation != null) {
  59.             scoreDirector.beforeVariableChanged(oldNextAllocation, "previousAllocation");
  60.             oldNextAllocation.setPreviousAllocation(oldPreviousAllocation);
  61.             scoreDirector.afterVariableChanged(oldNextAllocation, "previousAllocation");
  62.         }
  63.         
  64.         // Step 2: Insert allocation in new position
  65.         Allocation newNextAllocation = beforeAllocation.getNextAllocation();
  66.         
  67.         scoreDirector.beforeVariableChanged(allocation, "previousAllocation");
  68.         allocation.setPreviousAllocation(beforeAllocation);
  69.         scoreDirector.afterVariableChanged(allocation, "previousAllocation");
  70.         
  71.         if (newNextAllocation != null) {
  72.             scoreDirector.beforeVariableChanged(newNextAllocation, "previousAllocation");
  73.             newNextAllocation.setPreviousAllocation(allocation);
  74.             scoreDirector.afterVariableChanged(newNextAllocation, "previousAllocation");
  75.         }
  76.     }
  77. }
  78. // Variable Listener for updating start times
  79. public class StartTimeUpdatingVariableListener
  80.     implements VariableListener<JobShopSchedule, Allocation> {
  81.    
  82.     @Override
  83.     public void afterEntityAdded(ScoreDirector<JobShopSchedule> scoreDirector,
  84.                                Allocation allocation) {
  85.         updateStartTime(scoreDirector, allocation);
  86.     }
  87.    
  88.     @Override
  89.     public void afterVariableChanged(ScoreDirector<JobShopSchedule> scoreDirector,
  90.                                    Allocation allocation) {
  91.         updateStartTime(scoreDirector, allocation);
  92.     }
  93.    
  94.     private void updateStartTime(ScoreDirector<JobShopSchedule> scoreDirector,
  95.                                Allocation allocation) {
  96.         Allocation previousAllocation = allocation.getPreviousAllocation();
  97.         Integer newStartTime;
  98.         
  99.         if (previousAllocation == null) {
  100.             // First in resource - can start at 0
  101.             newStartTime = 0;
  102.         } else {
  103.             // Must wait for previous allocation to finish
  104.             newStartTime = previousAllocation.getEndTime();
  105.         }
  106.         
  107.         // Consider job dependencies (if this job must wait for other jobs)
  108.         for (Job prerequisite : allocation.getJob().getPrerequisites()) {
  109.             Allocation prerequisiteAllocation = findAllocationForJob(prerequisite);
  110.             if (prerequisiteAllocation != null) {
  111.                 newStartTime = Math.max(newStartTime,
  112.                                       prerequisiteAllocation.getEndTime());
  113.             }
  114.         }
  115.         
  116.         scoreDirector.beforeVariableChanged(allocation, "startTime");
  117.         allocation.setStartTime(newStartTime);
  118.         scoreDirector.afterVariableChanged(allocation, "startTime");
  119.     }
  120. }
复制代码
  1. package org.example.jobshop.solver;
  2. import ai.timefold.solver.core.api.score.director.ScoreDirector;
  3. import ai.timefold.solver.core.impl.phase.custom.CustomPhaseCommand;
  4. import java.util.*;
  5. public class JobShopInitializer implements CustomPhaseCommand<JobShopSchedule> {
  6.     @Override
  7.     public void changeWorkingSolution(ScoreDirector<JobShopSchedule> scoreDirector) {
  8.         JobShopSchedule schedule = scoreDirector.getWorkingSolution();
  9.         
  10.         // Step 1: Sort jobs by priority and dependencies
  11.         List<JobAllocation> sortedAllocations = prioritizeAllocations(schedule);
  12.         
  13.         // Step 2: Initialize all allocations to unassigned
  14.         for (JobAllocation allocation : schedule.getAllocations()) {
  15.             scoreDirector.beforeVariableChanged(allocation, "resource");
  16.             allocation.setResource(null);
  17.             scoreDirector.afterVariableChanged(allocation, "resource");
  18.             
  19.             scoreDirector.beforeVariableChanged(allocation, "previousAllocation");
  20.             allocation.setPreviousAllocation(null);
  21.             scoreDirector.afterVariableChanged(allocation, "previousAllocation");
  22.         }
  23.         // Step 3: Assign jobs considering both resource and sequence constraints
  24.         Map<Resource, JobAllocation> lastAllocationByResource = new HashMap<>();
  25.         Map<Job, LocalDateTime> jobEndTimes = new HashMap<>();
  26.         for (JobAllocation allocation : sortedAllocations) {
  27.             // Find best resource and position
  28.             ResourceAssignment bestAssignment = findBestAssignment(
  29.                 allocation,
  30.                 schedule.getResources(),
  31.                 lastAllocationByResource,
  32.                 jobEndTimes,
  33.                 schedule);
  34.             // Apply the assignment
  35.             if (bestAssignment != null) {
  36.                 // Assign resource
  37.                 scoreDirector.beforeVariableChanged(allocation, "resource");
  38.                 allocation.setResource(bestAssignment.resource);
  39.                 scoreDirector.afterVariableChanged(allocation, "resource");
  40.                 // Assign previous allocation
  41.                 scoreDirector.beforeVariableChanged(allocation, "previousAllocation");
  42.                 allocation.setPreviousAllocation(bestAssignment.previousAllocation);
  43.                 scoreDirector.afterVariableChanged(allocation, "previousAllocation");
  44.                 // Update tracking maps
  45.                 lastAllocationByResource.put(bestAssignment.resource, allocation);
  46.                 jobEndTimes.put(allocation.getJob(), bestAssignment.endTime);
  47.             }
  48.         }
  49.     }
  50.     private List<JobAllocation> prioritizeAllocations(JobShopSchedule schedule) {
  51.         List<JobAllocation> sortedAllocations = new ArrayList<>(schedule.getAllocations());
  52.         
  53.         // Create job dependency graph
  54.         Map<Job, Set<Job>> dependencies = new HashMap<>();
  55.         Map<Job, Integer> inDegree = new HashMap<>();
  56.         
  57.         for (Project project : schedule.getProjects()) {
  58.             for (Job job : project.getJobs()) {
  59.                 dependencies.putIfAbsent(job, new HashSet<>());
  60.                 inDegree.putIfAbsent(job, 0);
  61.                
  62.                 if (job.getNextJob() != null) {
  63.                     dependencies.get(job).add(job.getNextJob());
  64.                     inDegree.merge(job.getNextJob(), 1, Integer::sum);
  65.                 }
  66.             }
  67.         }
  68.         // Topological sort with additional priority factors
  69.         sortedAllocations.sort((a1, a2) -> {
  70.             Job job1 = a1.getJob();
  71.             Job job2 = a2.getJob();
  72.             
  73.             // First priority: dependency order
  74.             int dep1 = inDegree.getOrDefault(job1, 0);
  75.             int dep2 = inDegree.getOrDefault(job2, 0);
  76.             if (dep1 != dep2) return dep1 - dep2;
  77.             
  78.             // Second priority: project due date
  79.             int dueDate = job1.getProject().getDueDate()
  80.                 .compareTo(job2.getProject().getDueDate());
  81.             if (dueDate != 0) return dueDate;
  82.             
  83.             // Third priority: processing time (longer first)
  84.             return job2.getProcessingTime().compareTo(job1.getProcessingTime());
  85.         });
  86.         
  87.         return sortedAllocations;
  88.     }
  89.     @lombok.Data
  90.     @lombok.AllArgsConstructor
  91.     private static class ResourceAssignment {
  92.         private Resource resource;
  93.         private JobAllocation previousAllocation;
  94.         private LocalDateTime startTime;
  95.         private LocalDateTime endTime;
  96.         private double score;
  97.     }
  98.     private ResourceAssignment findBestAssignment(
  99.             JobAllocation allocation,
  100.             List<Resource> resources,
  101.             Map<Resource, JobAllocation> lastAllocationByResource,
  102.             Map<Job, LocalDateTime> jobEndTimes,
  103.             JobShopSchedule schedule) {
  104.         
  105.         ResourceAssignment bestAssignment = null;
  106.         double bestScore = Double.NEGATIVE_INFINITY;
  107.         Job job = allocation.getJob();
  108.         
  109.         // Get earliest start time based on job dependencies
  110.         LocalDateTime earliestStart = job.getPreviousJob() != null ?
  111.             jobEndTimes.getOrDefault(job.getPreviousJob(), LocalDateTime.MIN) :
  112.             LocalDateTime.now();
  113.         // Try each compatible resource
  114.         for (Resource resource : job.getCompatibleResources()) {
  115.             JobAllocation lastAllocation = lastAllocationByResource.get(resource);
  116.             
  117.             // Calculate possible start time
  118.             LocalDateTime startTime = calculateStartTime(
  119.                 earliestStart,
  120.                 lastAllocation,
  121.                 resource);
  122.             
  123.             LocalDateTime endTime = startTime.plus(job.getProcessingTime());
  124.             
  125.             // Calculate assignment score
  126.             double score = calculateAssignmentScore(
  127.                 resource,
  128.                 startTime,
  129.                 endTime,
  130.                 job,
  131.                 schedule);
  132.             if (score > bestScore) {
  133.                 bestScore = score;
  134.                 bestAssignment = new ResourceAssignment(
  135.                     resource, lastAllocation, startTime, endTime, score);
  136.             }
  137.         }
  138.         return bestAssignment;
  139.     }
  140.     private LocalDateTime calculateStartTime(
  141.             LocalDateTime earliestStart,
  142.             JobAllocation lastAllocation,
  143.             Resource resource) {
  144.         
  145.         LocalDateTime resourceAvailable = lastAllocation != null ?
  146.             lastAllocation.getEndTime() :
  147.             resource.getAvailableFrom();
  148.             
  149.         return earliestStart.isAfter(resourceAvailable) ?
  150.             earliestStart : resourceAvailable;
  151.     }
  152.     private double calculateAssignmentScore(
  153.             Resource resource,
  154.             LocalDateTime startTime,
  155.             LocalDateTime endTime,
  156.             Job job,
  157.             JobShopSchedule schedule) {
  158.         
  159.         double score = 0.0;
  160.         
  161.         // Factor 1: Resource utilization balance
  162.         score -= getResourceUtilization(resource, schedule) * 2;
  163.         
  164.         // Factor 2: Start time (earlier is better)
  165.         score -= startTime.until(job.getProject().getDueDate(), ChronoUnit.HOURS);
  166.         
  167.         // Factor 3: Resource cost
  168.         score -= resource.getCostPerHour() *
  169.             job.getProcessingTime().toHours();
  170.         
  171.         // Factor 4: Project critical path consideration
  172.         if (isOnCriticalPath(job)) {
  173.             score += 1000;  // Prioritize critical path jobs
  174.         }
  175.         
  176.         return score;
  177.     }
  178.     private double getResourceUtilization(Resource resource, JobShopSchedule schedule) {
  179.         return schedule.getAllocations().stream()
  180.             .filter(a -> a.getResource() == resource)
  181.             .mapToDouble(a -> a.getJob().getProcessingTime().toHours())
  182.             .sum();
  183.     }
  184.     private boolean isOnCriticalPath(Job job) {
  185.         // Simple critical path detection
  186.         Job current = job;
  187.         while (current.getNextJob() != null) {
  188.             current = current.getNextJob();
  189.         }
  190.         return current.getProject().getDueDate()
  191.             .minusHours(calculatePathDuration(job))
  192.             .isBefore(LocalDateTime.now());
  193.     }
  194.     private long calculatePathDuration(Job startJob) {
  195.         long duration = 0;
  196.         Job current = startJob;
  197.         while (current != null) {
  198.             duration += current.getProcessingTime().toHours();
  199.             current = current.getNextJob();
  200.         }
  201.         return duration;
  202.     }
  203. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

鼠扑

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表